forked from apps/featurer
		
	Initial commit.
This commit is contained in:
		
							
								
								
									
										59
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| --- | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: "Tests and linting" | ||||
|  | ||||
| trigger: | ||||
|   event: | ||||
|     exclude: | ||||
|       - pull_request | ||||
|  | ||||
| steps: | ||||
|   - name: Linting | ||||
|     image: code.pztrn.name/containers/go-toolbox:v4 | ||||
|     pull: if-not-exists | ||||
|     commands: | ||||
|       - task lint | ||||
|  | ||||
|   - name: Tests | ||||
|     image: code.pztrn.name/containers/go-toolbox:v4 | ||||
|     pull: if-not-exists | ||||
|     commands: | ||||
|       - task test | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: "Build Release" | ||||
|  | ||||
| depends_on: | ||||
|   - "Tests and linting" | ||||
|  | ||||
| trigger: | ||||
|   event: | ||||
|     - tag | ||||
|  | ||||
| steps: | ||||
|   - name: Build Docker image for Featurer | ||||
|     image: code.pztrn.name/containers/mirror/plugins/buildx:1.1.11 | ||||
|     settings: | ||||
|       registry: code.pztrn.name | ||||
|       username: drone | ||||
|       password: | ||||
|         from_secret: drone_secret | ||||
|       repo: code.pztrn.name/pztrn/notificator | ||||
|       dockerfile: server/Dockerfile.featurer | ||||
|       auto_tag: true | ||||
|       force_tag: true | ||||
|  | ||||
|   - name: Build Docker image for CMS | ||||
|     image: code.pztrn.name/containers/mirror/plugins/buildx:1.1.11 | ||||
|     settings: | ||||
|       registry: code.pztrn.name | ||||
|       username: drone | ||||
|       password: | ||||
|         from_secret: drone_secret | ||||
|       repo: code.pztrn.name/pztrn/notificator | ||||
|       dockerfile: server/Dockerfile.CMS | ||||
|       auto_tag: true | ||||
|       force_tag: true | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| *DS_Store* | ||||
| _build | ||||
							
								
								
									
										71
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| --- | ||||
| run: | ||||
|   timeout: 5m | ||||
|  | ||||
| linters: | ||||
|   enable-all: true | ||||
|   disable: | ||||
|     # deprecated | ||||
|     - varnamelen | ||||
|     - testpackage | ||||
|     - containedctx # very strange linter | ||||
|     - gomnd | ||||
|     - execinquery | ||||
|     # meaningfully disabled | ||||
|     - gochecknoglobals | ||||
|     - exhaustruct | ||||
|     - depguard # wtf is this linter | ||||
|     - tparallel # duplicates paralleltest | ||||
|     - unused # not very clever lint for generics | ||||
|     - ireturn # not very clever lint for generics | ||||
|     - interfacebloat # love big interfaces :) | ||||
|     - gci # whines about imports too much. | ||||
|  | ||||
| linters-settings: | ||||
|   cyclop: | ||||
|     skip-tests: true | ||||
|     max-complexity: 20 | ||||
|     package-average: 10 | ||||
|   forbidigo: | ||||
|     forbid: | ||||
|       - '^(fmt\.Print(|f|ln)|print|println)$' | ||||
|       - '^time\.Now\(\)($|\.F|\.A|\.B|\.L|\.UTC\(\)\.I|,|\))(# Calls of time\.Now() without \.UTC() is prohibited\.)?' | ||||
|   funlen: | ||||
|     ignore-comments: true | ||||
|     lines: 200 | ||||
|     statements: 60 | ||||
|   gocyclo: | ||||
|     min-complexity: 20 | ||||
|   gofumpt: | ||||
|     extra-rules: true | ||||
|   govet: | ||||
|     enable-all: true | ||||
|   lll: | ||||
|     line-length: 120 | ||||
|   nestif: | ||||
|     min-complexity: 20 | ||||
|   tagliatelle: | ||||
|     case: | ||||
|       use-field-name: true | ||||
|       rules: | ||||
|         json: snake | ||||
|         yaml: camel | ||||
|  | ||||
| issues: | ||||
|   exclude-use-default: false | ||||
|   exclude: | ||||
|     - ST1000 # package comment | ||||
|     - package-comments | ||||
|   exclude-rules: | ||||
|     - path: .+_test\.go | ||||
|       linters: | ||||
|         - gosec | ||||
|     - linters: | ||||
|         - godox | ||||
|       text: "TODO" | ||||
|     # Default error checking pattern "if err := ... ;err != nil {}" multiple times makes go vet to whine. | ||||
|     - linters: | ||||
|         - govet | ||||
|       text: 'declaration of "err" shadows' | ||||
|   max-issues-per-linter: 0 | ||||
|   max-same-issues: 0 | ||||
							
								
								
									
										4
									
								
								.markdownlint.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.markdownlint.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| { | ||||
|     "line-length": false, | ||||
|     "first-line-h1": false | ||||
| } | ||||
							
								
								
									
										9
									
								
								.yamllint
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.yamllint
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| --- | ||||
| extends: default | ||||
|  | ||||
| rules: | ||||
|   line-length: disable | ||||
|   braces: | ||||
|     min-spaces-inside: 1 | ||||
|     max-spaces-inside: 1 | ||||
|  | ||||
							
								
								
									
										58
									
								
								Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| --- | ||||
