From 400e06a37c6566197ddb8b5cb890598c1b8f7f7e Mon Sep 17 00:00:00 2001 From: 030 Date: Fri, 23 Dec 2022 18:49:56 +0100 Subject: [PATCH] feat: [#20] Add Helm package. --- .github/workflows/yamllint.yml | 4 +- configs/.yamllint.yaml | 1 + deployments/helm/.helmignore | 23 ++++++ deployments/helm/Chart.yaml | 24 ++++++ deployments/helm/templates/NOTES.txt | 22 ++++++ deployments/helm/templates/_helpers.tpl | 62 +++++++++++++++ deployments/helm/templates/hpa.yaml | 24 ++++++ deployments/helm/templates/ingress.yaml | 24 ++++++ .../helm/templates/persistentvolume.yaml | 13 ++++ .../helm/templates/persistentvolumeclaim.yaml | 13 ++++ deployments/helm/templates/secret.yaml | 19 +++++ deployments/helm/templates/service.yaml | 15 ++++ deployments/helm/templates/statefulset.yaml | 71 ++++++++++++++++++ deployments/helm/values.yaml | 59 +++++++++++++++ docs/CHANGELOG.md | 10 ++- index.yaml | 14 ++++ yaam-0.1.0.tgz | Bin 0 -> 3254 bytes 17 files changed, 393 insertions(+), 5 deletions(-) create mode 100644 deployments/helm/.helmignore create mode 100644 deployments/helm/Chart.yaml create mode 100644 deployments/helm/templates/NOTES.txt create mode 100644 deployments/helm/templates/_helpers.tpl create mode 100644 deployments/helm/templates/hpa.yaml create mode 100644 deployments/helm/templates/ingress.yaml create mode 100644 deployments/helm/templates/persistentvolume.yaml create mode 100644 deployments/helm/templates/persistentvolumeclaim.yaml create mode 100644 deployments/helm/templates/secret.yaml create mode 100644 deployments/helm/templates/service.yaml create mode 100644 deployments/helm/templates/statefulset.yaml create mode 100644 deployments/helm/values.yaml create mode 100644 index.yaml create mode 100644 yaam-0.1.0.tgz diff --git a/.github/workflows/yamllint.yml b/.github/workflows/yamllint.yml index ee4e595..01aa924 100644 --- a/.github/workflows/yamllint.yml +++ b/.github/workflows/yamllint.yml @@ -6,11 +6,9 @@ jobs: runs-on: ubuntu-latest container: image: pipelinecomponents/yamllint:0.22.0 - env: - YAMLLINT_CONFIG_FILE: /code/configs/.yamllint.yaml options: --cpus 1 steps: - name: Checkout code uses: actions/checkout@v2 - name: run yamllint - run: yamllint . + run: yamllint -c configs/.yamllint.yaml . diff --git a/configs/.yamllint.yaml b/configs/.yamllint.yaml index 807204c..2c44d8f 100644 --- a/configs/.yamllint.yaml +++ b/configs/.yamllint.yaml @@ -2,4 +2,5 @@ extends: default ignore: | + deployments/helm/ test/npm/demo/node_modules/ diff --git a/deployments/helm/.helmignore b/deployments/helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/deployments/helm/.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/deployments/helm/Chart.yaml b/deployments/helm/Chart.yaml new file mode 100644 index 0000000..cd54144 --- /dev/null +++ b/deployments/helm/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: yaam +description: A Helm chart for Kubernetes + +# 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: "v0.5.3" diff --git a/deployments/helm/templates/NOTES.txt b/deployments/helm/templates/NOTES.txt new file mode 100644 index 0000000..128f772 --- /dev/null +++ b/deployments/helm/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 "yaam.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 "yaam.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "yaam.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 "yaam.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/deployments/helm/templates/_helpers.tpl b/deployments/helm/templates/_helpers.tpl new file mode 100644 index 0000000..68e4226 --- /dev/null +++ b/deployments/helm/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "yaam.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 "yaam.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 "yaam.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "yaam.labels" -}} +helm.sh/chart: {{ include "yaam.chart" . }} +{{ include "yaam.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "yaam.selectorLabels" -}} +app.kubernetes.io/name: {{ include "yaam.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "yaam.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "yaam.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/deployments/helm/templates/hpa.yaml b/deployments/helm/templates/hpa.yaml new file mode 100644 index 0000000..ea8813c --- /dev/null +++ b/deployments/helm/templates/hpa.yaml @@ -0,0 +1,24 @@ +{{- if .Values.autoscaling.enabled }} +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: yaam +spec: + behavior: + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.stabilizationWindowSeconds }} + metrics: + - resource: + name: cpu + target: + averageUtilization: {{ .Values.autoscaling.averageCpuUtilization }} + type: Utilization + type: Resource + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + scaleTargetRef: + apiVersion: apps/v1 + kind: StatefulSet + name: yaam +{{- end }} diff --git a/deployments/helm/templates/ingress.yaml b/deployments/helm/templates/ingress.yaml new file mode 100644 index 0000000..2ad5358 --- /dev/null +++ b/deployments/helm/templates/ingress.yaml @@ -0,0 +1,24 @@ +{{- if .Values.ingress.enabled -}} +{{- $svcPort := .Values.service.port -}} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: yaam +spec: + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - pathType: {{ .pathType }} + path: {{ .path }} + backend: + service: + name: yaam + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/deployments/helm/templates/persistentvolume.yaml b/deployments/helm/templates/persistentvolume.yaml new file mode 100644 index 0000000..bb03269 --- /dev/null +++ b/deployments/helm/templates/persistentvolume.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: repositories +spec: + storageClassName: standard + accessModes: + - ReadWriteOnce + capacity: + storage: 2Gi + hostPath: + path: /opt/yaam/repositories/ diff --git a/deployments/helm/templates/persistentvolumeclaim.yaml b/deployments/helm/templates/persistentvolumeclaim.yaml new file mode 100644 index 0000000..8f89ffb --- /dev/null +++ b/deployments/helm/templates/persistentvolumeclaim.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: repositories +spec: + volumeName: repositories + storageClassName: standard + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/deployments/helm/templates/secret.yaml b/deployments/helm/templates/secret.yaml new file mode 100644 index 0000000..5a2b602 --- /dev/null +++ b/deployments/helm/templates/secret.yaml @@ -0,0 +1,19 @@ +{{- if .Values.secret.enabled }} +--- +apiVersion: v1 +data: + pass: {{ .Values.secret.pass }} + user: {{ .Values.secret.user }} +kind: Secret +metadata: + name: creds +--- +apiVersion: v1 +kind: Secret +metadata: + name: conf +type: Opaque +stringData: + config.yaml: |- + {{ .Values.secret.conf }} +{{- end }} diff --git a/deployments/helm/templates/service.yaml b/deployments/helm/templates/service.yaml new file mode 100644 index 0000000..0dba23b --- /dev/null +++ b/deployments/helm/templates/service.yaml @@ -0,0 +1,15 @@ +{{- if .Values.service.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: yaam + labels: + app: yaam +spec: + ports: + - port: {{ .Values.service.port }} + name: yaam + selector: + app: yaam +{{- end }} \ No newline at end of file diff --git a/deployments/helm/templates/statefulset.yaml b/deployments/helm/templates/statefulset.yaml new file mode 100644 index 0000000..5c50a65 --- /dev/null +++ b/deployments/helm/templates/statefulset.yaml @@ -0,0 +1,71 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: yaam + labels: + app: yaam +spec: + serviceName: yaam + selector: + matchLabels: + app: yaam + replicas: 2 + template: + metadata: + labels: + app: yaam + spec: + containers: + - name: yaam + env: + - name: YAAM_LOG_LEVEL + value: info + - name: YAAM_HOST + value: yaam.some-domain + - name: YAAM_USER + valueFrom: + secretKeyRef: + name: creds + key: user + - name: YAAM_PASS + valueFrom: + secretKeyRef: + name: creds + key: pass + image: utrecht/yaam:v0.5.3 + livenessProbe: + httpGet: + path: /status + port: 25213 + readinessProbe: + httpGet: + path: /status + port: 25213 + resources: + limits: + cpu: 480m + memory: 30Mi + requests: + cpu: 96m + memory: 5Mi + ports: + - containerPort: 25213 + name: yaam + volumeMounts: + - name: conf + mountPath: /opt/yaam/.yaam + - name: repositories + mountPath: /opt/yaam/.yaam/repositories + - mountPath: /opt/yaam/.yaam/logs + name: logs + volumes: + - name: conf + secret: + secretName: conf + - name: repositories + persistentVolumeClaim: + claimName: repositories + - name: logs + emptyDir: + sizeLimit: 50Mi diff --git a/deployments/helm/values.yaml b/deployments/helm/values.yaml new file mode 100644 index 0000000..df2e40d --- /dev/null +++ b/deployments/helm/values.yaml @@ -0,0 +1,59 @@ +--- +autoscaling: + averageCpuUtilization: 90 + enabled: true + maxReplicas: 10 + minReplicas: 2 + stabilizationWindowSeconds: 30 + +ingress: + enabled: true + hosts: + - host: yaam.some-domain + paths: + - path: / + pathType: Prefix + +secret: + conf: |- + caches: + apt: + 3rdparty-ubuntu-nl-archive: + url: http://nl.archive.ubuntu.com/ubuntu/ + maven: + 3rdparty-maven: + url: https://repo.maven.apache.org/maven2/ + 3rdparty-maven-gradle-plugins: + url: https://plugins.gradle.org/m2/ + 3rdparty-maven-spring: + url: https://repo.spring.io/release/ + other-nexus-repo-releases: + url: https://some-nexus/repository/some-repo/ + user: some-user + pass: some-pass + npm: + 3rdparty-npm: + url: https://registry.npmjs.org/ + groups: + maven: + hello: + - maven/releases + - maven/3rdparty-maven + - maven/3rdparty-maven-gradle-plugins + - maven/3rdparty-maven-spring + - maven/other-nexus-repo-releases + publications: + generic: + - something + maven: + - releases + npm: + - 3rdparty-npm + enabled: true + pass: d29ybGQ= + user: aGVsbG8= + +service: + enabled: true + port: 25213 + type: ClusterIP diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 60ecae9..9eda42a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,11 +2,16 @@ ## [Unreleased] + +## [v0.6.0] - 2022-12-23 +### Feat +- [[#20](https://github.com/030/yaam/issues/20)] Helm package. + + ## [v0.5.3] - 2022-11-27 ### Build - **auto-updater:** Update schedule. -- **deps:** Update versions. @@ -71,7 +76,8 @@ ## v0.2.1 - 2022-08-23 -[Unreleased]: https://github.com/030/yaam/compare/v0.5.3...HEAD +[Unreleased]: https://github.com/030/yaam/compare/v0.6.0...HEAD +[v0.6.0]: https://github.com/030/yaam/compare/v0.5.3...v0.6.0 [v0.5.3]: https://github.com/030/yaam/compare/v0.5.2...v0.5.3 [v0.5.2]: https://github.com/030/yaam/compare/v0.5.1...v0.5.2 [v0.5.1]: https://github.com/030/yaam/compare/v0.5.0...v0.5.1 diff --git a/index.yaml b/index.yaml new file mode 100644 index 0000000..109339a --- /dev/null +++ b/index.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +entries: + yaam: + - apiVersion: v2 + appVersion: v0.5.3 + created: "2023-01-01T14:19:11.729152405+01:00" + description: A Helm chart for Kubernetes + digest: cfab2aa0232a84d92cd20021429f6a8292f714d8325adb59c9673ed1778303a5 + name: yaam + type: application + urls: + - https://030.github.io/yaam/yaam-0.1.0.tgz + version: 0.1.0 +generated: "2023-01-01T14:19:11.728385797+01:00" diff --git a/yaam-0.1.0.tgz b/yaam-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..7a93722f9a4f8f799a9b49c09f72bb32cd5a3b0a GIT binary patch literal 3254 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 literal 0 HcmV?d00001