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
| Wersja | Data | Commit | Opis zmian |
|---|---|---|---|
| 0.1 | 2026-03-08 | 4d91f2c9 | Podstawa PROCES_TYPE — separacja CI od CD |
| 0.2 | 2026-03-08 | 1f482fec | glab release create z asset + CD download |
| 0.3 | 2026-03-08 | 708ef062 | Peł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: nevercommon/cd/rules/common.yml — deploy tylko gdy PROCES_TYPE == CD:
.cd.rules:
deploy:
- if: '$PROCES_TYPE == "CD"'
when: on_success
- when: neverWynik — co uruchamia się kiedy
| Trigger | PROCES_TYPE | WORKFLOW_TYPE | Joby które działają |
|---|---|---|---|
| commit/MR na branch | CI | snapshot | prepare→…→package, publish:snapshot |
| commit na main/release/* | CI | release | prepare→…→release-notes (z tagiem z publish:release) |
git tag v1.2.3 | CD | release | tylko 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 packageSkrypty 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 assetdeploy (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/policyKaż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.shwbefore_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.shirelease-notes.sh(glab w BUILDER_IMAGE?) - Cache zależności (
.go-cache/) — weryfikacja działania na runnerze - Merge
lab/experiment-tag→mainpo 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
- Zespol-agentow — Stefan 🔍 monitoruje pipeline tego projektu
- 2026-03-08-pipeline-postmortem — kontekst skąd się wzięły prace
- 2026-03-04-agenci-devops-coding — geneza centralnego pipeline
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ść:
| Job | Rola |
|---|---|
publish:release | Upload binarki do Generic Package Registry (curl PUT) |
release-notes | Tworzy 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
fiNie 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-certificatesDział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)
go 1.22vsgo 1.23— gin-contrib/sse wymaga >= 1.23go mod downloadfail — brak GONOSUMCHECK w prywatnej siecigo mod tidywymagane po zmianie wersjino Go files— main wcmd/api/nie w rocie- Lab CA — kontenery nie ufają self-signed certom
coverage.outnie przekazywane między jobamigit describezwraca stary tag- YAML multiline —
Build:parsowane jako klucz YAML glabnie zainstalowany w release-notesJOB_TOKENbez uprawnień do tagów → PAT jako CI variableglab --patternnieznana flaga w 1.46.1APP_NAME=appnadpisywane 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.
| Rola | Narzędzie |
|---|---|
| Monitoring | Orwil → exec + process poll |
| Analiza jakości | Stefan → 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)
| SHA | Opis |
|---|---|
10e43bbd | golang/main.yml — include CI+CD, stage deploy |
74b79072 | go mod tidy + GOFLAGS=-mod=mod |
ee9e7691 | package.sh — auto-detect cmd/ |
9f2a089c | unit-test artifact coverage.out |
17aab116 | publish-release → Package Registry, release-notes → tag+release |
b84be3a7 | Lab CA w before_script_executor |
90f995bc | release-notes przez GitLab REST API |
a2b187ab | prepare — CI_COMMIT_TAG zamiast git describe |
23f665d1 | deploy — curl z Package Registry |
62b66e96 | usuń 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)

Widok pipeline’ów projektu platform/biz/kunegunda/api — pierwsze testy separacji CI od CD:
- #248 ✅
v1.1.0(tag) — CD pipeline z jednym jobdeploy— sukces w 4 sekundy - #247 ✅
main(branch) — CI pipeline z 7 stage’ami — sukces w 40 sekund - #245 ❌
main— 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 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)

Widok 88 pipeline’ów — wyraźnie widać wzorzec iteracyjnych napraw:
| Pipeline | Ref | Status | Czas |
|---|---|---|---|
| #298 | v0.1.87 🏷️ | ✅ Passed | 6s |
| #297 | main | ✅ Passed | 5:02 |
| #295 | v0.1.85 🏷️ | ❌ Failed | 5s |
| #293 | main | ✅ Passed | 4:54 |
| #291 | v0.1.83 🏷️ | ❌ Failed | 5s |
| #290 | main | ✅ Passed | 4:57 |
| #288 | v0.1.81 🏷️ | ❌ Failed | 5s |
| #287 | main | ✅ 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)

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)

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
-
JOB_TOKEN≠PRIVATE_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. -
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). -
Subagenty nie trzymają pętli — Stefan przeprojektowany na analityka zakończonych pipeline. Orwil monitoruje przez
exec+process poll. -
git describezwraca stary tag — nie używać dla auto-wersjonowania. UżywaćCI_COMMIT_TAG(CD) lubCI_PIPELINE_IID(CI).