| version: "3" | ||||
|  | ||||
| vars: | ||||
|   BRANCH: | ||||
|     sh: ./scripts/version-data-generator.sh branch | ||||
|   BUILD_DATE: | ||||
|     sh: date '+%Y-%m-%d %H:%M:%S %z' | ||||
|   BUILD_NUMBER: | ||||
|     sh: ./scripts/version-data-generator.sh build_number | ||||
|   COMMIT: | ||||
|     sh: ./scripts/version-data-generator.sh commit | ||||
|   VERSION: | ||||
|     sh: ./scripts/version-data-generator.sh version | ||||
|   BASIC_LDFLAGS: "-X 'go.dev.pztrn.name/featurer/server/internal/application.Version={{ .VERSION }}' -X 'go.dev.pztrn.name/featurer/server/internal/application.Branch={{ .BRANCH }}' -X 'go.dev.pztrn.name/featurer/server/internal/application.Commit={{ .COMMIT }}' -X 'go.dev.pztrn.name/featurer/server/internal/application.Build={{ .BUILD_NUMBER }}' -X 'go.dev.pztrn.name/featurer/server/internal/application.BuildDate={{ .BUILD_DATE }}'" | ||||
|   LDFLAGS: "{{ .BASIC_LDFLAGS }}" | ||||
|  | ||||
| includes: | ||||
|   server: ./server | ||||
|  | ||||
| tasks: | ||||
|   cleanup: | ||||
|     desc: "Cleanup _build directory." | ||||
|     cmds: | ||||
|       - task: server:cmd:cms:cleanup | ||||
|       - task: server:cmd:featurer:cleanup | ||||
|  | ||||
|   default: | ||||
|     desc: "Default help." | ||||
|     cmds: | ||||
|       - echo "Run \"task -l\" for list of available tasks." | ||||
|  | ||||
|   lint: | ||||
|     desc: "Runs linters." | ||||
|     cmds: | ||||
|       - golangci-lint run ./... | ||||
|       - go vet ./... | ||||
|  | ||||
|   pre-commit: | ||||
|     desc: "Runs some tasks before comitting." | ||||
|     cmds: | ||||
|       - task: lint | ||||
|       - task: test | ||||
|  | ||||
|   test: | ||||
|     desc: "Run tests." | ||||
|     cmds: | ||||
|       - go test ./... | ||||
|  | ||||
|   vars: | ||||
|     desc: "Show vars which will be used for building or running." | ||||
|     silent: true | ||||
|     cmds: | ||||
|       - echo "BRANCH={{ .BRANCH }}" | ||||
|       - echo "BUILD_DATE={{ .BUILD_DATE }}" | ||||
|       - echo "BUILD_NUMBER={{ .BUILD_NUMBER }}" | ||||
|       - echo "COMMIT={{ .COMMIT }}" | ||||
|       - echo "VERSION={{ .VERSION }}" | ||||
							
								
								
									
										4
									
								
								client/go/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								client/go/client.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| package client | ||||
