diff --git a/.github/workflows/helm-charts-ci.yaml b/.github/workflows/helm-charts-ci.yaml new file mode 100644 index 0000000..34d69eb --- /dev/null +++ b/.github/workflows/helm-charts-ci.yaml @@ -0,0 +1,98 @@ +name: Lint and Test Charts + +on: + pull_request: + +jobs: + path-filter: + runs-on: ubuntu-latest + if: ${{ !github.event.pull_request.draft }} + outputs: + charts: ${{ steps.filter.outputs.charts }} + steps: + - name: Checks-out repository + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + - name: Check updated files paths + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + charts: + - 'helm-charts/**' + + lint-chart: + if: ${{ needs.path-filter.outputs.charts == 'true' }} + name: Lint chart + runs-on: ubuntu-latest + needs: + - path-filter + steps: + - name: Checks-out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + check-latest: true + + - name: Set up chart-testing + uses: helm/chart-testing-action@main + with: + version: v3.10.0 + yamale_version: "6.0.0" + + - name: Run chart-testing (lint) + run: ct lint --config ./ci/configs/chart-testing.yaml + + # - name: Create kind cluster + # uses: helm/kind-action@v1 + + # - name: Run chart-testing (install) + # run: ct install --config ./ci/configs/chart-testing.yaml + + lint-docs: + if: ${{ needs.path-filter.outputs.charts == 'true' }} + name: Lint docs + runs-on: ubuntu-latest + needs: + - path-filter + steps: + - name: Checks-out repository + uses: actions/checkout@v4 + + - name: Run helm-docs + uses: docker://jnorwood/helm-docs:v1.14.2 + with: + args: --chart-search-root=./helm-charts + + # Workaround for required status check in protection branches (see. https://github.com/orgs/community/discussions/13690) + all-jobs-passed: + name: Check jobs status + runs-on: ubuntu-latest + if: ${{ always() }} + needs: + - path-filter + - lint-chart + - lint-docs + steps: + - name: Check status of all required jobs + run: |- + NEEDS_CONTEXT='${{ toJson(needs) }}' + JOB_IDS=$(echo "$NEEDS_CONTEXT" | jq -r 'keys[]') + for JOB_ID in $JOB_IDS; do + RESULT=$(echo "$NEEDS_CONTEXT" | jq -r ".[\"$JOB_ID\"].result") + echo "$JOB_ID job result: $RESULT" + if [[ $RESULT != "success" && $RESULT != "skipped" ]]; then + echo "***" + echo "Error: The $JOB_ID job did not pass." + exit 1 + fi + done + echo "All jobs passed or were skipped." diff --git a/.github/workflows/helm-charts-release.yaml b/.github/workflows/helm-charts-release.yaml new file mode 100644 index 0000000..7b1e6bf --- /dev/null +++ b/.github/workflows/helm-charts-release.yaml @@ -0,0 +1,37 @@ +name: Release Charts + +on: + push: + branches: + - main + paths: + - 'helm-charts/**' + - '!helm-charts/**/README.md' + workflow_dispatch: + + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checks-out repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Install Helm + uses: azure/setup-helm@v4 + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1 + with: + charts_dir: helm-charts + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/ci/configs/chart-testing.yaml b/ci/configs/chart-testing.yaml new file mode 100644 index 0000000..c55160b --- /dev/null +++ b/ci/configs/chart-testing.yaml @@ -0,0 +1,11 @@ +remote: origin +target-branch: main +chart-dirs: +- helm-charts +helm-extra-args: '' +validate-chart-schema: true +validate-chart-values: true +validate-maintainers: true +validate-yaml: true +exclude-deprecated: true +debug: true diff --git a/helm-charts/html-db/Chart.yaml b/helm-charts/html-db/Chart.yaml new file mode 100644 index 0000000..02c39a8 --- /dev/null +++ b/helm-charts/html-db/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +name: html-db +description: This Helm chart deploy a basic html application using a relational database as backend. +type: application +version: 1.0.0 +maintainers: + - name: falltrades + url: https://falltrades.github.io/engineering diff --git a/helm-charts/html-db/README.md b/helm-charts/html-db/README.md new file mode 100644 index 0000000..4665926 --- /dev/null +++ b/helm-charts/html-db/README.md @@ -0,0 +1,33 @@ +# html-db + +![Version: 1.0.0](https://img.shields.io/badge/Version-1.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) + +This Helm chart deploy a basic html application using a relational database as backend. + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| falltrades | | | + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| db | object | `{"name":"myapp","password":"password","user":"admin"}` | Database connection and credential settings | +| db.name | string | `"myapp"` | Name of the database to create | +| db.password | string | `"password"` | Password for the database | +| db.user | string | `"admin"` | Username for the database administrator | +| image | object | `{"db":"postgres:15","web":"ghcr.io/falltrades/cloud-example/html-db:1.0.0"}` | Image configuration for the application components | +| image.db | string | `"postgres:15"` | The image for the postgres database | +| image.web | string | `"ghcr.io/falltrades/cloud-example/html-db:1.0.0"` | The image for the web application | +| ingress | object | `{"annotations":{"cert-manager.io/cluster-issuer":"letsencrypt-prod","kubernetes.io/ingress.class":"nginx"},"className":"nginx","enabled":true,"host":"html-db.example.com","tls":{"enabled":true,"secretName":"html-db-tls"}}` | Ingress configuration to expose the web service | +| ingress.annotations | object | `{"cert-manager.io/cluster-issuer":"letsencrypt-prod","kubernetes.io/ingress.class":"nginx"}` | Annotations for the Ingress (e.g., for cert-manager) | +| ingress.className | string | `"nginx"` | Ingress class name (usually 'nginx') | +| ingress.enabled | bool | `true` | Enable ingress record generation | +| ingress.host | string | `"html-db.example.com"` | Hostname for the application | +| ingress.tls | object | `{"enabled":true,"secretName":"html-db-tls"}` | TLS configuration | +| service | object | `{"dbPort":5432,"webPort":8080}` | Port configurations for internal services | +| service.dbPort | int | `5432` | The port the database container listens on | +| service.webPort | int | `8080` | The port the web container listens on | + diff --git a/helm-charts/html-db/templates/db/deployment.yaml b/helm-charts/html-db/templates/db/deployment.yaml new file mode 100644 index 0000000..bb00954 --- /dev/null +++ b/helm-charts/html-db/templates/db/deployment.yaml @@ -0,0 +1,26 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-db +spec: + selector: + matchLabels: + app: db + template: + metadata: + labels: + app: db + spec: + containers: + - name: db + image: {{ .Values.image.db }} + ports: + - containerPort: {{ .Values.service.dbPort }} + envFrom: + - secretRef: + name: {{ .Release.Name }}-secrets + livenessProbe: + exec: + command: ["pg_isready", "-U", "{{ .Values.db.user }}", "-d", "{{ .Values.db.name }}"] + initialDelaySeconds: 5 + periodSeconds: 5 diff --git a/helm-charts/html-db/templates/db/secrets.yaml b/helm-charts/html-db/templates/db/secrets.yaml new file mode 100644 index 0000000..005768e --- /dev/null +++ b/helm-charts/html-db/templates/db/secrets.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-secrets +type: Opaque +stringData: + POSTGRES_DB: {{ .Values.db.name }} + POSTGRES_USER: {{ .Values.db.user }} + POSTGRES_PASSWORD: {{ .Values.db.password }} + DATABASE_URL: "postgresql://{{ .Values.db.user }}:{{ .Values.db.password }}@{{ .Release.Name }}-db-service:5432/{{ .Values.db.name }}" diff --git a/helm-charts/html-db/templates/db/service.yaml b/helm-charts/html-db/templates/db/service.yaml new file mode 100644 index 0000000..f9a778c --- /dev/null +++ b/helm-charts/html-db/templates/db/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-db-service +spec: + selector: + app: db + ports: + - protocol: TCP + port: 5432 + targetPort: {{ .Values.service.dbPort }} diff --git a/helm-charts/html-db/templates/web/deployment.yaml b/helm-charts/html-db/templates/web/deployment.yaml new file mode 100644 index 0000000..6cdba71 --- /dev/null +++ b/helm-charts/html-db/templates/web/deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-web +spec: + replicas: 1 + selector: + matchLabels: + app: web + template: + metadata: + labels: + app: web + spec: + initContainers: + - name: wait-for-db + image: busybox:1.28 + command: + - 'sh' + - '-c' + - "until nc -z {{ .Release.Name }}-db-service 5432; do echo waiting for db; sleep 2; done;" + containers: + - name: web + image: {{ .Values.image.web }} + command: ["python", "app.py"] + ports: + - containerPort: {{ .Values.service.webPort }} + envFrom: + - secretRef: + name: {{ .Release.Name }}-secrets diff --git a/helm-charts/html-db/templates/web/ingress.yaml b/helm-charts/html-db/templates/web/ingress.yaml new file mode 100644 index 0000000..86160d5 --- /dev/null +++ b/helm-charts/html-db/templates/web/ingress.yaml @@ -0,0 +1,27 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-ingress + annotations: + {{- toYaml .Values.ingress.annotations | nindent 4 }} +spec: + ingressClassName: {{ .Values.ingress.className }} + {{- if .Values.ingress.tls.enabled }} + tls: + - hosts: + - {{ .Values.ingress.host | quote }} + secretName: {{ .Values.ingress.tls.secretName }} + {{- end }} + rules: + - host: {{ .Values.ingress.host | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-web-service + port: + number: 80 +{{- end }} diff --git a/helm-charts/html-db/templates/web/service.yaml b/helm-charts/html-db/templates/web/service.yaml new file mode 100644 index 0000000..895807b --- /dev/null +++ b/helm-charts/html-db/templates/web/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-web-service +spec: + type: ClusterIP + selector: + app: web + ports: + - protocol: TCP + port: 80 + targetPort: {{ .Values.service.webPort }} diff --git a/helm-charts/html-db/values.yaml b/helm-charts/html-db/values.yaml new file mode 100644 index 0000000..88bae6b --- /dev/null +++ b/helm-charts/html-db/values.yaml @@ -0,0 +1,39 @@ +# -- Image configuration for the application components +image: + # -- The image for the web application + web: ghcr.io/falltrades/cloud-example/html-db:1.0.0 + # -- The image for the postgres database + db: postgres:15 + +# -- Port configurations for internal services +service: + # -- The port the web container listens on + webPort: 8080 + # -- The port the database container listens on + dbPort: 5432 + +# -- Database connection and credential settings +db: + # -- Name of the database to create + name: "myapp" + # -- Username for the database administrator + user: "admin" + # -- Password for the database + password: "password" + +# -- Ingress configuration to expose the web service +ingress: + # -- Enable ingress record generation + enabled: true + # -- Ingress class name (usually 'nginx') + className: "nginx" + # -- Annotations for the Ingress (e.g., for cert-manager) + annotations: + kubernetes.io/ingress.class: nginx + cert-manager.io/cluster-issuer: "letsencrypt-prod" + # -- Hostname for the application + host: "html-db.example.com" + # -- TLS configuration + tls: + enabled: true + secretName: "html-db-tls"