From 1ec9320ce06eb80fa887df742f81b2c411d84e5f Mon Sep 17 00:00:00 2001 From: Abstractize Date: Wed, 30 Jul 2025 05:00:24 -0600 Subject: [PATCH 1/2] Add Helm Chart --- .github/workflows/build-and-release.yaml | 90 ++++++++++++++++++++++ .github/workflows/pr-validation.yaml | 51 ++++++++++++ .helm/.helmignore | 23 ++++++ .helm/Chart.yaml | 6 ++ .helm/templates/_helpers.tpl | 62 +++++++++++++++ .helm/templates/deployment.yaml | 30 ++++++++ .helm/templates/hpa.yaml | 20 +++++ .helm/templates/service.yaml | 15 ++++ .helm/templates/tests/test-connection.yaml | 16 ++++ .helm/values.yaml | 27 +++++++ Dockerfile | 42 +++++++++- 11 files changed, 380 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build-and-release.yaml create mode 100644 .github/workflows/pr-validation.yaml create mode 100644 .helm/.helmignore create mode 100644 .helm/Chart.yaml create mode 100644 .helm/templates/_helpers.tpl create mode 100644 .helm/templates/deployment.yaml create mode 100644 .helm/templates/hpa.yaml create mode 100644 .helm/templates/service.yaml create mode 100644 .helm/templates/tests/test-connection.yaml create mode 100644 .helm/values.yaml diff --git a/.github/workflows/build-and-release.yaml b/.github/workflows/build-and-release.yaml new file mode 100644 index 0000000..73db02a --- /dev/null +++ b/.github/workflows/build-and-release.yaml @@ -0,0 +1,90 @@ +name: Build and Release Gateway Chart + +on: + push: + branches: + - main + workflow_dispatch: + +env: + IMAGE_NAME: gateway + REGISTRY: ghcr.io + OCI_REPO: ghcr.io/abstractize + PROJECT_NAME: todo + +jobs: + docker-build: + runs-on: ubuntu-latest + permissions: + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set lowercase env vars + id: set_env + run: | + USERNAME=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]') + REPO_NAME=$(echo "${{ github.repository }}" | cut -d/ -f2 | tr '[:upper:]' '[:lower:]') + echo "USERNAME=$USERNAME" >> $GITHUB_ENV + echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV + echo "USERNAME=$USERNAME" >> $GITHUB_OUTPUT + echo "REPO_NAME=$REPO_NAME" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + file: ./Dockerfile + push: true + build-args: | + GITHUB_USERNAME=${{ github.repository_owner }} + secrets: | + GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + tags: | + ${{ env.OCI_REPO }}/docker-images/${{ env.PROJECT_NAME }}/${{ env.IMAGE_NAME }}:latest + ${{ env.OCI_REPO }}/docker-images/${{ env.PROJECT_NAME }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + + helm-release: + runs-on: ubuntu-latest + needs: docker-build + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v3.14.0 + + - name: Update Chart.yaml version and appVersion + run: | + sudo apt-get update && sudo apt-get install -y yq + VERSION="0.1.${GITHUB_RUN_NUMBER}" + yq -y -i ".appVersion = \"${GITHUB_SHA}\" | .version = \"$VERSION\"" .helm/Chart.yaml + + - name: Lint Helm chart + run: helm lint .helm + + - name: Package Helm chart + run: helm package .helm --destination .helm-dist + + - name: Push Helm chart to GHCR (OCI) + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io --username ${{ github.actor }} --password-stdin + CHART_FILE=$(ls .helm-dist/*.tgz) + helm push $CHART_FILE oci://${{ env.OCI_REPO }}/helm-charts/${{ env.PROJECT_NAME }} \ No newline at end of file diff --git a/.github/workflows/pr-validation.yaml b/.github/workflows/pr-validation.yaml new file mode 100644 index 0000000..1bf6c3f --- /dev/null +++ b/.github/workflows/pr-validation.yaml @@ -0,0 +1,51 @@ +name: PR Validation + +on: + pull_request: + branches: + - main + +jobs: + helm-lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v3.14.0 + + - name: Helm lint + run: helm lint .helm + + docker-build-test: + permissions: + contents: read + packages: write + + runs-on: ubuntu-latest + needs: helm-lint + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image with secret + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + push: false + load: true + build-args: | + GITHUB_USERNAME=${{ github.repository_owner }} + secrets: | + GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} + tags: test-image:pr-${{ github.event.pull_request.number }} + + - name: Run container test + run: docker run --rm --entrypoint sleep test-image:pr-${{ github.event.pull_request.number }} 5 \ No newline at end of file diff --git a/.helm/.helmignore b/.helm/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/.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/.helm/Chart.yaml b/.helm/Chart.yaml new file mode 100644 index 0000000..e8a703a --- /dev/null +++ b/.helm/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: auth-service +description: Helm chart for the Auth Service of the TODO app +type: application +version: 0.1.0 +appVersion: "latest" diff --git a/.helm/templates/_helpers.tpl b/.helm/templates/_helpers.tpl new file mode 100644 index 0000000..e2664fa --- /dev/null +++ b/.helm/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "auth-service.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 "auth-service.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 "auth-service.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "auth-service.labels" -}} +helm.sh/chart: {{ include "auth-service.chart" . }} +{{ include "auth-service.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "auth-service.selectorLabels" -}} +app.kubernetes.io/name: {{ include "auth-service.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "auth-service.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "auth-service.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/.helm/templates/deployment.yaml b/.helm/templates/deployment.yaml new file mode 100644 index 0000000..e347806 --- /dev/null +++ b/.helm/templates/deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "auth.fullname" . }} + labels: + app: {{ include "auth.name" . }} + chart: {{ include "auth.chart" . }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: {{ include "auth.name" . }} + template: + metadata: + labels: + app: {{ include "auth.name" . }} + spec: + containers: + - name: {{ include "auth.name" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.service.port }} + envFrom: + - configMapRef: + name: {{ .Values.configMapName }} + - secretRef: + name: {{ .Values.secretName }} + resources: + {{- toYaml .Values.resources | nindent 12 }} \ No newline at end of file diff --git a/.helm/templates/hpa.yaml b/.helm/templates/hpa.yaml new file mode 100644 index 0000000..cef6907 --- /dev/null +++ b/.helm/templates/hpa.yaml @@ -0,0 +1,20 @@ +{{- if .Values.hpa.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "auth.fullname" . }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "auth.fullname" . }} + minReplicas: {{ .Values.hpa.minReplicas }} + maxReplicas: {{ .Values.hpa.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.hpa.targetCPUUtilizationPercentage }} +{{- end }} \ No newline at end of file diff --git a/.helm/templates/service.yaml b/.helm/templates/service.yaml new file mode 100644 index 0000000..19a11d3 --- /dev/null +++ b/.helm/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "auth.fullname" . }} + labels: + app: {{ include "auth.name" . }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.port }} + protocol: TCP + name: http + selector: + app: {{ include "auth.name" . }} \ No newline at end of file diff --git a/.helm/templates/tests/test-connection.yaml b/.helm/templates/tests/test-connection.yaml new file mode 100644 index 0000000..bb2beda --- /dev/null +++ b/.helm/templates/tests/test-connection.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "auth.fullname" . }}-test-connection" + labels: + app: {{ include "auth.name" . }} + annotations: + "helm.sh/hook": test + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + containers: + - name: wget + image: busybox:1.36 + command: ['wget'] + args: ['-qO-', 'http://{{ include "auth.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never \ No newline at end of file diff --git a/.helm/values.yaml b/.helm/values.yaml new file mode 100644 index 0000000..f507f6e --- /dev/null +++ b/.helm/values.yaml @@ -0,0 +1,27 @@ +replicaCount: 1 + +image: + repository: ghcr.io/abstractize/docker-images/todo/auth + tag: latest + pullPolicy: IfNotPresent + +service: + type: ClusterIP + port: 8080 + +resources: {} + +hpa: + enabled: true + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +configMapName: infra-config +secretName: infra-secrets diff --git a/Dockerfile b/Dockerfile index 77f7bec..605e97c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,54 @@ -FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +# Stage 1: Base runtime image (minimal & secure) +FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS base WORKDIR /app + +ENV DOTNET_RUNNING_IN_CONTAINER=true \ + DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \ + ASPNETCORE_URLS=http://+:8080 + EXPOSE 8080 +# Labels for image metadata (useful for CI/CD and Helm) +ARG VERSION +LABEL org.opencontainers.image.source="https://github.com/Abstractize/todo.auth-service" +LABEL org.opencontainers.image.version=$VERSION + +# Stage 2: Build and publish FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build + +ARG GITHUB_USERNAME WORKDIR /src + COPY src . WORKDIR /src/API -RUN dotnet restore +RUN --mount=type=secret,id=GITHUB_TOKEN bash -c '\ + TOKEN=$(cat /run/secrets/GITHUB_TOKEN) && \ + echo "" > nuget.config && \ + echo "" >> nuget.config && \ + echo " " >> nuget.config && \ + echo " " >> nuget.config && \ + echo " " >> nuget.config && \ + echo " " >> nuget.config && \ + echo " " >> nuget.config && \ + echo " " >> nuget.config && \ + echo " " >> nuget.config && \ + echo " " >> nuget.config && \ + echo " " >> nuget.config && \ + echo " " >> nuget.config && \ + echo "" >> nuget.config \ + ' + +RUN dotnet restore --configfile /src/API/nuget.config RUN dotnet publish -c Release -o /app/publish +# Stage 3: Final runtime image FROM base AS final WORKDIR /app + +RUN adduser --disabled-password --gecos "" appuser && chown -R appuser /app +USER appuser + COPY --from=build /app/publish . + ENTRYPOINT ["dotnet", "API.dll"] \ No newline at end of file From 2a4296d67524937bc1d81c7b43270228c3f300c0 Mon Sep 17 00:00:00 2001 From: Abstractize Date: Wed, 30 Jul 2025 05:10:48 -0600 Subject: [PATCH 2/2] Update chart --- .github/workflows/build-and-release.yaml | 4 ++-- .helm/Chart.yaml | 1 + .helm/icons/auth.svg | 7 +++++++ .helm/templates/deployment.yaml | 12 ++++++------ .helm/templates/hpa.yaml | 4 ++-- .helm/templates/service.yaml | 6 +++--- .helm/templates/tests/test-connection.yaml | 6 +++--- 7 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 .helm/icons/auth.svg diff --git a/.github/workflows/build-and-release.yaml b/.github/workflows/build-and-release.yaml index 73db02a..d119a32 100644 --- a/.github/workflows/build-and-release.yaml +++ b/.github/workflows/build-and-release.yaml @@ -1,4 +1,4 @@ -name: Build and Release Gateway Chart +name: Build and Release Auth Chart on: push: @@ -7,7 +7,7 @@ on: workflow_dispatch: env: - IMAGE_NAME: gateway + IMAGE_NAME: auth REGISTRY: ghcr.io OCI_REPO: ghcr.io/abstractize PROJECT_NAME: todo diff --git a/.helm/Chart.yaml b/.helm/Chart.yaml index e8a703a..74b8dbd 100644 --- a/.helm/Chart.yaml +++ b/.helm/Chart.yaml @@ -4,3 +4,4 @@ description: Helm chart for the Auth Service of the TODO app type: application version: 0.1.0 appVersion: "latest" +icon: https://raw.githubusercontent.com/Abstractize/todo.auth/main/.helm/icons/auth.svg diff --git a/.helm/icons/auth.svg b/.helm/icons/auth.svg new file mode 100644 index 0000000..7fecf33 --- /dev/null +++ b/.helm/icons/auth.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.helm/templates/deployment.yaml b/.helm/templates/deployment.yaml index e347806..d330de4 100644 --- a/.helm/templates/deployment.yaml +++ b/.helm/templates/deployment.yaml @@ -1,22 +1,22 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "auth.fullname" . }} + name: {{ include "auth-service.fullname" . }} labels: - app: {{ include "auth.name" . }} - chart: {{ include "auth.chart" . }} + app: {{ include "auth-service.name" . }} + chart: {{ include "auth-service.chart" . }} spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: - app: {{ include "auth.name" . }} + app: {{ include "auth-service.name" . }} template: metadata: labels: - app: {{ include "auth.name" . }} + app: {{ include "auth-service.name" . }} spec: containers: - - name: {{ include "auth.name" . }} + - name: {{ include "auth-service.name" . }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: diff --git a/.helm/templates/hpa.yaml b/.helm/templates/hpa.yaml index cef6907..80bdc04 100644 --- a/.helm/templates/hpa.yaml +++ b/.helm/templates/hpa.yaml @@ -2,12 +2,12 @@ apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: - name: {{ include "auth.fullname" . }} + name: {{ include "auth-service.fullname" . }} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment - name: {{ include "auth.fullname" . }} + name: {{ include "auth-service.fullname" . }} minReplicas: {{ .Values.hpa.minReplicas }} maxReplicas: {{ .Values.hpa.maxReplicas }} metrics: diff --git a/.helm/templates/service.yaml b/.helm/templates/service.yaml index 19a11d3..3f21a35 100644 --- a/.helm/templates/service.yaml +++ b/.helm/templates/service.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "auth.fullname" . }} + name: {{ include "auth-service.fullname" . }} labels: - app: {{ include "auth.name" . }} + app: {{ include "auth-service.name" . }} spec: type: {{ .Values.service.type }} ports: @@ -12,4 +12,4 @@ spec: protocol: TCP name: http selector: - app: {{ include "auth.name" . }} \ No newline at end of file + app: {{ include "auth-service.name" . }} \ No newline at end of file diff --git a/.helm/templates/tests/test-connection.yaml b/.helm/templates/tests/test-connection.yaml index bb2beda..782482a 100644 --- a/.helm/templates/tests/test-connection.yaml +++ b/.helm/templates/tests/test-connection.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "auth.fullname" . }}-test-connection" + name: "{{ include "auth-service.fullname" . }}-test-connection" labels: - app: {{ include "auth.name" . }} + app: {{ include "auth-service.name" . }} annotations: "helm.sh/hook": test "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded @@ -12,5 +12,5 @@ spec: - name: wget image: busybox:1.36 command: ['wget'] - args: ['-qO-', 'http://{{ include "auth.fullname" . }}:{{ .Values.service.port }}'] + args: ['-qO-', 'http://{{ include "auth-service.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never \ No newline at end of file