|  | ||||
| // Client is a Featurer client for Golang application. | ||||
| type Client struct{} | ||||
							
								
								
									
										42
									
								
								scripts/version-data-generator.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										42
									
								
								scripts/version-data-generator.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| #!/usr/bin/env bash | ||||
|  | ||||
| VERSION_PREFIX="v" | ||||
|  | ||||
| # shellcheck disable=SC1083 | ||||
| BUILD_NUMBER=$(git rev-list --count --all) | ||||
| BRANCH_NAME=$(git branch --show-current) | ||||
| COMMIT=$(git rev-parse HEAD) | ||||
| VERSION=$(git tag | sort | grep "${VERSION_PREFIX}" | tail -n 1 | cut -c$((${#VERSION_PREFIX} + 1))-) | ||||
|  | ||||
| if [ "${VERSION}" == "" ]; then | ||||
|     VERSION="0.1.0-dev" | ||||
| fi | ||||
|  | ||||
| function show_help() { | ||||
|     echo "$0 [param]" | ||||
|     echo "" | ||||
|     echo "Parameters:" | ||||
|     echo "" | ||||
|     echo "  branch          Show branch." | ||||
|     echo "  build_number    Show build number (commits count)." | ||||
|     echo "  commit          Show commit hash." | ||||
|     echo "  version         Show version (real or '0.1.0-dev' if no version tags present)." | ||||
| } | ||||
|  | ||||
| case $1 in | ||||
| branch) | ||||
|     echo "${BRANCH_NAME}" | ||||
|     ;; | ||||
| build_number) | ||||
|     echo "${BUILD_NUMBER}" | ||||
|     ;; | ||||
| commit) | ||||
|     echo "${COMMIT}" | ||||
|     ;; | ||||
| version) | ||||
|     echo "${VERSION}" | ||||
|     ;; | ||||
| *) | ||||
|     show_help | ||||
|     ;; | ||||
| esac | ||||
							
								
								
									
										18
									
								
								server/Dockerfile.CMS
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								server/Dockerfile.CMS
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| FROM code.pztrn.name/containers/go-toolbox:v4 AS build | ||||
|  | ||||
| WORKDIR /featurer | ||||
| COPY . /featurer | ||||
| RUN --mount=type=cache,target="$GOCACHE" task server:cmd:cms:build | ||||
|  | ||||
| FROM debian:12.6-slim | ||||
|  | ||||
| RUN apt-get update && \ | ||||
|     apt-get install -y ca-certificates iputils-ping && \ | ||||
|     rm -rf /var/lib/apt/* /var/cache/apt/* | ||||
|  | ||||
| COPY --from=build /featurer/_build/featurer-cms /featurer-cms | ||||
| COPY --from=build /usr/local/bin/dlv /dlv | ||||
|  | ||||
| COPY server/entrypoint-cms.sh /entrypoint.sh | ||||
|  | ||||
| ENTRYPOINT ["/entrypoint.sh"] | ||||
							
								
								
									
										18
									
								
								server/Dockerfile.featurer
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								server/Dockerfile.featurer
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| FROM code.pztrn.name/containers/go-toolbox:v4 AS build | ||||
|  | ||||
| WORKDIR /featurer | ||||
| COPY . /featurer | ||||
| RUN --mount=type=cache,target="$GOCACHE" task server:cmd:featurer:build | ||||
|  | ||||
| FROM debian:12.6-slim | ||||
|  | ||||
| RUN apt-get update && \ | ||||
|     apt-get install -y ca-certificates iputils-ping && \ | ||||
|     rm -rf /var/lib/apt/* /var/cache/apt/* | ||||
|  | ||||
| COPY --from=build /featurer/_build/featurer /featurer | ||||
| COPY --from=build /usr/local/bin/dlv /dlv | ||||
|  | ||||
| COPY server/entrypoint-featurer.sh /entrypoint.sh | ||||
|  | ||||
| ENTRYPOINT ["/entrypoint.sh"] | ||||
							
								
								
									
										6
									
								
								server/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								server/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| --- | ||||
| version: "3" | ||||
|  | ||||
| includes: | ||||
|   cmd: ./cmd | ||||
|   localdev: ./localdev | ||||
							
								
								
									
										6
									
								
								server/cmd/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								server/cmd/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| --- | ||||
| version: "3" | ||||
|  | ||||
| includes: | ||||
|   cms: ./cms | ||||
|   featurer: ./featurer | ||||
							
								
								
									
										21
									
								
								server/cmd/cms/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								server/cmd/cms/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| --- | ||||
| version: "3" | ||||
|  | ||||
| tasks: | ||||
|   build: | ||||
|     desc: "Builds Featurer's CMS binary." | ||||
|     cmds: | ||||
|       - task: cleanup | ||||
|       - go build -ldflags="{{ .LDFLAGS }}" -tags netgo -o _build/featurer-cms{{exeExt}} ./server/cmd/cms/main.go | ||||
|     sources: | ||||
|       - ./**/*.go | ||||
|       - ./Taskfile.yml | ||||
|       - ./go.mod | ||||
|     generates: | ||||
|       - ./_build/featurer-cms{{exeExt}} | ||||
|     method: none | ||||
|  | ||||
|   cleanup: | ||||
|     desc: "Deletes Featurer's CMS binary from local build cache." | ||||
|     cmds: | ||||
|       - rm -f _build/featurer-cms{{exeExt}} | ||||
							
								
								
									
										42
									
								
								server/cmd/cms/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								server/cmd/cms/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"log/slog" | ||||
