diff --git a/Dockerfile b/Dockerfile index 662d52d..a203012 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,10 +11,10 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o /simpleboards cmd/simpleboards/main.go # Run the tests in the container FROM build-stage AS run-test-stage -RUN go test -v ./... +RUN go test -v ./internal/... # Deploy the application binary into a lean image -FROM alpine:3.18 AS build-release-stage +FROM alpine:3.19 AS build-release-stage WORKDIR / diff --git a/README.md b/README.md index 4d457ce..73197bf 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,18 @@ - VPC - Subnets +## Test locally with Kubernetes +### install k9s +`brew install derailed/k9s/k9s` +### Setup minikube + +Ref: https://minikube.sigs.k8s.io/docs/start + +- `curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-arm64` +- `sudo install minikube-darwin-arm64 /usr/local/bin/minikube` +- start the cluster: `minikube start -p simpleboards` +- install helm `minikube addons enable helm-tiller -p default` +- `minikube addons enable yakd -p default ` +- `minikube -p default service yakd-dashboard -n yakd-dashboard` +- ` minikube -p default addons enable metrics-server` +- `minikube -p default dashboard --url` diff --git a/internal/adapters/output/repository/dynamodb.go b/internal/adapters/output/repository/dynamodb.go index ef6dd6b..b4e3072 100644 --- a/internal/adapters/output/repository/dynamodb.go +++ b/internal/adapters/output/repository/dynamodb.go @@ -230,50 +230,6 @@ func (r *DynamoDBRepository) MinWithMetadata(entry string, leaderboard string, v return domain.ScoreUpdate{Score: s.Score, Done: true, Counter: s.Counter}, nil } -func (r *DynamoDBRepository) debugExpression(expr expression.Expression, meta domain.Metadata, entry string, leaderboard string) { - ctx, cancel := context.WithTimeoutCause(context.Background(), 1*time.Second, errors.New("get configuration timeout")) - defer cancel() - - keyCond := expression.KeyAnd( - expression.Key(hashKeyName).Equal(expression.Value(pkValue(entry))), - expression.Key(sortKeyName).Equal(expression.Value(skValue(leaderboard))), - ) - exprq, err := expression.NewBuilder().WithKeyCondition(keyCond).Build() - if err != nil { - panic(err) - } - input := dynamodb.QueryInput{ - TableName: aws.String(r.tableName), - ExpressionAttributeNames: exprq.Names(), - ExpressionAttributeValues: exprq.Values(), - KeyConditionExpression: exprq.KeyCondition(), - } - - out, err := r.client.Query(ctx, &input) - if err != nil { - panic(err) - } - var it []map[string]interface{} - err = attributevalue.UnmarshalListOfMaps(out.Items, &it) - - if err != nil { - panic(err) - } - - fmt.Println("items:", it) - - var v map[string]interface{} - attributevalue.UnmarshalMap(expr.Values(), &v) - fmt.Println() - fmt.Println() - if expr.Condition() != nil { - - fmt.Println("condition:", *expr.Condition(), "names", expr.Names(), "values", v) - } - fmt.Println() - fmt.Println() -} - // LastWithMetadata ... func (r *DynamoDBRepository) LastWithMetadata(entry string, leaderboard string, value float64, meta domain.Metadata) (domain.ScoreUpdate, error) { builder := expression.NewBuilder() @@ -457,7 +413,6 @@ func (*DynamoDBRepository) builderFromMetadata(meta domain.Metadata) expression. expression.And( expression.AttributeExists(expression.Name(a)), expression.Equal(expression.Name(a), expression.Value(v)))) - } } diff --git a/internal/core/domain/cron.go b/internal/core/domain/cron.go index fe9a960..ec6f11a 100644 --- a/internal/core/domain/cron.go +++ b/internal/core/domain/cron.go @@ -2,18 +2,23 @@ package domain import ( "fmt" - "math" "time" "github.com/gorhill/cronexpr" ) +type epochCache struct { + epoch int64 + timestamp time.Time +} + // CronExpression data type CronExpression struct { - expr *cronexpr.Expression - first time.Time - second time.Time - interval int64 + expr *cronexpr.Expression + first time.Time + second time.Time + interval int64 + lastEpochCached epochCache } // NewCronExpression creates a cron expression from a reset expression @@ -32,27 +37,105 @@ func NewCronExpression(reset ResetExpression) (CronExpression, error) { e = reset.CronExpression } - initUnix := time.Unix(0, 0).UTC() expr, err := cronexpr.Parse(e) if err != nil { return CronExpression{}, fmt.Errorf("failed to parse cron expression '%v': %v", e, err) } - + initUnix := time.Unix(0, 0).UTC() first := expr.Next(initUnix) second := expr.Next(first) intervalSecs := second.Sub(first).Seconds() - return CronExpression{ - expr: expr, - first: first, - second: second, - interval: int64(intervalSecs), + lec, err := calculateCurrentEpochSince(expr, initUnix) + if err != nil { + return CronExpression{}, fmt.Errorf("failed to calculate epoch: %w", err) + } + ce := CronExpression{ + expr: expr, + first: first, + second: second, + interval: int64(intervalSecs), + lastEpochCached: lec, + } + return ce, nil +} + +func calculateCurrentEpochSince(expr *cronexpr.Expression, since time.Time) (epochCache, error) { + now := time.Now().UTC() + + ec, err := calculateEpochBetween(expr, since, now) + if err != nil { + return epochCache{}, err + } + return ec, nil +} + +func calculateEpochBetween(expr *cronexpr.Expression, from time.Time, to time.Time) (epochCache, error) { + if !from.Before(to) { + return epochCache{}, fmt.Errorf("'from' time needs to be before 'to' time") + } + + next := from + previous := next + var epoch int64 = 1 + for next.Unix() < to.Unix() { + previous = next + next = expr.Next(next) + epoch++ + } + + return epochCache{ + epoch: epoch, + timestamp: previous, }, nil } +// GetCurrentEpoch returns the epoch based in the current Timestamp +func (e *CronExpression) GetCurrentEpoch() int64 { + now := time.Now().UTC().Unix() + + return e.GetEpochFromReferenceUnixTimestamp(now) +} + +func (e *CronExpression) GetEpochBetweenUnixTimestamps(from int64, to int64) int64 { + epoch := e.lastEpochCached.epoch + since := e.lastEpochCached.timestamp + + if from >= e.lastEpochCached.timestamp.Unix() { + // TODO: I need to review the error condition handling, not serious because is a private function + ec, _ := calculateEpochBetween(e.expr, since, time.Unix(to, 0)) + epoch = epoch + ec.epoch + since = ec.timestamp + e.lastEpochCached = epochCache{epoch: epoch, timestamp: since} + } else { + initUnix := time.Unix(0, 0).UTC() + // TODO: I need to review the error condition handling, not serious because is a private function + ec, _ := calculateEpochBetween(e.expr, initUnix, time.Unix(to, 0)) + epoch = ec.epoch + } + + return epoch +} + // GetEpochFromReferenceUnixTimestamp calculates the epoch based on a cron expression and a ref unix timestamp func (e *CronExpression) GetEpochFromReferenceUnixTimestamp(ref int64) int64 { - return int64(math.Floor(float64((ref-e.first.Unix())/int64(e.interval)))) + 1 + epoch := e.lastEpochCached.epoch + since := e.lastEpochCached.timestamp + + if ref >= e.lastEpochCached.timestamp.Unix() { + // TODO: I need to review the error condition handling, not serious because is a private function + ec, _ := calculateCurrentEpochSince(e.expr, since) + epoch = epoch + ec.epoch + since = ec.timestamp + e.lastEpochCached = epochCache{epoch: epoch, timestamp: since} + } else { + initUnix := time.Unix(0, 0).UTC() + // TODO: I need to review the error condition handling, not serious because is a private function + ec, _ := calculateCurrentEpochSince(e.expr, initUnix) + epoch = ec.epoch + } + + return epoch } // GetNexFromNowUTC returns the next time after the current UTC timestamp diff --git a/internal/core/domain/cron_test.go b/internal/core/domain/cron_test.go index d9f20c6..0630add 100644 --- a/internal/core/domain/cron_test.go +++ b/internal/core/domain/cron_test.go @@ -1,8 +1,6 @@ package domain import ( - "fmt" - "math" "testing" "time" @@ -12,7 +10,10 @@ import ( ) // fix the reference timestamp -const refGlobal int64 = 1719848640 +const ( + refGlobal int64 = 1719848640 + toGlobal int64 = 1735948809 +) func TestParseCustom(t *testing.T) { e := "00 6 * * 1" @@ -21,8 +22,10 @@ func TestParseCustom(t *testing.T) { Type: Custom, CronExpression: e, }) + + assert.NoError(t, err) assert.NoError(t, err) - assert.Equal(t, int64(2844), ce.GetEpochFromReferenceUnixTimestamp(ref)) + assert.Equal(t, int64(2872), ce.GetEpochBetweenUnixTimestamps(ref, toGlobal)) } func TestParseHourly(t *testing.T) { @@ -32,7 +35,7 @@ func TestParseHourly(t *testing.T) { }) assert.NoError(t, err) - assert.Equal(t, int64(477735), ce.GetEpochFromReferenceUnixTimestamp(ref)) + assert.Equal(t, int64(482210), ce.GetEpochBetweenUnixTimestamps(ref, toGlobal)) } func TestParseDaily(t *testing.T) { @@ -42,8 +45,9 @@ func TestParseDaily(t *testing.T) { }) assert.NoError(t, err) - assert.Equal(t, int64(19905), ce.GetEpochFromReferenceUnixTimestamp(ref)) + assert.Equal(t, int64(20094), ce.GetEpochBetweenUnixTimestamps(ref, toGlobal)) } + func TestParseWeekly(t *testing.T) { ref := refGlobal ce, err := NewCronExpression(ResetExpression{ @@ -51,7 +55,7 @@ func TestParseWeekly(t *testing.T) { }) assert.NoError(t, err) - assert.Equal(t, int64(2844), ce.GetEpochFromReferenceUnixTimestamp(ref)) + assert.Equal(t, int64(2872), ce.GetEpochBetweenUnixTimestamps(ref, toGlobal)) } func TestGetNexFromRefUTC(t *testing.T) { @@ -63,6 +67,7 @@ func TestGetNexFromRefUTC(t *testing.T) { assert.Equal(t, time.Time(time.Date(2024, time.July, 1, 16, 0, 0, 0, time.UTC)), ce.GetNexFromRefUTC(time.Unix(ref, 0))) } + func TestGetNexTimestampFromRefUTC(t *testing.T) { ref := refGlobal ce, err := NewCronExpression(ResetExpression{ @@ -72,31 +77,31 @@ func TestGetNexTimestampFromRefUTC(t *testing.T) { assert.Equal(t, int64(1719849600), ce.GetNexTimestampFromRefUTC(time.Unix(ref, 0))) } -func TestUnixTimestamp(t *testing.T) { - e := "00 6 * * 1" // every Monday at 6am - e = "* * * * *" // every minute - e = "0 * * * *" // hourly - e = "0 0 1 * *" // every 1st of month - - now := time.Now().UTC().Unix() - - initUnix := time.Unix(0, 0).UTC() - first := cronexpr.MustParse(e).Next(initUnix) - second := cronexpr.MustParse(e).Next(first) - intervalSecs := second.Sub(first).Seconds() - epoch := int64(math.Floor(float64((now-first.Unix())/int64(intervalSecs)))) + 1 - - fmt.Println(now) - fmt.Println("init:\t\t", initUnix) - fmt.Println("first:\t\t", first) - fmt.Println("second:\t\t", second) - fmt.Println("interval:\t", intervalSecs) - fmt.Println("epoch:\t\t", epoch) - - assert.True(t, true) + +func TestEpochByRef(t *testing.T) { + ref := refGlobal + ce, err := NewCronExpression(ResetExpression{ + Type: Hourly, + }) + assert.NoError(t, err) + + epoch := ce.GetEpochBetweenUnixTimestamps(ref, toGlobal) + assert.Equal(t, int64(482210), epoch) +} + +func TestEpochByRefBeforeCached(t *testing.T) { + ref := refGlobal + before := time.Unix(ref, 0).Add(-60 * time.Minute).UTC().Unix() + ce, err := NewCronExpression(ResetExpression{ + Type: Hourly, + }) + assert.NoError(t, err) + + epoch := ce.GetEpochBetweenUnixTimestamps(before, toGlobal) + assert.Equal(t, int64(482210), epoch) } -func BenchmarkPrimeNumbers(b *testing.B) { +func BenchmarkPrimeNumbers(b *testing.B) { for i := 0; i < b.N; i++ { e := "0 0 1 * *" // every 1st of month @@ -106,7 +111,7 @@ func BenchmarkPrimeNumbers(b *testing.B) { first := cronexpr.MustParse(e).Next(initUnix) second := cronexpr.MustParse(e).Next(first) intervalSecs := second.Sub(first).Seconds() - _ = int64(math.Floor(float64((now-first.Unix())/int64(intervalSecs)))) + 1 + _ = int64(float64((now-first.Unix())/int64(intervalSecs))) + 1 } } diff --git a/internal/core/domain/leaderboards.go b/internal/core/domain/leaderboards.go index ee386a8..1724fa8 100644 --- a/internal/core/domain/leaderboards.go +++ b/internal/core/domain/leaderboards.go @@ -61,14 +61,14 @@ type LeaderboardScoreBoardConfig struct { type ResetExpression struct { Type LeaderboardResetType `json:"reset_type"` - CronExpression string `json:"cron, omitempty"` + CronExpression string `json:"cron,omitempty"` } // LeaderboardConfig holds information of a Leaderboard instance type LeaderboardConfig struct { Name string `json:"name"` Function LeaderboardFunctionType `json:"function"` - ResetExpression ResetExpression `json:"reset` + ResetExpression ResetExpression `json:"reset"` PrizeTable LeaderboardPrizeTable `json:"prizes_table"` Scoreboards []LeaderboardScoreBoardConfig `json:"scoreboards"` CronExpression CronExpression `json:"-"` diff --git a/internal/core/services/leaderboards.go b/internal/core/services/leaderboards.go index c875ab5..747acf5 100644 --- a/internal/core/services/leaderboards.go +++ b/internal/core/services/leaderboards.go @@ -49,7 +49,6 @@ func (s *LeaderboardsService) ReportScore(entryID string, name string, score flo // ReportScoreWithMetadata ... func (s *LeaderboardsService) ReportScoreWithMetadata(entryID string, name string, score float64, meta domain.Metadata) (domain.ReportScoreOutput, error) { - // ReportScore register a new score to a given entry on a leaderboard config, err := s.GetConfig(name) if err != nil { @@ -57,7 +56,6 @@ func (s *LeaderboardsService) ReportScoreWithMetadata(entryID string, name strin } leaderboard, epoch, err := GetLeaderboardNameWithEpoch(name, config.CronExpression) - if err != nil { return domain.ReportScoreOutput{}, fmt.Errorf("failed to generate name from configs: %v", err) } @@ -75,7 +73,7 @@ func (s *LeaderboardsService) ReportScoreWithMetadata(entryID string, name strin return domain.ReportScoreOutput{}, fmt.Errorf("failed to add score to scoreboard: %v", err) } // add to other scoreboards - if config.Scoreboards != nil && len(config.Scoreboards) > 0 { + if len(config.Scoreboards) > 0 { for _, sb := range config.Scoreboards { // TODO: we may enforce to exist the config fields in the meta for correctness lb := s.sbNameFromType(name, epoch, sb, meta[sb.Field]) @@ -121,7 +119,6 @@ func (s *LeaderboardsService) applyFunction(entryID string, leaderboard string, } } return lbFn - } // ListScoresWithMetadata returns a list of scores from leaderboards with metadata @@ -152,7 +149,7 @@ func (s *LeaderboardsService) ListScoresWithMetadata(name string, meta domain.Me } allLeaderboardScores = append(allLeaderboardScores, resultScores) - if config.Scoreboards != nil && len(config.Scoreboards) > 0 { + if len(config.Scoreboards) > 0 { for _, sb := range config.Scoreboards { lb := s.sbNameFromType(name, epoch, sb, meta[sb.Field]) scores, err := s.scoreboard.Get(lb) @@ -172,7 +169,6 @@ func (s *LeaderboardsService) ListScoresWithMetadata(name string, meta domain.Me } } return allLeaderboardScores, epoch, nil - } // ListScores returns a list of scores from leaderboards @@ -211,7 +207,7 @@ func (s *LeaderboardsService) GetResultsWithMetadata(name string, epoch int64, m } allResults = append(allResults, resultScores) - if config.Scoreboards != nil && len(config.Scoreboards) > 0 { + if len(config.Scoreboards) > 0 { for _, sb := range config.Scoreboards { leaderboard = s.sbNameFromType(name, epoch, sb, meta[sb.Field]) scores, err := s.scoreboard.Get(leaderboard) @@ -231,7 +227,6 @@ func (s *LeaderboardsService) GetResultsWithMetadata(name string, epoch int64, m allResults = append(allResults, resultScores) } - } return allResults, nil diff --git a/internal/core/services/leaderboards_test.go b/internal/core/services/leaderboards_test.go index 1b70476..b390d21 100644 --- a/internal/core/services/leaderboards_test.go +++ b/internal/core/services/leaderboards_test.go @@ -1,7 +1,6 @@ package services import ( - "fmt" "strings" "testing" @@ -143,11 +142,11 @@ func TestGetResults(t *testing.T) { lbSrv := NewLeaderboardsService(repo, scoreboard, configProvider) v, err := lbSrv.GetResults(lbName, epoch) - fmt.Println(v) assert.NoError(t, err) assert.Len(t, v, 1) assert.True(t, strings.Contains(v[0].Name, lbName)) } + func TestGetResultsWithMetadata(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -170,7 +169,6 @@ func TestGetResultsWithMetadata(t *testing.T) { "country": "PT", "league": "gold", }) - fmt.Println(v) assert.NoError(t, err) assert.Len(t, v, 1) assert.True(t, strings.Contains(v[0].Name, lbName)) diff --git a/k8s/simpleboards/.helmignore b/k8s/simpleboards/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/k8s/simpleboards/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/k8s/simpleboards/Chart.yaml b/k8s/simpleboards/Chart.yaml new file mode 100644 index 0000000..f56ead8 --- /dev/null +++ b/k8s/simpleboards/Chart.yaml @@ -0,0 +1,25 @@ +apiVersion: v2 +name: simpleboards +description: Simpleboards helm chart + + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/k8s/simpleboards/templates/NOTES.txt b/k8s/simpleboards/templates/NOTES.txt new file mode 100644 index 0000000..9714818 --- /dev/null +++ b/k8s/simpleboards/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "simpleboards.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "simpleboards.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "simpleboards.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "simpleboards.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/k8s/simpleboards/templates/_helpers.tpl b/k8s/simpleboards/templates/_helpers.tpl new file mode 100644 index 0000000..434a153 --- /dev/null +++ b/k8s/simpleboards/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "simpleboards.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "simpleboards.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "simpleboards.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "simpleboards.labels" -}} +helm.sh/chart: {{ include "simpleboards.chart" . }} +{{ include "simpleboards.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "simpleboards.selectorLabels" -}} +app.kubernetes.io/name: {{ include "simpleboards.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "simpleboards.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "simpleboards.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/k8s/simpleboards/templates/deployment.yaml b/k8s/simpleboards/templates/deployment.yaml new file mode 100644 index 0000000..32e3204 --- /dev/null +++ b/k8s/simpleboards/templates/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "simpleboards.fullname" . }} + labels: + {{- include "simpleboards.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "simpleboards.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "simpleboards.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "simpleboards.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/k8s/simpleboards/templates/hpa.yaml b/k8s/simpleboards/templates/hpa.yaml new file mode 100644 index 0000000..74d3cd8 --- /dev/null +++ b/k8s/simpleboards/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "simpleboards.fullname" . }} + labels: + {{- include "simpleboards.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "simpleboards.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/k8s/simpleboards/templates/ingress.yaml b/k8s/simpleboards/templates/ingress.yaml new file mode 100644 index 0000000..621d37b --- /dev/null +++ b/k8s/simpleboards/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "simpleboards.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "simpleboards.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/k8s/simpleboards/templates/service.yaml b/k8s/simpleboards/templates/service.yaml new file mode 100644 index 0000000..1bb07a5 --- /dev/null +++ b/k8s/simpleboards/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "simpleboards.fullname" . }} + labels: + {{- include "simpleboards.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "simpleboards.selectorLabels" . | nindent 4 }} diff --git a/k8s/simpleboards/templates/serviceaccount.yaml b/k8s/simpleboards/templates/serviceaccount.yaml new file mode 100644 index 0000000..e5a2250 --- /dev/null +++ b/k8s/simpleboards/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "simpleboards.serviceAccountName" . }} + labels: + {{- include "simpleboards.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/k8s/simpleboards/templates/tests/test-connection.yaml b/k8s/simpleboards/templates/tests/test-connection.yaml new file mode 100644 index 0000000..45301f0 --- /dev/null +++ b/k8s/simpleboards/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "simpleboards.fullname" . }}-test-connection" + labels: + {{- include "simpleboards.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "simpleboards.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/k8s/simpleboards/values.yaml b/k8s/simpleboards/values.yaml new file mode 100644 index 0000000..e670f20 --- /dev/null +++ b/k8s/simpleboards/values.yaml @@ -0,0 +1,82 @@ +# Default values for simpleboards. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: simpleboards + pullPolicy: Never + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {}