WIP: can deploy?

This commit is contained in:
2026-07-29 00:30:37 -04:00
parent 8f5c499ee2
commit 4a6732798a
31 changed files with 803 additions and 739 deletions
+30
View File
@@ -0,0 +1,30 @@
# 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/
# NOTES.txt
# deployment.yml
# secrets.yml
# service.yml
# persistent_volume.yml
+24
View File
@@ -0,0 +1,24 @@
apiVersion: v2
name: helm
description: By Other Means Control Timer 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.0.1
# 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: "0.0.1"
+64
View File
@@ -0,0 +1,64 @@
## Installation
```
helm upgrade --install by-other-means ./helm -n by-other-means --create-namespace
```
## Secrets
Gitea Auth secret for pulling the container from the private registry (namespace-scoped, so this
needs to be created once per namespace):
```
kubectl -n by-other-means \
create secret docker-registry gitea-auth \
--docker-server=https://gitea.pixelparasol.com \
--docker-username=nathan \
--docker-password=<redacted> \
--docker-email=nathan@pixelparasol.com
```
## Build the image
```
npm run build
DOCKER_REGISTRY="gitea.pixelparasol.com/nathan/by-other-means" && CI_COMMIT_SHORT_SHA=$(git rev-parse --short HEAD)
docker buildx build --no-cache -f Dockerfile . --platform linux/amd64 -t $DOCKER_REGISTRY:latest -t $DOCKER_REGISTRY:$CI_COMMIT_SHORT_SHA
docker login gitea.pixelparasol.com
docker push $DOCKER_REGISTRY:$CI_COMMIT_SHORT_SHA; docker push $DOCKER_REGISTRY:latest
```
### Troubleshooting
You may find yourself in a state where you need to do secret checks to see how they render.
adding the `--dry-run=server` argument to your upgrade will render the secret checks.
```
helm upgrade --install by-other-means ./helm -n by-other-means --dry-run=server
```
## Helm Diff
Install the `helm-diff` plugin
```
helm plugin install https://github.com/databus23/helm-diff
```
view the diff if an upgrade were issued
```
helm diff upgrade by-other-means ./helm -n by-other-means
```
## Deployment
The app can be deployed by tag by specifying it with `--set`
```
helm upgrade by-other-means ./helm -n by-other-means --set deploy.app.tag="v1.0.4"
```
+23
View File
@@ -0,0 +1,23 @@
1. {{ .Release.Name }} Deployment Information:
- Release Name: {{ .Release.Name }}
- Namespace: {{ .Release.Namespace }}
- Chart Name: {{ .Chart.Name }}
- Chart Version: {{ .Chart.Version }}
2. {{ .Release.Name }} Service Information:
- Service Name: {{ .Values.system }}-service
- Service Type: LoadBalancer
- Service Port: 80
3. Useful Commands:
- Check the {{ .Release.Name }} Deployment Status:
helm status {{ .Release.Name }}
- Get Detailed Information about the {{ .Release.Name }} Deployment:
helm get all {{ .Release.Name }}
helm diff
4. Clean Up:
- To uninstall/delete the {{ .Release.Name }} deployment, run:
helm uninstall {{ .Release.Name }}
+60
View File
@@ -0,0 +1,60 @@
{{- define "system.deployment.component" }}
{{ $image := .component.image }}
{{ $component := .component }}
{{ $release := .release }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .component.name }}
labels:
{{ include "helper.metaLabels" . | indent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{ include "helper.matchLabels" . | indent 8 }}
template:
metadata:
labels:
{{ include "helper.metaLabels" . | indent 8 }}
spec:
{{- include "helper.affinity" . | indent 6 }}
{{- if $image.imagePullSecrets }}
imagePullSecrets:
- name: {{ toString $image.imagePullSecrets }}
{{- end }}
{{- if .component.volumes }}
{{- range $key, $volume := .component.volumes }}
volumes:
{{- if eq $volume.type "configMap" }}
- name: "{{ $volume.name }}"
configMap:
name: "{{ $volume.name}}"
{{- else }}
- name: "{{ $release.Name }}-{{ $component.name }}-pvc"
persistentVolumeClaim:
claimName: "{{ $release.Name }}-{{ $component.name }}-pvc"
{{- end }}
{{- end }}
{{- end }}
containers:
- name: {{ .component.name }}
{{ include "helper.deploy_tag" (dict "component" .component "values" .values "release" .release) | indent 10 }}
{{- if .component.deployment }}
{{- toYaml .component.deployment | trim | nindent 10 }}
{{- end }}
{{- if .component.volumes }}
{{- range $key, $volume := .component.volumes }}
volumeMounts:
{{- if eq $volume.type "configMap" }}
- name: {{ $volume.name }}
mountPath: {{ $volume.mountPath }}
subPath: {{ $volume.subPath }}
{{- else }}
- mountPath: {{ $volume.containerMountPath }}
name: "{{ $release.Name }}-{{ $component.name }}-pvc"
{{- end }}
{{- end }}
{{- end }}
{{- end }}
+76
View File
@@ -0,0 +1,76 @@
{{/*
match labels
*/}}
{{- define "helper.matchLabels" -}}
app: {{ .component.name }}
{{- end }}
{{/*
meta labels
*/}}
{{- define "helper.metaLabels" -}}
app: {{ .component.name }}
tenant: {{ .values.system }}
{{- end }}
{{/*
Node Affinity helper
*/}}
{{- define "helper.affinity" -}}
{{- if .component.affinity -}}
nodeName: {{ .component.affinity }}
{{- end -}}
{{- end -}}
{{/*
Image name and tag helper
*/}}
{{- define "helper.image" -}}
image: "{{ .component.image.repository }}:{{ .component.image.tag }}"
{{- end -}}
{{- /* # returns one of the following
# - image: current_release:current_tag from lookup
# - image: current_release:deploy_tag from image lookup
# if this iteration of the loop has a .Values.deploy override tag
# - set $new_image to the override tag
# else
# - set $new_image to the current tag
*/ -}}
{{- define "helper.deploy_tag" -}}
{{- $new_deployment := "" -}}
{{- $component := .component.name -}}
{{- $deployment := (lookup "apps/v1" "Deployment" .release.Namespace $component) | default dict }}
{{- if $deployment -}}
{{- $targetContainerName := $component -}}
{{- $targetContainer := dict -}}
{{- range $deployment.spec.template.spec.containers }}
{{- if eq .name $targetContainerName }}
{{- $targetContainer = . -}}
{{- end -}}
{{- end -}}
{{- range $key, $deploy := .values.deploy -}}
{{- if eq $key $component -}}
{{- $new_deployment = $deploy.tag -}}
{{- end -}}
{{- end -}}
{{- if $new_deployment -}}
image: "{{ .component.image.repository }}:{{ $new_deployment }}"
{{- else -}}
image: {{ $targetContainer.image }}
{{- end -}}
{{- else -}}
image: "{{ .component.image.repository }}:{{ .component.image.tag }}"
{{- end -}}
{{- end -}}
{{- /* include "helper.var_dump" . */ -}}
{{- define "helper.var_dump" -}}
{{- . | mustToPrettyJson | printf "\n%s" | fail }}
{{- end -}}
+35
View File
@@ -0,0 +1,35 @@
{{- define "system.persistent_volume.component" }}
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: "{{ .release.Name }}-{{ .component.name }}-pv"
labels:
{{ include "helper.metaLabels" . | indent 4 }}
spec:
storageClassName: {{ .volume.storageClassName }}
persistentVolumeReclaimPolicy: {{ .volume.reclaimPolicy }}
capacity:
storage: {{ .volume.capacity.storage }}
accessModes:
{{ toYaml .volume.accessModes }}
{{- if .volume.hostPath }}
hostPath:
path: {{ .volume.hostPath }}
{{- end }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: "{{ .release.Name }}-{{ .component.name }}-pvc"
labels:
{{ include "helper.metaLabels" . | indent 4 }}
spec:
storageClassName: {{ .volume.storageClassName }}
volumeName: "{{ .release.Name }}-{{ .component.name }}-pv"
accessModes:
{{ toYaml .volume.accessModes }}
resources:
requests:
storage: {{ .volume.capacity.storage }}
{{- end }}
+21
View File
@@ -0,0 +1,21 @@
{{- define "system.service.component" }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .component.name }}-service
labels:
{{ include "helper.metaLabels" . | indent 4 }}
spec:
type: {{ .component.service.type }}
selector:
app: {{ .component.name }}
ports:
{{- range .component.service.ports }}
- protocol: {{ .protocol }}
port: {{ .port }}
targetPort: {{ .targetPort }}
name: {{ .name }}
{{- end }}
{{- end }}
+57
View File
@@ -0,0 +1,57 @@
{{- define "system.backup.cronjob" -}}
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ .system_name }}-{{ .backup.pvc_name }}-backup
spec:
schedule: {{ required "Values.backups.volumes[n].schedule required in crontab format" .backup.schedule }}
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
volumes:
- name: nfs-backup
nfs:
server: {{ required "Values.backups.volumes[n].nas_ip required to make nfs connection" .nas_ip }}
path: {{ required "Values.backups.volumes[n].nas_path required to store backup to a nfs connection" .backup.nas_path }}
- name: {{ required "Values.backups.volumes[n].pvc_name required" .backup.pvc_name }}
persistentVolumeClaim:
claimName: {{ .backup.pvc_name }}
containers:
- name: config-backup
image: {{ .image | default "alpine" }}
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- date;
echo "Creating backup for {{ .system_name }}";
echo -e "\nCreating {{ .system_name }} directory if it doesnt already exist";
mkdir -p /nas/backup/{{ .system_name }};
echo -e "\nProcessing {{ .backup.pvc_name }}";
echo " - Creating backup of {{ .backup.pvc_name }} volume";
tar -czf /nas/backup/{{ .system_name }}/{{ .backup.pvc_name }}_$(date +%Y-%m-%d).tar.gz /backup_source;
echo " - Enforcing backup retention policy for {{ .backup.pvc_name }} to latest {{ required "Values.backups.volumes[n].retention required as an integer" .backup.retention }}";
ls -t /nas/backup/{{ required "Values.backups.system_name required" .system_name }}/{{ .backup.pvc_name }}_*.tar.gz | sort -r | tail -n +{{ add1 .backup.retention }} | xargs rm -f;
echo -e "\nCompleted backup of {{ .backup.pvc_name }}";
date;
volumeMounts:
- name: nfs-backup
mountPath: /nas/backup
- name: {{ .backup.pvc_name }}
mountPath: /backup_source
{{- end }}
{{ if .Values.backups.enabled }}
{{- range $parent, $backup := .Values.backups.volumes -}}
{{- if $backup.enabled }}
{{ include "system.backup.cronjob" (dict "backup" $backup "image" $.Values.backups.image "nas_ip" $.Values.backups.nas_ip "system_name" $.Values.backups.system_name) }}
{{- end }}
{{- end }}
{{- end }}
+3
View File
@@ -0,0 +1,3 @@
{{- range $system, $settings := .Values.components -}}
{{ include "system.deployment.component" (dict "component" $settings "values" $.Values "release" $.Release) }}
{{- end }}
+29
View File
@@ -0,0 +1,29 @@
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Values.system }}-ingress
labels:
app: {{ .Values.system }}
tenant: {{ .Values.system }}
annotations:
cert-manager.io/cluster-issuer: lets-encrypt-prod
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: {{ .Values.components.app.ingress.className }}
tls:
- hosts:
- "{{ .Values.components.app.ingress.domain }}"
secretName: {{ .Values.system }}-ingress-tls
rules:
- host: "{{ .Values.components.app.ingress.domain }}"
http:
paths:
- path: {{ .Values.components.app.ingress.path }}
pathType: Prefix
backend:
service:
name: {{ .Values.system }}-service
port:
number: {{ .Values.components.app.ingress.port }}
+28
View File
@@ -0,0 +1,28 @@
---
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-configmap
labels:
app: {{ .Values.system }}
tenant: {{ .Values.system }}
data:
default.conf: |
server {
listen 80;
listen [::]:80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html =404;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
+9
View File
@@ -0,0 +1,9 @@
{{- range $system, $settings := .Values.components -}}
{{ if $settings.volumes }}
{{- range $component, $volume := $settings.volumes -}}
{{- if ne $volume.type "configMap" -}}
{{ include "system.persistent_volume.component" (dict "component" $settings "volume" $volume "values" $.Values "release" $.Release) }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
+33
View File
@@ -0,0 +1,33 @@
{{/*
Secret initializer and data maintainer
This helper will initialize the secret data with "initial" value if the secret is not found in the secret object.
IF the secret is found in the secret object, it will use the set value from the secret object thereby maintaining the secret data.
*/}}
{{- define "helper.secrets" -}}
{{- $secretObj := (lookup "v1" "Secret" .release.Namespace .component.name) | default dict }}
{{- range $key, $value := .component.secrets }}
{{- if empty $value -}}
{{- $secretData := (get $secretObj "data") | default dict }}
{{- $secret := (get $secretData $key) | default ("initial" | b64enc) }}
{{ $key }}: {{ $secret | quote }}
{{- else }}
{{ $key }}: {{ $value | b64enc | quote }}
{{- end -}}
{{- end }}
{{- end -}}
{{- range $system, $settings := .Values.components -}}
{{ if $settings.secrets }}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ $settings.name }}
labels:
{{ include "helper.metaLabels" (dict "component" $settings "values" $.Values) | indent 4 }}
type: Opaque
data:
{{- include "helper.secrets" (dict "component" $settings "release" $.Release) }}
{{- end }}
{{- end }}
+5
View File
@@ -0,0 +1,5 @@
{{- range $system, $settings := .Values.components -}}
{{ if $settings.service }}
{{ include "system.service.component" (dict "component" $settings "values" $.Values) }}
{{- end }}
{{- end }}
+43
View File
@@ -0,0 +1,43 @@
system: by-other-means
components:
app:
name: by-other-means
affinity: null
image:
repository: gitea.pixelparasol.com/nathan/by-other-means
tag: "latest"
imagePullPolicy: "Always"
imagePullSecrets: "gitea-auth"
ingress:
tls: true
domain: bom.nathanbstanley.com
path: /
className: "nginx"
port: 80
service:
type: ClusterIP
ports:
- protocol: TCP
port: 80
targetPort: 80
name: http
deployment:
resources:
limits:
memory: "128Mi"
cpu: "500m"
volumes:
- name: nginx-configmap
type: configMap
mountPath: "/etc/nginx/conf.d/default.conf"
subPath: "default.conf"
backups:
enabled: false