--- name: gitops-update description: | Setting up GitOps version updates: GitOps-repo workflow template, code repo dispatch, secret requirements, and two-repo commit-status pattern. Activates when the user needs to wire up artifact builds to GitOps configuration updates. activation-gate: | User mentions GitOps update, gitops-update, dispatch to another repo, two-repo version bump, cross-repo deployment, or wiring build output to config repo. category: ci impact: high --- # GitOps Update — Provider-palvelu `scripts/gitops-update.sh` ja `scripts/dispatch-workflow.sh` muodostavat GitOps-päivityspalvelun. Artifact buildataan code repossa, minkä jälkeen code repo dispatchaa GitOps-repoon, joka päivittää konfiguraatiotiedoston ja pushaa muutoksen. ## Arkkitehtuuri Kaksi erillistä repoa, eristetyt oikeudet: ``` Code repo GitOps repo (build & push artifact) (konfiguraatiot) build & push onnistuu (v0.2.3) │ │ dispatch ci-main.yml │ {file, yq_tpl, version, source_repo, source_commit} │ └────────────────────────────────────→┐ │ dispatch-workflow.sh pollaa ←─────────┘ │ code repo asettaa │ git clone, yq update, oman commit-statusnsa │ git commit + push dispatchin exit-koodilla │ status GitOps-repoon ``` **Token-periaate:** Vain GitOps-repoon kirjoitetaan. Code repo asettaa oman commit-statusnsa dispatch-kutsun exit-koodin perusteella omalla auto-tokenillaan. GitOps-repon auto-token ei tarvitse oikeuksia code repoon. ## GitOps-repon workflow (ci-main.yml) GitOps-repoon luodaan `.gitea/workflows/ci-main.yml`: ```yaml name: GitOps Update run-name: "GitOps Service (${{ inputs.dispatch_id || 'manual' }})" on: workflow_dispatch: inputs: file: required: true type: string yq_tpl: required: true type: string version: required: true type: string source_repo: required: true type: string source_commit: required: true type: string dispatch_id: required: false type: string git_tag_prefix: required: false type: string env: INPUT_FILE: ${{ inputs.file }} YQ_TPL: ${{ inputs.yq_tpl }} VERSION: ${{ inputs.version }} SOURCE_REPO: ${{ inputs.source_repo }} SOURCE_COMMIT: ${{ inputs.source_commit }} GITOPS_REPO: ${{ github.repository }} GITOPS_BRANCH: ${{ github.ref_name }} GITEA_API_URL: ${{ gitea.server_url }} GIT_TAG_PREFIX: ${{ inputs.git_tag_prefix || '' }} jobs: update: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: repository: niko/gitea-ci-library path: .ci - name: Install yq run: | wget -qO /usr/local/bin/yq \ https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 chmod +x /usr/local/bin/yq - name: Run GitOps update env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} run: | bash .ci/scripts/gitops-update.sh ``` **Huomiot:** - `GITEA_TOKEN` on Gitean auto-token — scopeutuu GitOps-repoon, riittää cloneen, committiin, pushiin ja commit-statusiin GitOps-repossa - `run-name` ja `dispatch_id` mahdollistavat dispatchaavan skriptin tunnistaa tämän workflow-runin yksiselitteisesti `display_title`-kentästä, vaikka samassa repossa olisi samanaikaisia ajoja - yq ladataan lennossa (kompromissi, ks. "Tuleva CI-kontti") ### Tulossa: custom CI-kontti Nykyinen job lataa yq:n lennossa. Myöhemmin rakennetaan oma kontti (`ci-gitops`), jossa on nodejs + git + yq valmiina. Sama patterni kuin `ci-bats` ja `ci-cucumber`. Ks. `skills/ci-container-build/SKILL.md`. ## Code-repon dispatch-step Code repo dispatchaa GitOps-repon workflown artifact buildin onnistuttua: ```yaml gitops-update: needs: [helm-build-push] if: success() runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: repository: niko/gitea-ci-library path: .ci - name: Dispatch GitOps update id: dispatch env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} run: | INPUTS=$(jq -nc \ --arg file "dev/Chart.yaml" \ --arg yq_tpl '(.dependencies[] | select(.name == "agent-platform-helm") | .version) = "{{VERSION}}"' \ --arg version "${{ needs.check-version.outputs.version }}" \ --arg source_repo "${{ github.repository }}" \ --arg source_commit "${{ github.sha }}" \ '{file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit}') OUTPUT=$(bash .ci/scripts/dispatch-workflow.sh \ "niko/agent-platform-gitops" \ "ci-main.yml" \ "main" \ "$INPUTS" \ "${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }}" \ "${{ secrets.GITEA_TOKEN }}" \ "30") echo "$OUTPUT" GITOPS_COMMIT=$(echo "$OUTPUT" | grep '^GITOPS_COMMIT=' | cut -d= -f2) echo "gitops_commit=$GITOPS_COMMIT" >> "$GITHUB_OUTPUT" ``` ### Multi-artifact pipeline (kontti + helm) Yksi main-haaran build tuottaa usein sekä Docker-imagen että Helm-chartin. Kumpikin artefakti dispatchaa oman GitOps-päivityksensä rinnakkain: ```yaml gitops-helm: needs: [helm-build-push] if: success() runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: repository: niko/gitea-ci-library path: .ci - name: Update helm version id: helm run: | INPUTS=$(jq -nc \ --arg file "dev/Chart.yaml" \ --arg yq_tpl '(.dependencies[] | select(.name == "git-pages") | .version) = "{{VERSION}}"' \ --arg version "${{ needs.check-version.outputs.version }}" \ --arg source_repo "${{ github.repository }}" \ --arg source_commit "${{ github.sha }}" \ --arg git_tag_prefix "helm" \ '{dispatch_id: "", file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_tag_prefix}') OUTPUT=$(bash .ci/scripts/dispatch-workflow.sh \ "niko/gitea-ci-gitops-tests" "gitops-service.yaml" "main" \ "$INPUTS" "${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }}" \ "${{ secrets.GITOPS_DISPATCH_TOKEN }}" "30") echo "$OUTPUT" echo "helm_commit=$(echo "$OUTPUT" | grep '^GITOPS_COMMIT=' | cut -d= -f2)" >> "$GITHUB_OUTPUT" gitops-docker: needs: [docker-build-push] if: success() runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/checkout@v4 with: repository: niko/gitea-ci-library path: .ci - name: Update docker tag id: docker run: | INPUTS=$(jq -nc \ --arg file "dev/values.yaml" \ --arg yq_tpl '.service.tag = "{{VERSION}}"' \ --arg version "${{ needs.check-version.outputs.version }}" \ --arg source_repo "${{ github.repository }}" \ --arg source_commit "${{ github.sha }}" \ --arg git_tag_prefix "docker" \ '{dispatch_id: "", file: $file, yq_tpl: $yq_tpl, version: $version, source_repo: $source_repo, source_commit: $source_commit, git_tag_prefix: $git_tag_prefix}') OUTPUT=$(bash .ci/scripts/dispatch-workflow.sh \ "niko/gitea-ci-gitops-tests" "gitops-service.yaml" "main" \ "$INPUTS" "${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }}" \ "${{ secrets.GITOPS_DISPATCH_TOKEN }}" "30") echo "$OUTPUT" echo "docker_commit=$(echo "$OUTPUT" | grep '^GITOPS_COMMIT=' | cut -d= -f2)" >> "$GITHUB_OUTPUT" ``` Kaksi dispatchia, kaksi eri tiedostoa, kaksi eri `GIT_TAG_PREFIX`-arvoa. Kummallakin on oma commit-status-linja ja oma summary-rivi. `dispatch-workflow.sh` hoitaa rinnakkaisuuden `display_title`-matchauksella. **GITEA_TOKEN dispatch-vaiheessa:** Tarvitaan manuaalinen token, jolla on **write-oikeus GitOps-repoon** (esim. org-tason token). Code-repon auto-token ei oikeuta dispatchaamaan toiseen repoon. Token luodaan Giteassa: `Settings → Applications → Generate Token` ja asetetaan code-repoon Actions Secretiksi. ### Commit-status dispatchin perusteella `dispatch-workflow.sh` tulostaa `GITOPS_COMMIT=` stdoutiin onnistuneen GitOps-päivityksen jälkeen. Code repo parsii sen ja asettaa commit-statusin linkillä GitOps-committiin: ```yaml - name: Set commit-status with GitOps link if: always() env: GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_API_URL: ${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }} GITOPS_COMMIT: ${{ steps.dispatch.outputs.gitops_commit }} VERSION: ${{ needs.check-version.outputs.version }} run: | GITOPS_URL="${GITEA_API_URL}/niko/agent-platform-gitops/commit/${GITOPS_COMMIT}" CTX="gitops/$(basename ${{ github.repository }})" DESC="Deploy to dev ${VERSION}" if [ -n "$GITOPS_COMMIT" ]; then bash .ci/scripts/report-status.sh success "$DESC" "$CTX" "" "$GITOPS_URL" else bash .ci/scripts/report-status.sh success "$DESC" "$CTX" fi ``` `dispatch-workflow.sh` palauttaa: - exit 0 = GitOps-päivitys onnistui (+ `GITOPS_COMMIT=`) - exit 1 = GitOps-päivitys failasi - exit 124 = aikakatkaisu (360 min oletus) ### Loppuraportti (report-summary) Code-repon viimeinen job (`report-summary`) lisää GitOps-päivityksestä rivin GITHUB_STEP_SUMMARYyn: ```yaml - name: GitOps summary if: always() env: GITEA_API_URL: ${{ fromJson(needs.load-config.outputs.env_json).GITEA_API_URL }} GITOPS_COMMIT: ${{ steps.dispatch.outputs.gitops_commit }} VERSION: ${{ needs.check-version.outputs.version }} run: | if [ -n "$GITOPS_COMMIT" ]; then LINK="${GITEA_API_URL}/niko/agent-platform-gitops/commit/${GITOPS_COMMIT}" else LINK="#" fi cat >> "$GITHUB_STEP_SUMMARY" << 'GITOPS' ## GitOps updates | Component | Version | Status | Commit | |-----------|---------|--------|--------| | agent-platform-helm | __VERSION__ | __STATUS__ | [link](__LINK__) | GITOPS sed -i "s|__VERSION__|${VERSION}|; s|__STATUS__|${{ job.status }}|; s|__LINK__|${LINK}|" \ "$GITHUB_STEP_SUMMARY" ``` ## Secretit ja tokenit | Secret | Missä | Scope | Kuvaus | |--------|-------|-------|--------| | `GITEA_TOKEN` (auto) | Code repo | Vain code repo | Asettaa commit-statusin dispatchin jälkeen | | `GITEA_TOKEN` (auto) | GitOps repo | Vain GitOps repo | Klooni, push, commit-status GitOps-repossa | | `GITOPS_DISPATCH_TOKEN` (manuaalinen) | Code repo | Write GitOps-repoon | Dispatchaa GitOps-repon workflow | **Tokenin luonti:** 1. Gitea → `Settings` → `Applications` → `Generate Token` 2. Valitse repo-oikeudet: valitse GitOps-repo, anna write-oikeudet 3. Token asetetaan code-repoon: `{repo} → Settings → Actions Secrets` 4. Salaisuuden nimi: esim. `GITOPS_DISPATCH_TOKEN` ## Provider-skriptit ### `scripts/gitops-update.sh` Ajaan GitOps-repon workflow'ssa. Päivittää konfiguraatiotiedoston yq:llä, committaa ja pushaa. Asettaa commit-statuksen vain GitOps-repoon. **Input-ympäristömuuttujat:** | Muuttuja | Pakollinen | Kuvaus | |---|---|---| | `INPUT_FILE` | Kyllä | Tiedosto GitOps-repossa (esim. `dev/Chart.yaml`) | | `YQ_TPL` | Kyllä | yq-lauseke `{{VERSION}}`-placeholderilla | | `VERSION` | Kyllä | Uusi versio (esim. `0.2.3`) | | `SOURCE_REPO` | Kyllä | Code-repo slug (esim. `org/app`) | | `SOURCE_COMMIT` | Kyllä | Code-repon commit SHA | | `GITOPS_REPO` | Kyllä | GitOps-repo slug | | `GITEA_API_URL` | Kyllä | Gitean API-URL | | `GITEA_TOKEN` | Kyllä | Gitea API-token (write GitOps-repoon) | | `GITOPS_BRANCH` | Ei | Branch (oletus `main`) | | `GIT_TAG_PREFIX` | Ei | Komponentin tag-prefix status-nimeämiseen (esim. `agent-platform-helm`) | | `GITOPS_CLONE_URL` | Ei | Yliajaa clone-URL (esim. eri protokolla) | | `GITOPS_TARGET_DIR` | Ei | Yliajaa clone-kohdehakemisto | **Commit-status muoto:** GitOps-repoon asetetaan commit-status: | Kenttä | Formaatti | Esimerkki | |--------|-----------|-----------| | Context | `{repo}/{GIT_TAG_PREFIX} {RUN_ID}` tai `{repo} {RUN_ID}` | `gitea-ci-library/agent-platform-helm 473` | | Description | `Install to {env} {version}` | `Install to dev 0.2.0` | | Target URL | Linkki code-repon committiin | `/niko/gitea-ci-library/commit/abc123` | Jos tiedosto on jo halutussa versiossa (ei muutoksia), status saa descriptionin `Install to {env} {version} — no change`. Commit-pushia ei tehdä, GitOps-repo pysyy muuttumattomana. - `{env}` parsitaan `INPUT_FILE`:stä (`dev/Chart.yaml` → `dev`) - `{repo}` parsitaan `SOURCE_REPO`:sta (`niko/gitea-ci-library` → `gitea-ci-library`) - `{GIT_TAG_PREFIX}` tulee env-varista (sama kuin `gitea-env.conf`:ssa) ### `scripts/dispatch-workflow.sh` Dispatchaa workflow_dispatchin kohderepoon ja pollaa valmistumista. Generoi automaattisesti `dispatch_id`-tunnisteen, lisää sen dispatch- inputteihin ja tunnistaa workflow-runin kohdereposta `display_title`- kentän perusteella. Toimii luotettavasti vaikka samassa repossa olisi useita samanaikaisia dispatch-attribuutioita. **Argumentit:** | # | Pakollinen | Kuvaus | |---|------------|--------| | 1 | Kyllä | Kohderepo (esim. `niko/agent-platform-gitops`) | | 2 | Kyllä | Workflow-tiedosto (esim. `ci-main.yml`) | | 3 | Kyllä | Branch/ref | | 4 | Kyllä | Inputs JSON | | 5 | Kyllä | Gitea API URL | | 6 | Kyllä | Gitea token | | 7 | Ei | Aikakatkaisu minuutteina (oletus 360) | Kutsujan ei tarvitse välittää `dispatch_id`:tä — skripti generoi sen itse ja lisää inputteihin ennen dispatchia. ## [skip ci] Commit-viestissä on `[skip ci]`, joka estää GitActions-runneria triggeröimästä uutta CI-ajoa GitOps-repoon pushista. Näin vältetään ääretön trigger-loop. ## Race condition `dispatch-workflow.sh` tunnistaa jokaisen dispatchatun runin uniikilla `dispatch_id`-tunnisteella `display_title`-kentästä. Vaikka useampi artifakti dispatchaisi samaan aikaan ja useita workflow-runeja olisi käynnissä rinnakkain, jokainen skripti löytää oikean runinsa. ## Sääntöjä 1. **Token ei kirjoita code repoon.** GitOps-repon workflow ei tarvitse oikeuksia code repoon. Kaikki status-kutsut kohdistuvat vain GitOps-repoon. Code repo asettaa oman statusnsa itse. 2. **Ei provider-workflowta.** GitOps-päivitys ei ole reusable workflow. GitOps-repo ajaa `scripts/gitops-update.sh`:n suoraan. 3. **Vain `workflow_dispatch`.** GitOps-repon workflow:ta ei triggeröidä pushista — se laukeaa vain dispatch-kutsusta. 4. **Dispatch ei palauta tarkkaa SHA:**ta. Code repo ei tiedä GitOps- commitin SHA:ta ennen dispatch-valmistumista. Status asetetaan dispatchin exit-koodin perusteella, ei GitOps-commitin tiedoilla. 5. **`dispatch_id` on pakollinen kohde-workflow'ssa** — ilman sitä `dispatch-workflow.sh` ei löydä oikeaa runia moniajo-tilanteessa. 6. **`[skip ci]` commit-viestissä.** Pakollinen trigger-loopin estoon.