|  | ||||
| 	"go.dev.pztrn.name/featurer/server/internal/application" | ||||
| 	"go.dev.pztrn.name/featurer/server/internal/services/core/datastore" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	_ = slog.SetLogLoggerLevel(slog.LevelDebug) | ||||
|  | ||||
| 	app := application.New() | ||||
|  | ||||
| 	slog.Info( | ||||
| 		"Launching Featurer's CMS...", | ||||
| 		"version", application.Version, | ||||
| 		"build", application.Build, | ||||
| 		"branch", application.Branch, | ||||
| 		"commit", application.Commit, | ||||
| 		"build date", application.BuildDate, | ||||
| 	) | ||||
|  | ||||
| 	// Initializing core services first. | ||||
| 	checkError(datastore.Initialize(app)) | ||||
|  | ||||
| 	// Then - features services. | ||||
|  | ||||
| 	// Start application. | ||||
| 	checkError(app.Start()) | ||||
|  | ||||
| 	<-app.GetShutdownDoneChannel() | ||||
| 	slog.Info("Featurer's CMS.") | ||||
| } | ||||
|  | ||||
| func checkError(err error) { | ||||
| 	if err == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	panic(err) | ||||
| } | ||||
							
								
								
									
										21
									
								
								server/cmd/featurer/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								server/cmd/featurer/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| --- | ||||
| version: "3" | ||||
|  | ||||
| tasks: | ||||
|   build: | ||||
|     desc: "Builds Featurer main binary." | ||||
|     cmds: | ||||
|       - task: cleanup | ||||
|       - go build -ldflags="{{ .LDFLAGS }}" -tags netgo -o _build/featurer{{exeExt}} ./server/cmd/featurer/main.go | ||||
|     sources: | ||||
|       - ./**/*.go | ||||
|       - ./Taskfile.yml | ||||
|       - ./go.mod | ||||
|     generates: | ||||
|       - ./_build/featurer{{exeExt}} | ||||
|     method: none | ||||
|  | ||||
|   cleanup: | ||||
|     desc: "Deletes Featurer main binary from local build cache." | ||||
|     cmds: | ||||
|       - rm -f _build/featurer{{exeExt}} | ||||
							
								
								
									
										42
									
								
								server/cmd/featurer/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								server/cmd/featurer/main.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"log/slog" | ||||
