From 864f00da6a161dd6bd9878a70d85d4cdc667b870 Mon Sep 17 00:00:00 2001 From: Seagyn Davis Date: Fri, 12 Dec 2025 10:18:07 +0200 Subject: [PATCH 1/2] feat: add helm chart --- .github/workflows/helm-release.yml | 132 +++++++++ README.md | 27 ++ chart/.helmignore | 25 ++ chart/Chart.yaml | 17 ++ chart/README.md | 257 ++++++++++++++++++ chart/templates/NOTES.txt | 21 ++ chart/templates/_helpers.tpl | 159 +++++++++++ chart/templates/certificate.yaml | 37 +++ chart/templates/certificates.yaml | 27 ++ chart/templates/deployment.yaml | 99 +++++++ .../mutatingwebhookconfiguration.yaml | 50 ++++ chart/templates/service.yaml | 21 ++ chart/templates/serviceaccount.yaml | 17 ++ chart/values.yaml | 127 +++++++++ 14 files changed, 1016 insertions(+) create mode 100644 .github/workflows/helm-release.yml create mode 100644 chart/.helmignore create mode 100644 chart/Chart.yaml create mode 100644 chart/README.md create mode 100644 chart/templates/NOTES.txt create mode 100644 chart/templates/_helpers.tpl create mode 100644 chart/templates/certificate.yaml create mode 100644 chart/templates/certificates.yaml create mode 100644 chart/templates/deployment.yaml create mode 100644 chart/templates/mutatingwebhookconfiguration.yaml create mode 100644 chart/templates/service.yaml create mode 100644 chart/templates/serviceaccount.yaml create mode 100644 chart/values.yaml diff --git a/.github/workflows/helm-release.yml b/.github/workflows/helm-release.yml new file mode 100644 index 0000000..b3754f9 --- /dev/null +++ b/.github/workflows/helm-release.yml @@ -0,0 +1,132 @@ +name: Helm Chart Release + +on: + push: + branches: + - 'main' + paths: + - 'chart/**' + workflow_dispatch: + +permissions: + contents: read + packages: write + +jobs: + release-chart: + name: Release Helm Chart + runs-on: ubuntu-latest + steps: + - name: Checkout code + 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 + with: + version: '3.14.0' + + - name: Log in to GitHub Container Registry + run: | + echo ${{ secrets.GITHUB_TOKEN }} | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Extract chart version + id: chart_version + run: | + CHART_VERSION=$(grep '^version:' chart/Chart.yaml | awk '{print $2}') + echo "Using Chart.yaml version: $CHART_VERSION" + echo "version=$CHART_VERSION" >> $GITHUB_OUTPUT + + - name: Check if version was updated for chart changes + if: github.event_name == 'push' + run: | + CHART_VERSION="${{ steps.chart_version.outputs.version }}" + + # Get the previous commit's chart version + git show HEAD~1:chart/Chart.yaml > /tmp/previous-chart.yaml 2>/dev/null || echo "version: 0.0.0" > /tmp/previous-chart.yaml + PREVIOUS_VERSION=$(grep '^version:' /tmp/previous-chart.yaml | awk '{print $2}' || echo "0.0.0") + + echo "Current version: $CHART_VERSION" + echo "Previous version: $PREVIOUS_VERSION" + + # Check if chart files were modified + CHART_CHANGES=$(git diff --name-only HEAD~1 HEAD | grep '^chart/' | grep -v 'chart/README.md' || true) + + if [ -n "$CHART_CHANGES" ] && [ "$CHART_VERSION" = "$PREVIOUS_VERSION" ]; then + echo "❌ Chart files were modified but version was not updated!" + echo "Modified files:" + echo "$CHART_CHANGES" + echo "" + echo "Please update the version in chart/Chart.yaml before pushing chart changes." + exit 1 + elif [ -n "$CHART_CHANGES" ]; then + echo "✅ Chart files modified and version updated: $PREVIOUS_VERSION → $CHART_VERSION" + else + echo "ℹ️ No chart files modified (excluding README)" + fi + + - name: Check if chart version already exists + id: check_version + run: | + CHART_VERSION="${{ steps.chart_version.outputs.version }}" + REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + + # Check if the chart version already exists in the registry + if helm pull oci://ghcr.io/$REPO_LOWER/charts/archy --version $CHART_VERSION 2>/dev/null; then + echo "Chart version $CHART_VERSION already exists in registry" + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "Chart version $CHART_VERSION does not exist, proceeding with release" + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Package Helm chart + if: steps.check_version.outputs.exists == 'false' + run: | + helm dependency update chart/ + helm package chart/ --destination . + + - name: Push Helm chart to OCI registry + if: steps.check_version.outputs.exists == 'false' + run: | + CHART_VERSION="${{ steps.chart_version.outputs.version }}" + REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + + helm push archy-${CHART_VERSION}.tgz oci://ghcr.io/$REPO_LOWER/charts + + - name: Create GitHub release for chart + if: steps.check_version.outputs.exists == 'false' && github.event_name == 'push' + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: chart-v${{ steps.chart_version.outputs.version }} + release_name: Helm Chart v${{ steps.chart_version.outputs.version }} + body: | + Helm Chart release v${{ steps.chart_version.outputs.version }} + + ## Installation + + ```bash + helm install archy oci://ghcr.io/${{ github.repository_owner }}/archy/charts/archy --version ${{ steps.chart_version.outputs.version }} + ``` + draft: false + prerelease: false + + - name: Summary + run: | + CHART_VERSION="${{ steps.chart_version.outputs.version }}" + REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]') + + if [ "${{ steps.check_version.outputs.exists }}" == "true" ]; then + echo "✅ Chart version $CHART_VERSION already exists in registry" + else + echo "✅ Successfully released Helm chart v$CHART_VERSION" + echo "📦 Chart available at: oci://ghcr.io/$REPO_LOWER/charts/archy:$CHART_VERSION" + fi \ No newline at end of file diff --git a/README.md b/README.md index 50d5378..8229d07 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,33 @@ When a Pod is submitted to the cluster: - **Network Connectivity**: The webhook must have network access to the container registries to inspect images. - **Explicit Secrets**: For private registries, `imagePullSecrets` must be explicitly defined in the Pod spec or attached to the ServiceAccount. +## Installation + +### Using Helm (Recommended) + +Install from GitHub Container Registry: + +```bash +# Install the latest version +helm install archy oci://ghcr.io/lsdopen/archy/charts/archy + +# Install a specific version +helm install archy oci://ghcr.io/lsdopen/archy/charts/archy --version 0.1.0 + +# Install with custom values +helm install archy oci://ghcr.io/lsdopen/archy/charts/archy --values my-values.yaml +``` + +### Manual Deployment + +Apply the Kubernetes manifests directly: + +```bash +kubectl apply -f deploy/ +``` + +For detailed configuration options and advanced deployment scenarios, see the [Helm Chart README](chart/README.md). + ## License Apache License 2.0 diff --git a/chart/.helmignore b/chart/.helmignore new file mode 100644 index 0000000..0d95a92 --- /dev/null +++ b/chart/.helmignore @@ -0,0 +1,25 @@ +# 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/ +# Example values file +values-example.yaml \ No newline at end of file diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000..049f78e --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: archy +description: A Kubernetes mutating admission webhook that automatically ensures Pods are scheduled on nodes with compatible architectures +type: application +version: 0.1.0 +appVersion: "latest" +keywords: + - kubernetes + - admission-webhook + - multi-architecture + - scheduling +home: https://github.com/lsdopen/archy +sources: + - https://github.com/lsdopen/archy +maintainers: + - name: Seagyn Davis + email: seagyn@lsdopen.io \ No newline at end of file diff --git a/chart/README.md b/chart/README.md new file mode 100644 index 0000000..8abc754 --- /dev/null +++ b/chart/README.md @@ -0,0 +1,257 @@ +# Archy Helm Chart + +A Kubernetes mutating admission webhook that automatically ensures Pods are scheduled on nodes with compatible architectures in multi-architecture clusters. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.0+ + +## Installing the Chart + +### From GitHub Container Registry (Recommended) + +Install the latest version: + +```bash +helm install archy oci://ghcr.io/lsdopen/archy/charts/archy +``` + +Install a specific version: + +```bash +helm install archy oci://ghcr.io/lsdopen/archy/charts/archy --version 0.1.0 +``` + +Install with custom values: + +```bash +helm install archy oci://ghcr.io/lsdopen/archy/charts/archy --values values-production.yaml +``` + +### From Source + +To install the chart from source with the release name `archy`: + +```bash +helm install archy ./chart +``` + +Or with custom values: + +```bash +helm install archy ./chart -f values-production.yaml +``` + +## Uninstalling the Chart + +To uninstall/delete the `archy` deployment: + +```bash +helm delete archy +``` + +## Configuration + +The chart comes with sensible defaults and requires no configuration for basic deployment. All parameters are optional and can be customized as needed. + +### Default Configuration + +The chart automatically configures: +- **Image**: `ghcr.io/lsdopen/archy:1.0.0` with `IfNotPresent` pull policy +- **Service**: ClusterIP on port 443 +- **Webhook**: 5-second timeout with "Fail" policy +- **Certificates**: Helm-generated self-signed certificates (1-year validity) + +### Certificate-Specific Required Parameters + +#### When using `certificates.method: "helm"` (default) +| Parameter | Description | Default | Type | +|-----------|-------------|---------|------| +| `certificates.helm.duration` | Certificate validity duration | `"8760h"` | `string` | +| `certificates.helm.subject.organizationName` | Certificate organization name | `"Archy Webhook"` | `string` | + +#### When using `certificates.method: "cert-manager"` +| Parameter | Description | Type | +|-----------|-------------|------| +| `certificates.certManager.issuer.name` | cert-manager issuer name | `string` | +| `certificates.certManager.issuer.kind` | cert-manager issuer kind (Issuer/ClusterIssuer) | `string` | + +#### When using `certificates.method: "external"` +| Parameter | Description | Type | +|-----------|-------------|------| +| `certificates.external.secretName` | Secret containing TLS certificates | `string` | +| `certificates.external.certFile` | Certificate file name in secret | `string` | +| `certificates.external.keyFile` | Private key file name in secret | `string` | +| `certificates.external.caBundle` | Base64 encoded CA bundle | `string` | + +### Optional Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `replicaCount` | Number of replicas | `1` | +| `imagePullSecrets` | Image pull secrets | `[]` | +| `serviceAccount.create` | Create service account | `true` | +| `serviceAccount.annotations` | Service account annotations | `{}` | +| `serviceAccount.name` | Service account name | `""` | +| `podAnnotations` | Pod annotations | `{}` | +| `podSecurityContext` | Pod security context | `{}` | +| `securityContext` | Container security context | `{}` | +| `resources` | Resource limits and requests | `{}` | +| `nodeSelector` | Node selector | `{}` | +| `tolerations` | Tolerations | `[]` | +| `affinity` | Affinity rules | `{}` | +| `topologySpreadConstraints` | Topology spread constraints | `[]` | +| `certificates.helm.subject.organizationalUnit` | Certificate organizational unit | `""` | +| `certificates.helm.subject.country` | Certificate country code | `""` | +| `certificates.helm.subject.province` | Certificate province/state | `""` | +| `certificates.helm.subject.locality` | Certificate city/locality | `""` | +| `certificates.certManager.duration` | Certificate duration (cert-manager) | `""` | +| `certificates.certManager.renewBefore` | Certificate renewal time (cert-manager) | `""` | +| `webhook.objectSelector` | Additional object selector expressions | `{}` | +| `webhook.namespaceSelector` | Additional namespace selector expressions | `{}` | +| `labels` | Additional labels for all resources | `{}` | +| `annotations` | Additional annotations for all resources | `{}` | + +## Certificate Management + +The Archy webhook requires TLS certificates to function properly. The chart supports three certificate management methods: + +### 1. Helm-Generated Certificates (Recommended for Development) + +Helm automatically generates self-signed certificates during installation: + +```yaml +certificates: + method: "helm" + helm: + duration: "8760h" # 1 year + subject: + organizationName: "Your Organization" +``` + +### 2. cert-manager Integration (Recommended for Production) + +Use cert-manager to automatically provision and renew certificates: + +```yaml +certificates: + method: "cert-manager" + certManager: + issuer: + name: "letsencrypt-prod" + kind: "ClusterIssuer" + duration: "2160h" # 90 days + renewBefore: "720h" # 30 days +``` + +### 3. External Certificate Management + +Bring your own certificates by creating a secret manually: + +```bash +# Generate certificates +./scripts/gen-certs.sh + +# Create secret +kubectl create secret tls archy-webhook-certs \ + --cert=certs/tls.crt \ + --key=certs/tls.key + +# Configure values +certificates: + method: "external" + external: + secretName: "archy-webhook-certs" + certFile: "tls.crt" + keyFile: "tls.key" + caBundle: "$(cat certs/ca.crt | base64 | tr -d '\n')" +``` + +## Example Configurations + +### Basic Configuration + +No configuration required! Install with defaults: + +```bash +helm install archy ./chart +``` + +Or customize as needed: + +```yaml +# Override image (optional) +image: + repository: "your-registry/archy-webhook" + tag: "v2.0.0" + +# Customize webhook behavior (optional) +webhook: + timeoutSeconds: 10 + failurePolicy: "Ignore" +``` + +### High Availability Configuration + +```yaml +replicaCount: 3 + +resources: + limits: + cpu: "200m" + memory: "256Mi" + requests: + cpu: "100m" + memory: "128Mi" + +affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - archy + topologyKey: kubernetes.io/hostname + +topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + app.kubernetes.io/name: archy +``` + +## Troubleshooting + +### Common Issues + +1. **Webhook not intercepting pods**: Check that the MutatingWebhookConfiguration is properly configured and the service is accessible. + +2. **Certificate errors**: Ensure the TLS certificates are valid and the CA bundle matches the certificate authority. + +3. **Permission errors**: Verify the service account has the necessary RBAC permissions to access secrets in target namespaces. + +### Debugging Commands + +```bash +# Check webhook configuration +kubectl get mutatingwebhookconfiguration archy + +# Check webhook pods +kubectl get pods -l app.kubernetes.io/name=archy + +# View webhook logs +kubectl logs -l app.kubernetes.io/name=archy -f + +# Test webhook connectivity +kubectl port-forward svc/archy 8443:443 +curl -k https://localhost:8443/healthz +``` + +## Contributing + +Please read the main project's CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests. \ No newline at end of file diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt new file mode 100644 index 0000000..c2d8e87 --- /dev/null +++ b/chart/templates/NOTES.txt @@ -0,0 +1,21 @@ +1. The Archy webhook is now installed and will automatically: + - Inspect container image manifests to determine supported architectures + - Add kubernetes.io/arch nodeSelector to Pods when a single common architecture is found + - Allow scheduler flexibility when multiple architectures are supported + - Reject Pods when no common architecture exists (fail-safe behavior) + +2. Check the webhook status: + kubectl get mutatingwebhookconfiguration {{ include "archy.fullname" . }} + kubectl get pods -n {{ .Release.Namespace }} -l "{{ include "archy.selectorLabels" . }}" + +3. View webhook logs: + kubectl logs -n {{ .Release.Namespace }} -l "{{ include "archy.selectorLabels" . }}" -f + +4. Test webhook connectivity: + kubectl port-forward -n {{ .Release.Namespace }} svc/{{ include "archy.fullname" . }} 8443:443 + curl -k https://localhost:8443/healthz + +5. The webhook will process all new Pod creations except: + - Pods in kube-system and kube-public namespaces + - Pods with the label app.kubernetes.io/name={{ include "archy.name" . }} + - Pods that already have a nodeSelector defined \ No newline at end of file diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000..738f554 --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,159 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "archy.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 "archy.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 "archy.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "archy.labels" -}} +helm.sh/chart: {{ include "archy.chart" . }} +{{ include "archy.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- with .Values.labels }} +{{ toYaml . }} +{{- end }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "archy.selectorLabels" -}} +app.kubernetes.io/name: {{ include "archy.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "archy.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "archy.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Validate required values +*/}} +{{- define "archy.validateValues" -}} +{{- if not .Values.certificates.method }} +{{- fail "certificates.method is required (helm, cert-manager, or external)" }} +{{- end }} +{{- if eq .Values.certificates.method "helm" }} +{{- if not .Values.certificates.helm.duration }} +{{- fail "certificates.helm.duration is required when using helm certificate method" }} +{{- end }} +{{- if not .Values.certificates.helm.subject.organizationName }} +{{- fail "certificates.helm.subject.organizationName is required when using helm certificate method" }} +{{- end }} +{{- else if eq .Values.certificates.method "cert-manager" }} +{{- if not .Values.certificates.certManager.issuer.name }} +{{- fail "certificates.certManager.issuer.name is required when using cert-manager certificate method" }} +{{- end }} +{{- if not .Values.certificates.certManager.issuer.kind }} +{{- fail "certificates.certManager.issuer.kind is required when using cert-manager certificate method" }} +{{- end }} +{{- else if eq .Values.certificates.method "external" }} +{{- if not .Values.certificates.external.secretName }} +{{- fail "certificates.external.secretName is required when using external certificate method" }} +{{- end }} +{{- if not .Values.certificates.external.certFile }} +{{- fail "certificates.external.certFile is required when using external certificate method" }} +{{- end }} +{{- if not .Values.certificates.external.keyFile }} +{{- fail "certificates.external.keyFile is required when using external certificate method" }} +{{- end }} +{{- if not .Values.certificates.external.caBundle }} +{{- fail "certificates.external.caBundle is required when using external certificate method" }} +{{- end }} +{{- else }} +{{- fail "certificates.method must be one of: helm, cert-manager, external" }} +{{- end }} +{{- end }} + +{{/* +Get the certificate secret name +*/}} +{{- define "archy.certificateSecretName" -}} +{{- if eq .Values.certificates.method "external" }} +{{- .Values.certificates.external.secretName }} +{{- else }} +{{- printf "%s-certs" (include "archy.fullname" .) }} +{{- end }} +{{- end }} + +{{/* +Get the certificate file name +*/}} +{{- define "archy.certificateFile" -}} +{{- if eq .Values.certificates.method "external" }} +{{- .Values.certificates.external.certFile }} +{{- else }} +{{- "tls.crt" }} +{{- end }} +{{- end }} + +{{/* +Get the private key file name +*/}} +{{- define "archy.privateKeyFile" -}} +{{- if eq .Values.certificates.method "external" }} +{{- .Values.certificates.external.keyFile }} +{{- else }} +{{- "tls.key" }} +{{- end }} +{{- end }} + +{{/* +Get the CA bundle +*/}} +{{- define "archy.caBundle" -}} +{{- if eq .Values.certificates.method "external" }} +{{- .Values.certificates.external.caBundle }} +{{- else if eq .Values.certificates.method "helm" }} +{{- $ca := genCA (printf "%s-ca" (include "archy.fullname" .)) (.Values.certificates.helm.duration | int) }} +{{- $cert := genSignedCert (include "archy.serviceName" .) nil (list (include "archy.serviceName" .) (printf "%s.%s" (include "archy.serviceName" .) .Release.Namespace) (printf "%s.%s.svc" (include "archy.serviceName" .) .Release.Namespace) (printf "%s.%s.svc.cluster.local" (include "archy.serviceName" .) .Release.Namespace)) (.Values.certificates.helm.duration | int) $ca }} +{{- $ca.Cert | b64enc }} +{{- else }} +{{- "" }} +{{- end }} +{{- end }} + +{{/* +Get the service name for certificates +*/}} +{{- define "archy.serviceName" -}} +{{- include "archy.fullname" . }} +{{- end }} \ No newline at end of file diff --git a/chart/templates/certificate.yaml b/chart/templates/certificate.yaml new file mode 100644 index 0000000..4dc6202 --- /dev/null +++ b/chart/templates/certificate.yaml @@ -0,0 +1,37 @@ +{{- include "archy.validateValues" . -}} +{{- if eq .Values.certificates.method "cert-manager" }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "archy.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "archy.labels" . | nindent 4 }} + {{- with .Values.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + secretName: {{ include "archy.certificateSecretName" . }} + {{- with .Values.certificates.certManager.duration }} + duration: {{ . }} + {{- end }} + {{- with .Values.certificates.certManager.renewBefore }} + renewBefore: {{ . }} + {{- end }} + subject: + organizationUnits: + - {{ .Release.Namespace }} + commonName: {{ include "archy.serviceName" . }}.{{ .Release.Namespace }}.svc.cluster.local + dnsNames: + - {{ include "archy.serviceName" . }} + - {{ include "archy.serviceName" . }}.{{ .Release.Namespace }} + - {{ include "archy.serviceName" . }}.{{ .Release.Namespace }}.svc + - {{ include "archy.serviceName" . }}.{{ .Release.Namespace }}.svc.cluster.local + issuerRef: + name: {{ .Values.certificates.certManager.issuer.name }} + kind: {{ .Values.certificates.certManager.issuer.kind }} + {{- if eq .Values.certificates.certManager.issuer.kind "Issuer" }} + group: cert-manager.io + {{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/templates/certificates.yaml b/chart/templates/certificates.yaml new file mode 100644 index 0000000..e8bc45b --- /dev/null +++ b/chart/templates/certificates.yaml @@ -0,0 +1,27 @@ +{{- include "archy.validateValues" . -}} +{{- if eq .Values.certificates.method "helm" }} +{{- $serviceName := include "archy.serviceName" . }} +{{- $secretName := include "archy.certificateSecretName" . }} +{{- $duration := (.Values.certificates.helm.duration | int) }} +{{- $subject := .Values.certificates.helm.subject }} + +{{- $ca := genCA (printf "%s-ca" (include "archy.fullname" .)) $duration }} +{{- $cert := genSignedCert $serviceName nil (list $serviceName (printf "%s.%s" $serviceName .Release.Namespace) (printf "%s.%s.svc" $serviceName .Release.Namespace) (printf "%s.%s.svc.cluster.local" $serviceName .Release.Namespace)) $duration $ca }} + +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "archy.labels" . | nindent 4 }} + {{- with .Values.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + tls.crt: {{ $cert.Cert | b64enc }} + tls.key: {{ $cert.Key | b64enc }} + ca.crt: {{ $ca.Cert | b64enc }} +{{- end }} \ No newline at end of file diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml new file mode 100644 index 0000000..26e1235 --- /dev/null +++ b/chart/templates/deployment.yaml @@ -0,0 +1,99 @@ +{{- include "archy.validateValues" . -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "archy.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "archy.labels" . | nindent 4 }} + {{- with .Values.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "archy.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "archy.selectorLabels" . | nindent 8 }} + {{- with .Values.labels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.annotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "archy.serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: webhook + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + args: + - "--tls-cert=/etc/webhook/certs/{{ include "archy.certificateFile" . }}" + - "--tls-key=/etc/webhook/certs/{{ include "archy.privateKeyFile" . }}" + - "--port=8443" + ports: + - name: webhook + containerPort: 8443 + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: webhook + scheme: HTTPS + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /healthz + port: webhook + scheme: HTTPS + initialDelaySeconds: 5 + periodSeconds: 5 + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: webhook-certs + mountPath: /etc/webhook/certs + readOnly: true + volumes: + - name: webhook-certs + secret: + secretName: {{ include "archy.certificateSecretName" . }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/chart/templates/mutatingwebhookconfiguration.yaml b/chart/templates/mutatingwebhookconfiguration.yaml new file mode 100644 index 0000000..27ee6c2 --- /dev/null +++ b/chart/templates/mutatingwebhookconfiguration.yaml @@ -0,0 +1,50 @@ +{{- include "archy.validateValues" . -}} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ include "archy.fullname" . }} + labels: + {{- include "archy.labels" . | nindent 4 }} + {{- with .Values.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +webhooks: + - name: {{ include "archy.name" . }}.{{ .Release.Namespace }}.svc.cluster.local + admissionReviewVersions: ["v1"] + sideEffects: None + timeoutSeconds: {{ .Values.webhook.timeoutSeconds }} + failurePolicy: {{ .Values.webhook.failurePolicy }} + clientConfig: + service: + name: {{ include "archy.fullname" . }} + namespace: {{ .Release.Namespace }} + path: "/mutate" + {{- if eq .Values.certificates.method "external" }} + caBundle: {{ .Values.certificates.external.caBundle }} + {{- else if eq .Values.certificates.method "helm" }} + caBundle: {{ include "archy.caBundle" . }} + {{- else }} + caBundle: "" + {{- end }} + rules: + - operations: ["CREATE"] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] + objectSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: NotIn + values: [{{ include "archy.name" . | quote }}] + {{- with .Values.webhook.objectSelector.matchExpressions }} + {{- toYaml . | nindent 8 }} + {{- end }} + namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: NotIn + values: ["kube-system", "kube-public"] + {{- with .Values.webhook.namespaceSelector.matchExpressions }} + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml new file mode 100644 index 0000000..29c5345 --- /dev/null +++ b/chart/templates/service.yaml @@ -0,0 +1,21 @@ +{{- include "archy.validateValues" . -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "archy.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "archy.labels" . | nindent 4 }} + {{- with .Values.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + ports: + - port: 443 + targetPort: webhook + protocol: TCP + name: webhook + selector: + {{- include "archy.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/chart/templates/serviceaccount.yaml b/chart/templates/serviceaccount.yaml new file mode 100644 index 0000000..dcfe1bf --- /dev/null +++ b/chart/templates/serviceaccount.yaml @@ -0,0 +1,17 @@ +{{- include "archy.validateValues" . -}} +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "archy.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "archy.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/chart/values.yaml b/chart/values.yaml new file mode 100644 index 0000000..48ab768 --- /dev/null +++ b/chart/values.yaml @@ -0,0 +1,127 @@ +# Default values for archy. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Image configuration +image: + repository: ghcr.io/lsdopen/archy + tag: "1.0.0" + pullPolicy: IfNotPresent + +# Image pull secrets +imagePullSecrets: [] + # - name: myregistrykey + +# Deployment configuration +replicaCount: 1 + +# Service account +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: "" + +# Pod annotations +podAnnotations: {} + +# Pod security context +podSecurityContext: {} + # fsGroup: 2000 + +# Container security context +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# Resource limits and requests +resources: {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# Node selector +nodeSelector: {} + +# Tolerations +tolerations: [] + +# Affinity rules +affinity: {} + +# Topology spread constraints +topologySpreadConstraints: [] + +# Certificate management configuration +certificates: + # Certificate management method: "helm" (default), "cert-manager", or "external" + method: helm + + # Helm-generated certificates (when method is "helm") + helm: + # Certificate validity duration + duration: "8760h" # 1 year + # Certificate subject + subject: + organizationName: "Archy Webhook" + organizationalUnit: # Optional: Organizational unit + country: # Optional: Country code + province: # Optional: Province/state + locality: # Optional: City/locality + + # cert-manager configuration (when method is "cert-manager") + certManager: + # Issuer configuration + issuer: + name: # Required when method is "cert-manager": Issuer name + kind: # Required when method is "cert-manager": Issuer kind (Issuer or ClusterIssuer) + # Certificate duration (optional, uses issuer default if not specified) + duration: # Optional: Certificate duration (e.g., "2160h" for 90 days) + # Certificate renewal before expiry + renewBefore: # Optional: Renew certificate before expiry (e.g., "720h" for 30 days) + + # External certificate configuration (when method is "external") + external: + # Secret name containing externally managed TLS certificates + secretName: # Required when method is "external": Name of existing secret + # Certificate and key file names within the secret + certFile: # Required when method is "external": Certificate file name in secret + keyFile: # Required when method is "external": Private key file name in secret + # CA bundle for webhook client config (base64 encoded) + caBundle: # Required when method is "external": CA bundle + +# Webhook configuration +webhook: + # Webhook server configuration + timeoutSeconds: 5 + failurePolicy: Fail + + # Object selector to filter which objects the webhook processes (optional) + objectSelector: {} + # matchExpressions: + # - key: app + # operator: NotIn + # values: ["custom-app"] + + # Namespace selector to filter which namespaces the webhook processes (optional) + namespaceSelector: {} + # matchExpressions: + # - key: kubernetes.io/metadata.name + # operator: NotIn + # values: ["custom-namespace"] + +# Additional labels to add to all resources +labels: {} + +# Additional annotations to add to all resources +annotations: {} \ No newline at end of file From dc3ba9e7712f4dca1c42ddfe02038eb1d059a380 Mon Sep 17 00:00:00 2001 From: Seagyn Davis Date: Fri, 12 Dec 2025 10:23:31 +0200 Subject: [PATCH 2/2] ci: only bump app versions on app changes --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5a8d62b..df7451d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,8 @@ on: push: branches: - 'main' + paths-ignore: + - 'chart/**' permissions: contents: write