Centralny pipeline CI/CD

Repozytorium: platform/arch/cicd/pipeline na gitlab.internal
Aktywny branch: lab/experiment-tag
Projekt konsumencki (test): platform/biz/kunegunda/api (ID: 3)


Historia wersji dokumentu

WersjaDataCommitOpis zmian
0.12026-03-084d91f2c9Podstawa PROCES_TYPE — separacja CI od CD
0.22026-03-081f482fecglab release create z asset + CD download
0.32026-03-08708ef062Pełny wsad golang (go 1.23, skrypty CI/CD)

Diagram procesu

Przepływ pipeline — CI vs CD

flowchart TD
    DEV([👨‍💻 Developer]) -->|git push branch/MR| TRIGGER_CI
    DEV -->|git tag vX.Y.Z| TRIGGER_CD

    TRIGGER_CI([commit / MR]) --> WF_CI
    TRIGGER_CD([tag vX.Y.Z]) --> WF_CD

    WF_CI["🔀 workflow rules
    PROCES_TYPE = CI
    WORKFLOW_TYPE = snapshot / release"]

    WF_CD["🔀 workflow rules
    PROCES_TYPE = CD
    WORKFLOW_TYPE = release"]

    subgraph CI ["Pipeline CI — PROCES_TYPE=CI"]
        direction TB
        P[prepare
📄 prepare.env
APP_VERSION, APP_NAME]
        D[dependency
go mod download]
        B[build
go vet + go build]
        UT[unit-test
go test -race -cover]
        CR[coverage-report
go tool cover]
        SQ[sonarqube
🔍 analiza jakości]
        PK[package
CGO_ENABLED=0 binary
dist/app-vX.Y.Z.tar.gz]
        PS[publish:snapshot
📦 snapshot]
        PR["publish:release
🏷️ glab tag create vX.Y.Z
(WORKFLOW_TYPE=release)"]
        RN["release-notes
📋 glab release create
+ upload asset"]

        P --> D --> B --> UT --> CR
        B --> PK
        UT --> SQ
        PK --> PS
        PK --> PR --> RN
    end

    subgraph CD ["Pipeline CD — PROCES_TYPE=CD"]
        direction TB
        DP["deploy
⬇️ glab release download
📦 rozpakuj asset
🚀 deploy"]
    end

    WF_CI --> CI
    WF_CD --> CD

    subgraph MONITOR ["🔍 Stefan — monitoring"]
        ST1[sprawdza co 60s]
        ST2{pipeline
skończony?}
        ST3[raport do Orwila]
        ST1 --> ST2
        ST2 -->|nie| ST1
        ST2 -->|tak| ST3
    end

    CI -.->|Stefan obserwuje| MONITOR
    CD -.->|Stefan obserwuje| MONITOR

    style CI fill:#1b5e20,stroke:#69f0ae,color:#fff
    style CD fill:#0d47a1,stroke:#64b5f6,color:#fff
    style MONITOR fill:#bf360c,stroke:#ffab40,color:#fff

Agenci i ich role

flowchart LR
    subgraph ORWIL ["🐹 Orwil — orchestrator"]
        O1[konfiguruje pipeline
w common/]
        O2[tworzy agentów
& skille]
        O3[merguje zmiany
do main]
    end

    subgraph KLAUDYNKA ["🤖 Klaudynka — ACP coding agent"]
        K1[implementuje
skrypty CI/CD]
        K2[naprawia błędy
w skryptach]
        K3[technology/golang/
scripts/]
    end

    subgraph STEFAN ["🔍 Stefan — pipeline inspector"]
        S1[monitoruje pipeline
co 60 sekund]
        S2[wykrywa anomalie
CI/CD]
        S3["raportuje:
✅ success
❌ failed
⚠️ anomalia"]
    end

    subgraph PIPELINE ["🏭 GitLab CI/CD"]
        GL[gitlab.internal
platform/arch/cicd/pipeline]
        BR[branch lab/experiment-tag]
        CONS[projekt konsumencki
platform/biz/kunegunda/api]
    end

    ORWIL -->|projektuje architekturę| PIPELINE
    KLAUDYNKA -->|implementuje kod| PIPELINE
    STEFAN -->|obserwuje| PIPELINE
    PIPELINE -->|wyniki| STEFAN
    STEFAN -->|raport| ORWIL
    ORWIL -->|zleca poprawki| KLAUDYNKA

    style ORWIL fill:#880e4f,stroke:#f48fb1,color:#fff
    style KLAUDYNKA fill:#4a148c,stroke:#ce93d8,color:#fff
    style STEFAN fill:#bf360c,stroke:#ffab40,color:#fff
    style PIPELINE fill:#1a237e,stroke:#7986cb,color:#fff

Tagi git — kto co robi

sequenceDiagram
    actor OW as 🐹 Orwil
    participant GL as GitLab CI
    actor ST as 🔍 Stefan
    actor KL as 🤖 Klaudynka

    Note over OW: commit na main/release/*
    OW->>GL: git push (WORKFLOW_TYPE=release)
    GL->>GL: CI: prepare→…→package
    GL->>GL: publish:release → glab tag create vX.Y.Z
    GL->>GL: release-notes → glab release create + asset

    ST->>GL: sprawdź pipeline co 60s
    GL-->>ST: status jobów
    ST-->>OW: ✅ Pipeline sukces / ❌ failed + logi

    alt Pipeline failed
        OW->>KL: zlec poprawkę błędu
        KL->>GL: commit fix
        GL->>GL: nowy pipeline CI
        ST->>GL: monitoruj nowy pipeline
        ST-->>OW: raport
    end

    Note over GL: tag vX.Y.Z → PROCES_TYPE=CD
    GL->>GL: CD: deploy ← glab release download
    ST->>GL: monitoruj pipeline CD
    ST-->>OW: ✅ Deploy zakończony

Cel projektu

Repozytorium definiuje wzorcowy, generyczny proces CI/CD dla GitLab. Nie jest to pipeline konkretnej aplikacji — to szablon includowany przez projekty konsumenckie.

Co rozwiązuje:

  • Standaryzacja CI/CD w organizacji — jeden zestaw reguł dla wszystkich projektów
  • Separacja orkiestracji (process.yml) od implementacji (skrypty per technologia)
  • Walidacja struktury pipeline przez policy-as-code (OPA Rego + conftest)
  • Czytelny podział CI (build/test) od CD (deploy) przez zmienną PROCES_TYPE

Architektura — warstwy

Projekt konsumencki (.gitlab-ci.yml)
         │
         ▼
technology/<lang>/main.yml        ← punkt wejścia per technologia
         │                           (spec inputs: go_version, app_name, env)
         ▼
common/process.yml                ← orkiestrator CI
         │                           (stages, workflow, include szablonów)
         ├── common/workflow/common.yml    ← .common.workflow (PROCES_TYPE, WORKFLOW_TYPE)
         ├── common/rules/common.yml      ← .common.rules (kiedy job się uruchamia)
         ├── common/needs/common.yml      ← .common.needs (graf DAG zależności)
         ├── common/variables/common.yml  ← .common.variables (zmienne per job)
         ├── common/helpers/*.yml         ← logger.sh, executors
         ├── common/scripts/*.sh.yml      ← implementacje skryptów per job
         └── common/jobs/*.yml            ← kontrakty jobów (extends + !reference)

Gdy projekt chce też CD (deploy przy tagu):

technology/<lang>/main.yml  →  common/cd/process.yml
                                      │
                                      ├── include: common/process.yml  (CI bez zmian)
                                      ├── common/cd/rules/common.yml   (.cd.rules)
                                      ├── common/cd/scripts/deploy.sh.yml
                                      └── common/cd/jobs/deploy.yml    (.deploy)

Separacja CI od CD — mechanizm PROCES_TYPE

Problem

GitLab CI nie pozwala na warunkowe includowanie plików. Nie można powiedzieć “includuję joby CI tylko jeśli to nie jest tag”. Rozwiązanie: zmienna PROCES_TYPE ustawiana w workflow rules, która blokuje joby przez when: never.

Implementacja

common/workflow/common.yml — PROCES_TYPE ustawiany jako pierwsza reguła:

.common.workflow:
  # Tag → CD (tylko deploy, brak CI)
  - if: '$CI_COMMIT_TAG'
    variables:
      PROCES_TYPE: "CD"
      WORKFLOW_TYPE: "release"
 
  # MR do release brancha → CI snapshot
  - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ $RELEASE_BRANCH_REGEXP'
    variables:
      PROCES_TYPE: "CI"
      WORKFLOW_TYPE: "snapshot"
 
  # Commit na release branchu (main, release/*) → CI release
  - if: '$CI_COMMIT_BRANCH =~ $RELEASE_BRANCH_REGEXP && $CI_OPEN_MERGE_REQUESTS == null'
    variables:
      PROCES_TYPE: "CI"
      WORKFLOW_TYPE: "release"
 
  # Pozostałe → CI snapshot
  - if: '$CI_OPEN_MERGE_REQUESTS == null'
    variables:
      PROCES_TYPE: "CI"
      WORKFLOW_TYPE: "snapshot"

common/rules/common.yml — każdy CI job blokowany gdy PROCES_TYPE != CI:

.common.rules:
  prepare:
    - if: '$PROCES_TYPE == "CI"'
      when: always
    - when: never
 
  build:
    - if: '$PROCES_TYPE == "CI"'
      when: on_success
    - when: never
 
  publish:release:
    - if: '$PROCES_TYPE == "CI" && $WORKFLOW_TYPE == "release"'
      when: on_success
    - when: never
 
  release-notes:
    - if: '$PROCES_TYPE == "CI" && $WORKFLOW_TYPE == "release"'
      when: on_success
    - when: never

common/cd/rules/common.yml — deploy tylko gdy PROCES_TYPE == CD:

.cd.rules:
  deploy:
    - if: '$PROCES_TYPE == "CD"'
      when: on_success
    - when: never

Wynik — co uruchamia się kiedy

TriggerPROCES_TYPEWORKFLOW_TYPEJoby które działają
commit/MR na branchCIsnapshotprepare→…→package, publish:snapshot
commit na main/release/*CIreleaseprepare→…→release-notes (z tagiem z publish:release)
git tag v1.2.3CDreleasetylko deploy

Test na platform/biz/kunegunda/api

Pipeline #247 (main, commit):
  prepare ✅  dependency ✅  build ✅  unit-test ✅  ...  release-notes ✅
  deploy → niewidoczny (when: never przy PROCES_TYPE=CI)

Pipeline #248 (tag v1.1.0):
  deploy ✅  (log: "jestem w deploy", "Tag: v1.1.0")
  prepare/build/test → niewidoczne (when: never przy PROCES_TYPE=CD)

Kontrakt joba — struktura

Każdy job w common/jobs/ jest kontraktem który deklaruje sekcje przez !reference:

.build:
  variables: !reference [.common.variables, build]
  rules:     !reference [.common.rules, build]
  needs:     !reference [.common.needs, build]
  artifacts: !reference [.common.artifacts, build]
  cache:     !reference [.common.cache, build]
  before_script:
    - !reference [.common.logger.sh]
    - !reference [.common.before_script_executor.sh]
  script:    !reference [.build.sh]
  after_script:
    - !reference [.common.logger.sh]
    - !reference [.common.after_script_executor.sh]

Technologia (np. golang) nadpisuje klucz skryptu definiując własny .build.sh w technology/golang/scripts/build.sh.yml. GitLab CI bierze ostatnią definicję klucza.


Wsad golang — technology/golang/

Pełna implementacja CI/CD dla języka Go.

technology/golang/main.yml — spec inputs

spec:
  inputs:
    go_version:  { default: '1.23' }
    app_name:    { default: 'app' }
    environment: { options: [development, staging, production] }
 
include:
  - local: common/process.yml
  - local: technology/golang/scripts/prepare.sh.yml
  - local: technology/golang/scripts/dependency.sh.yml
  # ... wszystkie skrypty
 
variables:
  BUILDER_IMAGE: golang:$[[ inputs.go_version ]]   # Debian-based (ma bash!)
  APP_NAME: $[[ inputs.app_name ]]
  GLAB_VERSION: "1.46.1"
 
# Overrides
prepare:
  artifacts:
    reports: { dotenv: prepare.env }    # eksportuje APP_VERSION, APP_NAME
 
package:
  artifacts:
    paths: [dist/]
    expire_in: 1 week
 
release-notes:
  needs: [prepare, package, publish:release]   # dist/ z package

Skrypty golang — przepływ danych

prepare.sh    →  prepare.env (dotenv)
                  APP_VERSION=1.2.3
                  APP_NAME=kunegunda-api
                       │
                       ▼
dependency.sh →  go mod download (cache: .go-cache/pkg/mod/)
                       │
                       ▼
build.sh      →  go vet + go build ./...
                       │
                       ▼
unit-test.sh  →  go test -race -cover → coverage.out
                       │
                       ▼
package.sh    →  CGO_ENABLED=0 go build → dist/app
                  tar czf dist/app-v1.2.3.tar.gz
                       │
                       ▼ (WORKFLOW_TYPE=release)
publish:release.sh  →  glab tag create "v${APP_VERSION}"
                       │
                       ▼
release-notes.sh    →  glab release create "v${APP_VERSION}"
                         upload: dist/app-v${APP_VERSION}.tar.gz

──────────── (osobny pipeline po tagu) ────────────

deploy.sh     →  glab release download "v${APP_VERSION}"
                  tar tzf dist/app-v${APP_VERSION}.tar.gz
                  [deploy]

Release assets — przepływ glab

publish:release (CI, WORKFLOW_TYPE=release) zakłada tag przez glab tag create:

glab tag create "v${APP_VERSION}" \
  --message "Release v${APP_VERSION}" \
  --ref "${CI_COMMIT_SHA}"

release-notes (CI) tworzy GitLab Release z assetem:

glab release create "${TAG}" \
  --name "Release ${TAG}" \
  --notes "${RELEASE_NOTES}" \
  --ref "${TAG}" \
  "${ARCHIVE}#${BINARY}-${TAG}.tar.gz"   # named asset

deploy (CD, przy kolejnym pipeline na tagu) pobiera asset:

glab release download "${TAG}" \
  --pattern "${APP_NAME}-${TAG}.tar.gz" \
  --dir "${DEPLOY_DIR}"

Policy-as-code — walidacja struktury

Polityki Rego w tests/policy/jobs.rego walidują każdy plik joba:

# Walidacja wszystkich jobów
conftest test -p tests/policy --combine common/jobs/*.yml
 
# Testy polityk (unit testy Rego)
conftest verify -p tests/policy

Każdy job jest sprawdzany pod kątem:

  • Obecności wymaganych sekcji (variables, rules, needs, script, etc.)
  • Poprawności !reference (klucz + zagnieżdżenie)
  • Obecności helpera .common.logger.sh w before_script

Projekt konsumencki — minimalny .gitlab-ci.yml

Tylko CI

include:
  - project: 'platform/arch/cicd/pipeline'
    file: '/technology/golang/main.yml'
    ref: main
    inputs:
      go_version: '1.23'
      app_name: 'moja-api'
      environment: development
 
variables:
  FF_SCRIPT_SECTIONS: true
  RELEASE_BRANCH_REGEXP: '/^(master|main|release/.*)$/'

CI + CD (deploy przy tagu)

include:
  - project: 'platform/arch/cicd/pipeline'
    file: '/common/cd/process.yml'
    ref: main
    inputs:
      go_version: '1.23'
      app_name: 'moja-api'
      environment: production
 
variables:
  FF_SCRIPT_SECTIONS: true
  RELEASE_BRANCH_REGEXP: '/^(master|main|release/.*)$/'

Otwarte punkty i TODO

  • Pełna implementacja publish:release.sh i release-notes.sh (glab w BUILDER_IMAGE?)
  • Cache zależności (.go-cache/) — weryfikacja działania na runnerze
  • Merge lab/experiment-tagmain po zatwierdzeniu
  • Dodać technology/nodejs/ analogicznie do golang (patrz .llm/migracja-npm.md)
  • OPA policy dla jobów CD (common/cd/jobs/)
  • Dokumentacja dla nowych technologii (.llm/ per technologia)

Powiązane


Sesja 2026-03-08 — Implementacja Golang pipeline

Cel

Zbudowanie pełnego pipeline CI/CD dla Go (platform/biz/kunegunda/api) działającego end-to-end: CI na main → CD na tagu.

Kluczowe decyzje architektoniczne

Podział publish:release vs release-notes

To była najważniejsza decyzja sesji — każdy job ma jedną odpowiedzialność:

JobRola
publish:releaseUpload binarki do Generic Package Registry (curl PUT)
release-notesTworzy git tag (API) + GitLab Release z linkiem do pakietu

Wersjonowanie automatyczne

# prepare.sh — logika wyboru wersji
if [ -n "${CI_COMMIT_TAG}" ]; then
  VERSION="${CI_COMMIT_TAG#v}"    # CD: bierz z taga
else
  VERSION="0.1.${CI_PIPELINE_IID}"  # CI: auto-numer
fi

Nie używamy git describe — może zwrócić stary tag i spowodować konflikty.

Lab Root CA — automatyczna instalacja

Zamiast modyfikować każdy skrypt, CA jest instalowany w before_script_executor.sh:

curl -sSLk ca.internal:9000/roots.pem /usr/local/share/ca-certificates/lab-step-ca.crt
update-ca-certificates

Działa dla Debian/Ubuntu/Alpine/RHEL. Źródło: homelab-acme-step-ca.

Deploy — Package Registry zamiast glab

glab release download --pattern nie istnieje w v1.46.1. Deploy pobiera przez curl:

curl -k -f -H "JOB-TOKEN: ${CI_JOB_TOKEN}" \
  "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${BINARY}/${VERSION}/${FILENAME}" \
  -o "${DEPLOY_DIR}/${FILENAME}"

Napotkane błędy (12 iteracji)

  1. go 1.22 vs go 1.23 — gin-contrib/sse wymaga >= 1.23
  2. go mod download fail — brak GONOSUMCHECK w prywatnej sieci
  3. go mod tidy wymagane po zmianie wersji
  4. no Go files — main w cmd/api/ nie w rocie
  5. Lab CA — kontenery nie ufają self-signed certom
  6. coverage.out nie przekazywane między jobami
  7. git describe zwraca stary tag
  8. YAML multiline — Build: parsowane jako klucz YAML
  9. glab nie zainstalowany w release-notes
  10. JOB_TOKEN bez uprawnień do tagów → PAT jako CI variable
  11. glab --pattern nieznana flaga w 1.46.1
  12. APP_NAME=app nadpisywane przez cd/variables

Przeprojektowanie Stefana 🔍

Przed: Stefan próbował monitorować pipeline w pętli (subagenty nie trzymają pętli).
Po: Stefan = analityk zakończonych pipeline. Orwil monitoruje, Stefan analizuje.

RolaNarzędzie
MonitoringOrwil → exec + process poll
Analiza jakościStefan → pobiera logi, porównuje z wzorcem

Wyniki

  • ✅ CI #284, #287 — pełny sukces (9/9 jobów)
  • ✅ CD auto-triggerowanie z CI (tag wygenerowany przez release-notes)
  • ⏳ CD deploy — w trakcie naprawy (APP_NAME, curl zamiast glab)

Nowe commity (branch lab/experiment-tag)

SHAOpis
10e43bbdgolang/main.yml — include CI+CD, stage deploy
74b79072go mod tidy + GOFLAGS=-mod=mod
ee9e7691package.sh — auto-detect cmd//
9f2a089cunit-test artifact coverage.out
17aab116publish-release → Package Registry, release-notes → tag+release
b84be3a7Lab CA w before_script_executor
90f995bcrelease-notes przez GitLab REST API
a2b187abprepare — CI_COMMIT_TAG zamiast git describe
23f665d1deploy — curl z Package Registry
62b66e96usuń APP_NAME z cd/variables

Pełny log: 2026-03-08-pipeline-golang


Wyniki sesji 2026-03-08 — dokumentacja wizualna

Przebieg pipeline’ów — iteracje naprawcze

Screenshoty z sesji dokumentują całą ścieżkę od pierwszych błędów do pełnego sukcesu.

Screenshot 1 — Pierwsze testy PROCES_TYPE (12:29 UTC)

Pipeline #247 i #248 — pierwsze testy CI/CD (12:29 UTC)

Widok pipeline’ów projektu platform/biz/kunegunda/api — pierwsze testy separacji CI od CD:

  • #248v1.1.0 (tag) — CD pipeline z jednym job deploysukces w 4 sekundy
  • #247main (branch) — CI pipeline z 7 stage’ami — sukces w 40 sekund
  • #245main — wcześniejsza iteracja z błędem konfiguracji

To był moment weryfikacji że mechanizm PROCES_TYPE=CI/CD działa poprawnie.


Screenshot 2 — Dashboard OpenClaw (ok. 13:17 UTC)

Dashboard OpenClaw — Stefan i Orwil w pracy (13:17 UTC)

Dashboard agentów podczas pracy — widoczne:

  • Orwil 🐹 — główna sesja, monitorowanie pipeline
  • Stefan 🔍 — „Inspektor Pipeline CI/CD” aktywnie pracujący
  • Aleksander — status Drained 🔥 (długa sesja debugowania)

Screenshot 3 — Historia pipeline’ów kunegunda-api (21:54 UTC)

Historia pipeline'ów kunegunda-api — iteracje v0.1.81→v0.1.87 (21:54 UTC)

Widok 88 pipeline’ów — wyraźnie widać wzorzec iteracyjnych napraw:

PipelineRefStatusCzas
#298v0.1.87 🏷️✅ Passed6s
#297main✅ Passed5:02
#295v0.1.85 🏷️❌ Failed5s
#293main✅ Passed4:54
#291v0.1.83 🏷️❌ Failed5s
#290main✅ Passed4:57
#288v0.1.81 🏷️❌ Failed5s
#287main✅ Passed

Wzorzec: Każdy CI na main triggerował automatycznie CD na tagu. Tagi v0.1.81→v0.1.83→v0.1.85 failowały (naprawy deploy.sh). Dopiero v0.1.87 — pełny sukces CD.

Każdy sukces CI + sukces CD = dowód działającego pełnego flow.


Screenshot 4 — GitLab Release v0.1.87 (21:54 UTC)

GitLab Release v0.1.87 — assets i evidence (21:54 UTC)

Strona Releases projektu kunegunda-api — release v0.1.87:

  • 5 assets — 4 source code + 1 paczka binarna kunegunda-api-v0.1.87.tar.gz
  • Release notes: Release v0.1.87 | Build: 87 | Commit: d4f14e65
  • Evidence collection — automatyczne v0.1.87-evidences-6.json
  • Paczka wskazuje na Generic Package Registry

Screenshot 5 — Konfiguracja asset Release (21:54 UTC)

Konfiguracja asset Release — link do Package Registry (21:54 UTC)

Formularz asset linku w release v0.1.87:

  • URL: https://gitlab.internal/api/v4/projects/3/packages/generic/...
  • Tytuł: kunegunda-api-v0.1.87.tar.gz
  • Typ: Package

Potwierdza że release-notes prawidłowo linkuje do Generic Package Registry.


Końcowy stan po sesji

Pełny flow CI→CD działa end-to-end:

git push → CI #297 ✅ (9/9 jobów, 5:02)
  ↓ release-notes tworzy tag v0.1.87
  ↓ GitLab auto-triggeruje CD pipeline
CD #298 ✅ (deploy, 0:06)
  ↓ curl pobiera kunegunda-api-v0.1.87.tar.gz z Package Registry
  ↓ Deploy complete

Kluczowe metryki:

  • Czas CI: ~5 minut
  • Czas CD: ~6 sekund
  • Package Registry: plik dostępny pod /api/v4/projects/3/packages/generic/kunegunda-api/0.1.87/
  • GitLab Release: strona z asset linkiem, evidence, release notes

Wnioski architektoniczne

  1. JOB_TOKENPRIVATE_TOKEN — JOB_TOKEN nie ma domyślnie uprawnień do Package Registry ani do tworzenia tagów. Wymagany personal access token (GITLAB_RELEASE_TOKEN) jako masked CI variable.

  2. Nazwy kluczy YAML muszą być spójne!reference [.publish:release.sh] w job definition musi pasować do /.publish:release.sh: w skrypcie. Myślnik vs dwukropek — GitLab cicho ignoruje brakującą referencję (job jest “success” ale nic nie robi).

  3. Subagenty nie trzymają pętli — Stefan przeprojektowany na analityka zakończonych pipeline. Orwil monitoruje przez exec + process poll.

  4. git describe zwraca stary tag — nie używać dla auto-wersjonowania. Używać CI_COMMIT_TAG (CD) lub CI_PIPELINE_IID (CI).