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