|  | ||||
| 	"go.dev.pztrn.name/featurer/server/internal/application" | ||||
| 	"go.dev.pztrn.name/featurer/server/internal/services/core/datastore" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	_ = slog.SetLogLoggerLevel(slog.LevelDebug) | ||||
|  | ||||
| 	app := application.New() | ||||
|  | ||||
| 	slog.Info( | ||||
| 		"Launching Featurer server...", | ||||
| 		"version", application.Version, | ||||
| 		"build", application.Build, | ||||
| 		"branch", application.Branch, | ||||
| 		"commit", application.Commit, | ||||
| 		"build date", application.BuildDate, | ||||
| 	) | ||||
|  | ||||
| 	// Initializing core services first. | ||||
| 	checkError(datastore.Initialize(app)) | ||||
|  | ||||
| 	// Then - features services. | ||||
|  | ||||
| 	// Start application. | ||||
| 	checkError(app.Start()) | ||||
|  | ||||
| 	<-app.GetShutdownDoneChannel() | ||||
| 	slog.Info("Featurer stopped.") | ||||
| } | ||||
|  | ||||
| func checkError(err error) { | ||||
| 	if err == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	panic(err) | ||||
| } | ||||
							
								
								
									
										8
									
								
								server/entrypoint-cms.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										8
									
								
								server/entrypoint-cms.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| echo "* Starting Featurer's CMS..." | ||||
| if [ -n "${FEATURER_DEBUG}" ]; then | ||||
|     exec /dlv --listen=:4000 --headless=true --log=true --accept-multiclient --api-version=2 exec /featurer-cms --continue | ||||
| else | ||||
|     exec /featurer-cms | ||||
| fi | ||||
							
								
								
									
										8
									
								
								server/entrypoint-featurer.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										8
									
								
								server/entrypoint-featurer.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| echo "* Starting Featurer..." | ||||
| if [ -n "${FEATURER_DEBUG}" ]; then | ||||
|     exec /dlv --listen=:4000 --headless=true --log=true --accept-multiclient --api-version=2 exec /featurer --continue | ||||
| else | ||||
|     exec /featurer | ||||
| fi | ||||
							
								
								
									
										114
									
								
								server/internal/application/application.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								server/internal/application/application.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"sync" | ||||
