Initial commit.
This commit is contained in:
commit
57937a5845
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
|
Loading…
Reference in New Issue
Block a user