From bb6706d1a0cf6e6fc5b5bfcc30d7c08ee980dd2b Mon Sep 17 00:00:00 2001 From: Timo Behrendt Date: Wed, 29 Apr 2026 19:49:08 +0200 Subject: [PATCH] refactor!: modernize shared cicd deploy and validate workflows (#49) Reviewed-on: https://gitea.t000-n.de/t.behrendt/k_deploy_workflows/pulls/49 Co-authored-by: Timo Behrendt Co-committed-by: Timo Behrendt --- .../action.yaml | 19 --- .../action.yaml | 19 --- .gitea/workflows/{cd.yaml => deploy.yaml} | 108 ++++-------- .gitea/workflows/{ci.yaml => validate.yaml} | 12 +- README.md | 156 +++++------------- renovate.json | 5 +- 6 files changed, 81 insertions(+), 238 deletions(-) delete mode 100644 .gitea/actions/extract-chart-name-from-repo-name/action.yaml delete mode 100644 .gitea/actions/extract-namespace-from-repo-name/action.yaml rename .gitea/workflows/{cd.yaml => deploy.yaml} (58%) rename .gitea/workflows/{ci.yaml => validate.yaml} (89%) diff --git a/.gitea/actions/extract-chart-name-from-repo-name/action.yaml b/.gitea/actions/extract-chart-name-from-repo-name/action.yaml deleted file mode 100644 index 7e72d80..0000000 --- a/.gitea/actions/extract-chart-name-from-repo-name/action.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: Extract chart name from repo name -description: Extracts the chart name from the repo name, based on the convention of helm- -inputs: - repo: - description: The full repository name (e.g., "helm-my-chart") - required: true -outputs: - chart-name: - description: The extracted chart name - value: ${{ steps.extract.outputs.suffix }} -runs: - using: "composite" - steps: - - id: extract - shell: bash - run: | - full_repo="${{ inputs.repo }}" - suffix="${full_repo##*helm-}" - echo "suffix=$suffix" >> $GITHUB_OUTPUT diff --git a/.gitea/actions/extract-namespace-from-repo-name/action.yaml b/.gitea/actions/extract-namespace-from-repo-name/action.yaml deleted file mode 100644 index d8a639e..0000000 --- a/.gitea/actions/extract-namespace-from-repo-name/action.yaml +++ /dev/null @@ -1,19 +0,0 @@ -name: "Extract namespace from repo name" -description: "Extracts the namespace name from the repo name, based on the convention of k_" -inputs: - repo: - description: 'The repo name, get it from "github.repository"' - required: true -outputs: - namespace: - description: "The namespace name" - value: ${{ steps.extract.outputs.suffix }} -runs: - using: "composite" - steps: - - id: extract - shell: bash - run: | - full_repo="${{ inputs.repo }}" - suffix="${full_repo##*k_}" - echo "suffix=$suffix" >> $GITHUB_OUTPUT diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/deploy.yaml similarity index 58% rename from .gitea/workflows/cd.yaml rename to .gitea/workflows/deploy.yaml index 40c3ba2..fb4518c 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/deploy.yaml @@ -3,35 +3,30 @@ name: Deploy on: workflow_call: inputs: - # Optional: Override the default k8s directory path k8s_dir: - description: "Path to Kubernetes manifests directory" + description: "Override the default k8s directory path (k8s/)" required: false default: "k8s/" type: string - # Optional: Override the default helmfile path helmfile_path: - description: "Path to helmfile.yaml" + description: "Override the default helmfile path (hemfile.yaml)" required: false default: "helmfile.yaml" type: string - # Optional: Skip Helm deployment even if helmfile exists skip_helm_deployment: description: "Skip Helm deployment even if helmfile.yaml exists" required: false default: false type: boolean - # Optional: Custom secrets to create (JSON array of secret objects) - custom_secrets: - description: "JSON array of secrets to create. Each secret should have: name, type, data" + skip_shared_secrets_deployment: + description: "Skip shared secrets deployment (e.g. restic backup secret)" required: false - default: "[]" - type: string - # Optional: Branch to deploy from - deploy_branch: - description: "Branch to deploy from" + default: false + type: boolean + helmfile_env: + description: "Optional JSON object string of environment variables for Helmfile" required: false - default: "main" + default: "{}" type: string jobs: @@ -63,15 +58,33 @@ jobs: echo "No k8s directory found at ${{ inputs.k8s_dir }}" fi + deploy-shared-secrets: + runs-on: ubuntu-latest + needs: detect-service-type + if: inputs.skip_shared_secrets_deployment != 'true' + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Set restic backup secret + uses: azure/k8s-create-secret@6e0ba8047235646753f2a3a3b359b4d0006ff218 # v5.0.1 + with: + namespace: ${{ steps.namespace.outputs.namespace }} + secret-name: backupsidecar-secret + secret-type: generic + data: | + { + "restic_password": "${{ secrets.RESTIC_PASSWORD }}", + "restic_rest_username": "${{ secrets.RESTIC_REST_USERNAME }}", + "restic_rest_password": "${{ secrets.RESTIC_REST_PASSWORD }}", + "gotify_token": "${{ secrets.GOTIFY_TOKEN }}" + } + deploy-k8s: runs-on: ubuntu-latest needs: detect-service-type if: needs.detect-service-type.outputs.has_k8s == 'true' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ inputs.deploy_branch }} - - uses: ./.gitea/actions/extract-namespace-from-repo-name + - uses: https://gitea.t000-n.de/t.behrendt/k_deploy_actions/.gitea/actions/extract-namespace-from-repo-name@0.0.1 id: namespace with: repo: ${{ github.repository }} @@ -80,29 +93,6 @@ jobs: with: method: kubeconfig kubeconfig: ${{ secrets.KUBECONFIG }} - - name: Create custom secrets - id: create-secrets - run: | - # Parse custom secrets from input - SECRETS='${{ inputs.custom_secrets }}' - if [ "$SECRETS" != "[]" ]; then - echo "Creating custom secrets..." - echo "$SECRETS" | jq -c '.[]' | while read -r secret; do - SECRET_NAME=$(echo "$secret" | jq -r '.name') - SECRET_TYPE=$(echo "$secret" | jq -r '.type // "generic"') - SECRET_DATA=$(echo "$secret" | jq -r '.data') - - echo "Creating secret: $SECRET_NAME (type: $SECRET_TYPE)" - - # Create the secret using kubectl - echo "$SECRET_DATA" | kubectl create secret $SECRET_TYPE $SECRET_NAME \ - --from-literal=secret.json="$SECRET_DATA" \ - --namespace=${{ steps.namespace.outputs.namespace }} \ - --dry-run=client -o yaml | kubectl apply -f - - done - else - echo "No custom secrets to create" - fi - name: Deploy Kubernetes manifests uses: azure/k8s-deploy@c7ebd0d5f39477a23f1b5dea0f52e6db04adf28e # v6.0.0 with: @@ -120,9 +110,7 @@ jobs: inputs.skip_helm_deployment != 'true' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ inputs.deploy_branch }} - - uses: ./.gitea/actions/extract-namespace-from-repo-name + - uses: https://gitea.t000-n.de/t.behrendt/k_deploy_actions/.gitea/actions/extract-namespace-from-repo-name@0.0.1 id: namespace with: repo: ${{ github.repository }} @@ -132,38 +120,16 @@ jobs: with: method: kubeconfig kubeconfig: ${{ secrets.KUBECONFIG }} - - name: Create custom secrets - id: create-secrets - run: | - # Parse custom secrets from input - SECRETS='${{ inputs.custom_secrets }}' - if [ "$SECRETS" != "[]" ]; then - echo "Creating custom secrets..." - echo "$SECRETS" | jq -c '.[]' | while read -r secret; do - SECRET_NAME=$(echo "$secret" | jq -r '.name') - SECRET_TYPE=$(echo "$secret" | jq -r '.type // "generic"') - SECRET_DATA=$(echo "$secret" | jq -r '.data') - - echo "Creating secret: $SECRET_NAME (type: $SECRET_TYPE)" - - # Create the secret using kubectl - echo "$SECRET_DATA" | kubectl create secret $SECRET_TYPE $SECRET_NAME \ - --from-literal=secret.json="$SECRET_DATA" \ - --namespace=${{ steps.namespace.outputs.namespace }} \ - --dry-run=client -o yaml | kubectl apply -f - - done - else - echo "No custom secrets to create" - fi - name: Deploy Helm uses: helmfile/helmfile-action@02671705b1dda1dc4b0a4ddd4f9f1ea8f4568c6f # v2.4.3 with: helmfile-args: apply + env: ${{ fromJSON(inputs.helmfile_env) }} # Summary job that always runs to show what was deployed deployment-summary: runs-on: ubuntu-latest - needs: [ detect-service-type, deploy-k8s, deploy-helm ] + needs: [detect-service-type, deploy-k8s, deploy-helm] if: always() steps: - name: Deployment Summary @@ -187,11 +153,3 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "**Service Type**: ${{ needs.detect-service-type.outputs.has_helmfile == 'true' && 'Helm + Kubernetes' || 'Kubernetes Only' }}" >> $GITHUB_STEP_SUMMARY - - # Show custom secrets info - SECRETS='${{ inputs.custom_secrets }}' - if [ "$SECRETS" != "[]" ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Custom Secrets Created**: $(echo "$SECRETS" | jq length)" >> $GITHUB_STEP_SUMMARY - echo "$SECRETS" | jq -r '.[] | "- " + .name + " (" + (.type // "generic") + ")"' >> $GITHUB_STEP_SUMMARY - fi diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/validate.yaml similarity index 89% rename from .gitea/workflows/ci.yaml rename to .gitea/workflows/validate.yaml index 42a249f..c9316e2 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/validate.yaml @@ -18,6 +18,11 @@ on: required: false default: false type: boolean + helmfile_env: + description: "Optional JSON object string of environment variables for Helmfile" + required: false + default: "{}" + type: string jobs: detect-service-type: @@ -54,7 +59,7 @@ jobs: if: needs.detect-service-type.outputs.has_k8s == 'true' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: ./.gitea/actions/extract-namespace-from-repo-name + - uses: https://gitea.t000-n.de/t.behrendt/k_deploy_actions/.gitea/actions/extract-namespace-from-repo-name@0.0.1 id: namespace with: repo: ${{ github.repository }} @@ -79,7 +84,7 @@ jobs: inputs.skip_helm_validation != 'true' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: ./.gitea/actions/extract-namespace-from-repo-name + - uses: https://gitea.t000-n.de/t.behrendt/k_deploy_actions/.gitea/actions/extract-namespace-from-repo-name@0.0.1 id: namespace with: repo: ${{ github.repository }} @@ -93,11 +98,12 @@ jobs: uses: helmfile/helmfile-action@02671705b1dda1dc4b0a4ddd4f9f1ea8f4568c6f # v2.4.3 with: helmfile-args: diff + env: ${{ fromJSON(inputs.helmfile_env) }} # Summary job that always runs to show what was validated ci-summary: runs-on: ubuntu-latest - needs: [ detect-service-type, validate-k8s, validate-helm ] + needs: [detect-service-type, validate-k8s, validate-helm] if: always() steps: - name: CI Summary diff --git a/README.md b/README.md index 1395b3e..5cd0e4a 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,75 @@ -> [!WARNING] -> Repo is currently not in use and not tested. -> We are waiting for proper shared workflow UI support in gitea. Otherwise errors are hard to identify. -> Follow https://github.com/go-gitea/gitea/issues/24604 +# Reusable CI/CD Workflows for k\_ Services -# Reusable CI Workflow for Kubernetes Services - -This directory contains a reusable CI workflow that automatically detects and validates your Kubernetes services, whether they use Helm + Kubernetes or just Kubernetes manifests. +This repository contains reusable CI and CD workflows that automatically detect Kubernetes service type (Kubernetes-only vs Helm + Kubernetes) and execute the relevant steps. ## Features -- **Automatic Detection**: Automatically detects if your service uses Helm (helmfile.yaml) or just Kubernetes manifests -- **Conditional Validation**: Only runs Helm validation when helmfile.yaml exists -- **Flexible Paths**: Configurable paths for k8s directory and helmfile -- **Comprehensive Validation**: Validates both Kubernetes manifests and Helm charts -- **CI Summary**: Provides a clear summary of what was validated +- **Reusable CI and CD**: Separate reusable workflows for validation and deployment +- **Automatic Detection**: Detects whether `helmfile.yaml` exists and whether `k8s/` exists +- **Conditional Execution**: Runs Helm steps only when applicable +- **Flexible Inputs**: Supports custom `k8s_dir` and `helmfile_path` ## Usage -### Basic Usage (Recommended) +### Basic CI Usage -Simply call the workflow without any parameters - it will automatically detect your service type: +Call the reusable CI workflow: ```yaml jobs: ci: - uses: ./.gitea/workflows/ci.yaml + uses: https://gitea.t000-n.de/t.behrendt/k_deploy_workflows/.gitea/workflows/ci.yaml@main + secrets: inherit ``` -### Advanced Usage with Custom Paths +### Basic CD Usage -If your service uses non-standard directory names: +Call the reusable CD workflow: + +```yaml +jobs: + deploy: + uses: https://gitea.t000-n.de/t.behrendt/k_deploy_workflows/.gitea/workflows/cd.yaml@main + secrets: inherit +``` + +### Advanced Usage with Custom Paths and Flags ```yaml jobs: ci: - uses: ./.gitea/workflows/ci.yaml + uses: https://gitea.t000-n.de/t.behrendt/k_deploy_workflows/.gitea/workflows/ci.yaml@main with: k8s_dir: "kubernetes/" helmfile_path: "helm/helmfile.yaml" -``` - -### Force Skip Helm Validation - -If you want to skip Helm validation even when helmfile.yaml exists: - -```yaml -jobs: - ci: - uses: ./.gitea/workflows/ci.yaml - with: skip_helm_validation: true + secrets: inherit ``` -## Input Parameters +## Inputs + +### CI (`.gitea/workflows/ci.yaml`) | Parameter | Description | Default | Required | | ---------------------- | -------------------------------------------- | --------------- | -------- | | `k8s_dir` | Path to Kubernetes manifests directory | `k8s/` | No | | `helmfile_path` | Path to helmfile.yaml | `helmfile.yaml` | No | | `skip_helm_validation` | Skip Helm validation even if helmfile exists | `false` | No | +| `helmfile_env` | JSON object string passed as env to helmfile | `{}` | No | + +### CD (`.gitea/workflows/cd.yaml`) + +| Parameter | Description | Default | Required | +| -------------------------------- | -------------------------------------------- | --------------- | -------- | +| `k8s_dir` | Path to Kubernetes manifests directory | `k8s/` | No | +| `helmfile_path` | Path to helmfile.yaml | `helmfile.yaml` | No | +| `skip_helm_deployment` | Skip Helm deployment even if helmfile exists | `false` | No | +| `skip_shared_secrets_deployment` | Skip shared secrets deployment | `false` | No | +| `helmfile_env` | JSON object string passed as env to helmfile | `{}` | No | ## Directory Structure Requirements -### For Kubernetes-only services: - -``` -your-service/ -├── k8s/ -│ ├── deployment.yaml -│ ├── service.yaml -│ └── ... -└── .gitea/workflows/your-workflow.yaml -``` - -### For Helm + Kubernetes services: +### Full example structure (Helm + Kubernetes): ``` your-service/ @@ -84,79 +80,3 @@ your-service/ ├── helmfile.yaml └── .gitea/workflows/your-workflow.yaml ``` - -## What Gets Validated - -### Always (if k8s/ directory exists): - -- Kubernetes manifest validation using `kubectl --dry-run` -- Namespace extraction from repository name -- Basic Kubernetes syntax and schema validation - -### Conditionally (if helmfile.yaml exists and Helm validation not skipped): - -- Helm chart validation using `helmfile diff` -- Kubernetes manifests in Helm context -- Helm-specific configurations and values - -## Example Workflows - -See `example-usage.yaml` for complete examples of how to use this workflow in different scenarios. - -## Available Actions - -### Extract Chart Name from Repository Name - -The `extract-chart-name-from-repo-name` action extracts the chart name from repository names following the `helm-` convention. - -#### Usage - -```yaml -- name: Extract chart name - uses: ./.gitea/actions/extract-chart-name-from-repo-name - with: - repo: ${{ github.repository_name }} # e.g., "helm-my-service" -``` - -#### Inputs - -| Parameter | Description | Required | -| --------- | ------------------------------------------------ | -------- | -| `repo` | The full repository name (e.g., "helm-my-chart") | Yes | - -#### Outputs - -| Output | Description | -| ------------ | ---------------------------------------------------------------- | -| `chart-name` | The extracted chart name (e.g., "my-chart" from "helm-my-chart") | - -#### Example - -For a repository named `helm-user-service`, this action will extract `user-service` as the chart name. - -## Dependencies - -This workflow requires: - -- `./.gitea/actions/extract-namespace-from-repo-name` action -- `./.gitea/actions/extract-chart-name-from-repo-name` action -- `KUBECONFIG` secret configured in your repository -- Access to your Kubernetes cluster - -## Troubleshooting - -### Helm validation skipped unexpectedly - -- Check if `helmfile.yaml` exists in the expected location -- Verify the `skip_helm_validation` parameter is not set to `true` -- Ensure the file path is correct if using custom paths - -### Kubernetes validation skipped - -- Verify the `k8s/` directory (or custom path) exists -- Check the directory contains valid Kubernetes manifests - -### Permission issues - -- Ensure the `KUBECONFIG` secret is properly configured -- Verify the workflow has access to your Kubernetes cluster diff --git a/renovate.json b/renovate.json index 9e28a36..f275018 100644 --- a/renovate.json +++ b/renovate.json @@ -1,7 +1,4 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "local>t.behrendt/renovate-configs:common", - "local>t.behrendt/renovate-configs:action" - ] + "extends": ["local>t.behrendt/renovate-configs:action"] }