| 	"syscall" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// ErrApplication indicates that error appeared somewhere in application's lifecycle controller. | ||||
| 	ErrApplication = errors.New("application") | ||||
|  | ||||
| 	// Version is application's version. | ||||
| 	Version string | ||||
| 	// Branch is a branch name from which application was built. | ||||
| 	Branch string | ||||
| 	// Commit is a commit hash from which application was built. | ||||
| 	Commit string | ||||
| 	// Build is a build number. | ||||
| 	Build string | ||||
| 	// BuildDate is a date on which application was built. | ||||
| 	BuildDate string | ||||
| ) | ||||
|  | ||||
| // Application is an application's lifecycle controlling structure. | ||||
| type Application struct { | ||||
| 	ctx           context.Context | ||||
| 	shutdownDone  chan struct{} | ||||
| 	cancelFunc    context.CancelFunc | ||||
| 	services      map[string]Service | ||||
| 	dataPath      string | ||||
| 	servicesMutex sync.RWMutex | ||||
| } | ||||
|  | ||||
| // New creates new application's lifecycle controlling structure. | ||||
| func New() *Application { | ||||
| 	appl := &Application{} | ||||
| 	appl.initialize() | ||||
|  | ||||
| 	return appl | ||||
| } | ||||
|  | ||||
| // GetContext returns application's global context. | ||||
| func (a *Application) GetContext() context.Context { | ||||
| 	return a.ctx | ||||
| } | ||||
|  | ||||
| // GetShutdownDoneChannel returns channel on which lifecycle controller will tell about shutdown completion. | ||||
| // Should be used only in main()! | ||||
| func (a *Application) GetShutdownDoneChannel() chan struct{} { | ||||
| 	slog.Debug("Returning shutdown completion channel.") | ||||
|  | ||||
| 	return a.shutdownDone | ||||
| } | ||||
|  | ||||
| func (a *Application) initialize() { | ||||
| 	a.ctx, a.cancelFunc = context.WithCancel(context.Background()) | ||||
| 	a.ctx, a.cancelFunc = signal.NotifyContext(a.ctx, os.Interrupt, syscall.SIGTERM) | ||||
|  | ||||
| 	a.services = make(map[string]Service) | ||||
|  | ||||
| 	a.shutdownDone = make(chan struct{}, 1) | ||||
| } | ||||
|  | ||||
| // Shutdown stops application and all registered services. | ||||
| func (a *Application) Shutdown() error { | ||||
| 	a.cancelFunc() | ||||
|  | ||||
| 	a.servicesMutex.RLock() | ||||
| 	defer a.servicesMutex.RUnlock() | ||||
|  | ||||
| 	for _, service := range a.services { | ||||
| 		if err := service.Shutdown(); err != nil { | ||||
| 			slog.Error("Error appeared when trying to shut down service", "service", service.GetName(), "error", err.Error()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	a.shutdownDone <- struct{}{} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Start starts application and all registered services. | ||||
| func (a *Application) Start() error { | ||||
| 	go a.signalsListener() | ||||
|  | ||||
| 	a.servicesMutex.RLock() | ||||
| 	defer a.servicesMutex.RUnlock() | ||||
|  | ||||
| 	// First pass - connecting dependencies. | ||||
| 	for _, service := range a.services { | ||||
| 		if err := service.ConnectDependencies(); err != nil { | ||||
| 			return fmt.Errorf("%w: connecting dependencies for service '%s': %w", ErrApplication, service.GetName(), err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Second pass - launching startup tasks. | ||||
| 	for name, service := range a.services { | ||||
| 		slog.Debug("Launching startup tasks for service", "service", name) | ||||
|  | ||||
| 		if err := service.LaunchStartupTasks(); err != nil { | ||||
| 			return fmt.Errorf("%w: launching startup tasks for '%s': %w", ErrApplication, service.GetName(), err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	slog.Debug("Application started.") | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										54
									
								
								server/internal/application/service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								server/internal/application/service.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	// ErrServiceAlreadyRegistered appears when trying to register a service with already taken name. | ||||
| 	ErrServiceAlreadyRegistered = errors.New("service already registered") | ||||
| 	// ErrServiceNotRegistered appears when trying to get a registered service with unknown name. | ||||
| 	ErrServiceNotRegistered = errors.New("unknown service") | ||||
| ) | ||||
|  | ||||
| // Service это интерфейс, которому должны соответствовать все сервисы, используемые в приложении. | ||||
| type Service interface { | ||||
| 	ConnectDependencies() error | ||||
| 	GetName() string | ||||
| 	Initialize() error | ||||
| 	LaunchStartupTasks() error | ||||
| 	Shutdown() error | ||||
| } | ||||
|  | ||||
| // GetService возвращает сервис по имени, или же ошибку, если сервис не был зарегистрирован ранее. | ||||
| func (a *Application) GetService(name string) (Service, error) { | ||||
| 	a.servicesMutex.RLock() | ||||
| 	service, found := a.services[name] | ||||
| 	a.servicesMutex.RUnlock() | ||||
|  | ||||
| 	if !found { | ||||
| 		return nil, fmt.Errorf("%w: get service '%s': %w", ErrApplication, name, ErrServiceNotRegistered) | ||||
| 	} | ||||
|  | ||||
| 	return service, nil | ||||
| } | ||||
|  | ||||
| // RegisterService регистрирует сервис, или возвращает ошибку, если сервис с таким именем уже был | ||||
| // зарегистрирован ранее. | ||||
| func (a *Application) RegisterService(service Service) error { | ||||
| 	_, found := a.services[service.GetName()] | ||||
| 	if found { | ||||
| 		return fmt.Errorf("%w: register service '%s': %w", ErrApplication, service.GetName(), ErrServiceAlreadyRegistered) | ||||
| 	} | ||||
|  | ||||
| 	if err := service.Initialize(); err != nil { | ||||
| 		return fmt.Errorf("%w: initializing service '%s': %w", ErrApplication, service.GetName(), err) | ||||
| 	} | ||||
|  | ||||
| 	a.servicesMutex.Lock() | ||||
| 	a.services[service.GetName()] = service | ||||
| 	a.servicesMutex.Unlock() | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										24
									
								
								server/internal/application/signals.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								server/internal/application/signals.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| package application | ||||
|  | ||||
| import ( | ||||
| 	"log/slog" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"syscall" | ||||
| ) | ||||
|  | ||||
| func (a *Application) signalsListener() { | ||||
| 	slog.Info("Starting listening for SIGTERM signal...") | ||||
|  | ||||
| 	listener := make(chan os.Signal, 1) | ||||
|  | ||||
| 	signal.Notify(listener, syscall.SIGTERM, os.Interrupt) | ||||
|  | ||||
| 	<-listener | ||||
| 	slog.Info("Got SIGTERM, stopping Featurer...") | ||||
|  | ||||
| 	if err := a.Shutdown(); err != nil { | ||||
| 		slog.Error("Something went wrong when trying to shutdown application", "error", err.Error()) | ||||
| 		slog.Error("!!! APPLICATION CANNOT BE STOPPED NORMALLY, KILL IT MANUALLY !!!") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										16
									
								
								server/internal/services/core/datastore.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								server/internal/services/core/datastore.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| package core | ||||
|  | ||||
| import "errors" | ||||
|  | ||||
| // ServiceNameDatastore is a service name for data storage implementation. | ||||
| const ServiceNameDatastore = "datastore" | ||||
|  | ||||
| var ( | ||||
| 	// ErrDatastore indicates that error appeared somewhere in data storage service. | ||||
| 	ErrDatastore = errors.New("datastore") | ||||
| 	// ErrDatastoreServiceIsInvalid appears when data storage implementation isn't conforming to interface. | ||||
| 	ErrDatastoreServiceIsInvalid = errors.New("invalid datastore implementation") | ||||
| ) | ||||
|  | ||||
| // DataStore is an interface for data storage implementations. | ||||
| type DataStore interface{} | ||||
							
								
								
									
										48
									
								
								server/internal/services/core/datastore/datastore.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								server/internal/services/core/datastore/datastore.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| package datastore | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"log/slog" | ||||
|  | ||||
| 	"go.dev.pztrn.name/featurer/server/internal/application" | ||||
| 	"go.dev.pztrn.name/featurer/server/internal/services/core" | ||||
| ) | ||||
|  | ||||
| type datastore struct { | ||||
| 	app *application.Application | ||||
| } | ||||
|  | ||||
| // Initialize initializes service. | ||||
| func Initialize(app *application.Application) error { | ||||
| 	plan := &datastore{ | ||||
| 		app: app, | ||||
| 	} | ||||
|  | ||||
| 	if err := app.RegisterService(plan); err != nil { | ||||
| 		return fmt.Errorf("%w: %w", core.ErrDatastore, err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *datastore) ConnectDependencies() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *datastore) GetName() string { | ||||
| 	return core.ServiceNameDatastore | ||||
| } | ||||
|  | ||||
| func (p *datastore) Initialize() error { | ||||
| 	slog.Info("Initializing data storage...") | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *datastore) LaunchStartupTasks() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (p *datastore) Shutdown() error { | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										19
									
								
								server/localdev/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								server/localdev/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| --- | ||||
| version: 3 | ||||
|  | ||||
| includes: | ||||
|   common: ./common | ||||
|   featurer: ./featurer | ||||
|  | ||||
| tasks: | ||||
|   down: | ||||
|     desc: "Removes development environment." | ||||
|     cmds: | ||||
|       - task: featurer:down | ||||
|       - task: common:network-down | ||||
|  | ||||
|   up: | ||||
|     desc: "Creates development environment." | ||||
|     cmds: | ||||
|       - task: common:network-up | ||||
|       - task: featurer:up | ||||
							
								
								
									
										15
									
								
								server/localdev/common/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								server/localdev/common/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| --- | ||||
| version: "3" | ||||
|  | ||||
| tasks: | ||||
|   network-down: | ||||
|     desc: "Deletes Docker network for development." | ||||
|     dir: "./server/localdev/common" | ||||
|     cmds: | ||||
|       - docker compose -p featurer-network -f network.yaml down | ||||
|  | ||||
|   network-up: | ||||
|     desc: "Creates Docker network for development" | ||||
|     dir: "./server/localdev/common" | ||||
|     cmds: | ||||
|       - docker compose -p featurer-network  -f network.yaml up -d | ||||
							
								
								
									
										18
									
								
								server/localdev/common/network.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								server/localdev/common/network.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| --- | ||||
| services: | ||||
|   dummy: | ||||
|     image: busybox | ||||
|     container_name: featurer-dummy | ||||
|     hostname: dummy | ||||
|     networks: | ||||
|       featurer: | ||||
|         ipv4_address: 248.248.0.254 | ||||
|  | ||||
| networks: | ||||
|   featurer: | ||||
|     driver: bridge | ||||
|     name: "featurer" | ||||
|     ipam: | ||||
|       config: | ||||
|         - subnet: 248.248.0.0/24 | ||||
|           gateway: 248.248.0.1 | ||||
							
								
								
									
										47
									
								
								server/localdev/featurer/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								server/localdev/featurer/Taskfile.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| --- | ||||
| version: "3" | ||||
|  | ||||
| tasks: | ||||
|   build: | ||||
|     desc: "Builds Featurer's container." | ||||
|     dir: "./server/localdev/featurer" | ||||
|     cmds: | ||||
|       - docker compose -p featurer -f docker-compose.yaml build | ||||
|  | ||||
|   down: | ||||
|     desc: "Deletes all Featurer's data (down)." | ||||
|     dir: "./server/localdev/featurer" | ||||
|     cmds: | ||||
|       - docker compose -p featurer -f docker-compose.yaml down --volumes | ||||
|  | ||||
|   logs: | ||||
|     desc: "Show Featurer logs." | ||||
|     dir: "./server/localdev/featurer" | ||||
|     cmds: | ||||
|       - docker compose -p featurer -f docker-compose.yaml logs -f | ||||
|  | ||||
|   restart: | ||||
|     desc: "Restart Featurer." | ||||
|     dir: "./server/localdev/featurer" | ||||
|     cmds: | ||||
|       - docker compose -p featurer -f docker-compose.yaml restart | ||||
|  | ||||
|   start: | ||||
|     desc: "Start Featurer." | ||||
|     dir: "./server/localdev/featurer" | ||||
|     cmds: | ||||
|       - docker compose -p featurer -f docker-compose.yaml start | ||||
|  | ||||
|   stop: | ||||
|     desc: "Stop Featurer without deleting it's data." | ||||
|     dir: "./server/localdev/featurer" | ||||
|     cmds: | ||||
|       - docker compose -p featurer -f docker-compose.yaml stop | ||||
|  | ||||
|   up: | ||||
|     desc: "Start Featurer (up -d)." | ||||
|     dir: "./server/localdev/featurer" | ||||
|     cmds: | ||||
|       - task: :common:network-up | ||||
|       - task: build | ||||
|       - docker compose -p featurer -f docker-compose.yaml up -d | ||||
							
								
								
									
										16
									
								
								server/localdev/featurer/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								server/localdev/featurer/docker-compose.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| --- | ||||
| services: | ||||
|   featurer: | ||||
|     container_name: "featurer" | ||||
|     build: | ||||
|       context: ../../../ | ||||
|       dockerfile: server/Dockerfile.featurer | ||||
|     networks: | ||||
|       featurer: | ||||
|         ipv4_address: 248.248.0.2 | ||||
|     cap_add: | ||||
|       - SYS_PTRACE | ||||
|  | ||||
| networks: | ||||
|   featurer: | ||||
|     external: true | ||||
		Reference in New Issue
	
	Block a user