mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-04-15 01:41:56 +00:00
Merge remote-tracking branch 'upstream/main' into integration-tests
This commit is contained in:
+21
-15
@@ -28,14 +28,15 @@
|
||||
"Bluesky",
|
||||
"Boguslawski",
|
||||
"bradrydzewski",
|
||||
"buildkit",
|
||||
"BUILDPLATFORM",
|
||||
"buildx",
|
||||
"buildkit",
|
||||
"caddyfile",
|
||||
"ccmenu",
|
||||
"CERTDIR",
|
||||
"certmagic",
|
||||
"charmbracelet",
|
||||
"checkmake",
|
||||
"cicd",
|
||||
"ciphertext",
|
||||
"Cloudron",
|
||||
@@ -48,6 +49,7 @@
|
||||
"datacenter",
|
||||
"DATASOURCE",
|
||||
"Debugf",
|
||||
"dejavusans",
|
||||
"Demilestoned",
|
||||
"desaturate",
|
||||
"devx",
|
||||
@@ -119,15 +121,16 @@
|
||||
"mapstructure",
|
||||
"markdownlint",
|
||||
"mdbook",
|
||||
"Milestoned",
|
||||
"memswap",
|
||||
"Metas",
|
||||
"mhmxs",
|
||||
"Milestoned",
|
||||
"moby",
|
||||
"Msgf",
|
||||
"mstruebing",
|
||||
"multiarch",
|
||||
"multierr",
|
||||
"narqo",
|
||||
"netdns",
|
||||
"Netrc",
|
||||
"Nextcloud",
|
||||
@@ -142,6 +145,7 @@
|
||||
"octocat",
|
||||
"openapi",
|
||||
"opensource",
|
||||
"opentype",
|
||||
"Pacman",
|
||||
"picus",
|
||||
"Pinia",
|
||||
@@ -175,6 +179,7 @@
|
||||
"secprofile",
|
||||
"selfhosted",
|
||||
"sess",
|
||||
"sfnt",
|
||||
"shellescape",
|
||||
"sigstore",
|
||||
"Sonatype",
|
||||
@@ -208,8 +213,8 @@
|
||||
"unsanitize",
|
||||
"Upsert",
|
||||
"urfave",
|
||||
"useragent",
|
||||
"usecase",
|
||||
"useragent",
|
||||
"varchar",
|
||||
"varz",
|
||||
"Vieter",
|
||||
@@ -238,32 +243,33 @@
|
||||
"zerologger"
|
||||
],
|
||||
"ignorePaths": [
|
||||
"*.excalidraw",
|
||||
"*.svg",
|
||||
"*_test.go",
|
||||
".cspell.json",
|
||||
".git/**/*",
|
||||
".gitignore",
|
||||
".golangci.yaml",
|
||||
".vscode/extensions.json",
|
||||
"*_test.go",
|
||||
"*.excalidraw",
|
||||
"*.svg",
|
||||
"**/*.pb.go",
|
||||
"**/fixtures/**",
|
||||
"**/testdata/**",
|
||||
"CHANGELOG.md",
|
||||
"Makefile",
|
||||
"docs/versioned_docs/",
|
||||
"flake.nix",
|
||||
"go.mod",
|
||||
"**/*.pb.go",
|
||||
"Makefile",
|
||||
"package.json",
|
||||
"server/store/datastore/migration/**/*",
|
||||
"web/components.d.ts",
|
||||
"web/src/assets/locales/**/*",
|
||||
"**/fixtures/**",
|
||||
"**/testdata/**",
|
||||
"docs/versioned_docs/",
|
||||
"package.json",
|
||||
// generated
|
||||
"go.sum",
|
||||
"flake.lock",
|
||||
"pnpm-lock.yaml",
|
||||
"**/mocks/**",
|
||||
"**/node_modules/**/*",
|
||||
"cmd/server/openapi/docs.go",
|
||||
"flake.lock",
|
||||
"go.sum",
|
||||
"pnpm-lock.yaml",
|
||||
"renovate.json",
|
||||
// TODO: remove the following
|
||||
"docs/**/*.js",
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ tasks:
|
||||
WOODPECKER_BACKEND_DOCKER_NETWORK: ci_default
|
||||
init: |
|
||||
# renovate: datasource=golang-version depName=golang
|
||||
GO_VERSION=1.25.5
|
||||
GO_VERSION=1.26.0
|
||||
rm -rf ~/go
|
||||
curl -fsSL https://dl.google.com/go/go$GO_VERSION.linux-amd64.tar.gz | tar xzs -C ~/
|
||||
go mod tidy
|
||||
|
||||
+33
-14
@@ -49,10 +49,10 @@ linters:
|
||||
agent:
|
||||
list-mode: lax
|
||||
files:
|
||||
- '**/agent/**/*.go'
|
||||
- '**/agent/*.go'
|
||||
- '**/cmd/agent/**/*.go'
|
||||
- '**/agent/**/*.go'
|
||||
- '**/cmd/agent/*.go'
|
||||
- '**/cmd/agent/**/*.go'
|
||||
deny:
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cli
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cmd/cli
|
||||
@@ -63,70 +63,89 @@ linters:
|
||||
cli:
|
||||
list-mode: lax
|
||||
files:
|
||||
- '**/cli/**/*.go'
|
||||
- '**/cli/*.go'
|
||||
- '**/cmd/cli/**/*.go'
|
||||
- '**/cli/**/*.go'
|
||||
- '**/cmd/cli/*.go'
|
||||
- '**/cmd/cli/**/*.go'
|
||||
deny:
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/agent
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/server
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cmd/agent
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cmd/server
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/rpc
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/server
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/web
|
||||
pipeline:
|
||||
list-mode: lax
|
||||
files:
|
||||
- '**/pipeline/**/*.go'
|
||||
- '**/pipeline/*.go'
|
||||
- '!**/cli/pipeline/*.go'
|
||||
- '!**/cli/pipeline/**/*.go'
|
||||
- '!**/server/pipeline/*.go'
|
||||
- '!**/server/pipeline/**/*.go'
|
||||
- '**/pipeline/*.go'
|
||||
- '**/pipeline/**/*.go'
|
||||
deny:
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/agent
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cli
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cmd
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/rpc
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/server
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/web
|
||||
server:
|
||||
list-mode: lax
|
||||
files:
|
||||
- '**/server/**/*.go'
|
||||
- '**/server/*.go'
|
||||
- '**/cmd/server/**/*.go'
|
||||
- '**/cmd/server/*.go'
|
||||
- '**/web/**/*.go'
|
||||
- '**/cmd/server/**/*.go'
|
||||
- '**/server/*.go'
|
||||
- '**/server/**/*.go'
|
||||
- '**/web/*.go'
|
||||
- '**/web/**/*.go'
|
||||
deny:
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/agent
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cli
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cmd/agent
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cmd/cli
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker
|
||||
rpc:
|
||||
list-mode: lax
|
||||
files:
|
||||
- '!**/agent/rpc/*.go'
|
||||
- '!**/agent/rpc/**/*.go'
|
||||
- '!**/server/rpc/*.go'
|
||||
- '!**/server/rpc/**/*.go'
|
||||
- '**/rpc/*.go'
|
||||
- '**/rpc/**/*.go'
|
||||
deny:
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/agent
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cli
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cmd
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/server
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/web
|
||||
shared:
|
||||
list-mode: lax
|
||||
files:
|
||||
- '**/shared/**/*.go'
|
||||
- '**/shared/*.go'
|
||||
- '!**/pipeline/shared/*.go'
|
||||
- '!**/pipeline/shared/**/*.go'
|
||||
- '**/shared/*.go'
|
||||
- '**/shared/**/*.go'
|
||||
deny:
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/agent
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cli
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cmd
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/pipeline
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/rpc
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/server
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/web
|
||||
woodpecker-go:
|
||||
list-mode: lax
|
||||
files:
|
||||
- '**/woodpecker-go/woodpecker/**/*.go'
|
||||
- '**/woodpecker-go/woodpecker/*.go'
|
||||
- '**/woodpecker-go/woodpecker/**/*.go'
|
||||
deny:
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/agent
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cli
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/cmd
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/pipeline
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/rpc
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/server
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/shared
|
||||
- pkg: go.woodpecker-ci.org/woodpecker/v3/web
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ filename: mock_{{.InterfaceName}}.go
|
||||
pkgname: mocks
|
||||
recursive: true
|
||||
packages:
|
||||
go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc:
|
||||
go.woodpecker-ci.org/woodpecker/v3/rpc:
|
||||
config:
|
||||
recursive: false
|
||||
go.woodpecker-ci.org/woodpecker/v3/server/forge:
|
||||
|
||||
@@ -12,7 +12,7 @@ repos:
|
||||
- id: trailing-whitespace
|
||||
exclude: ^docs/versioned_docs/.+/40-cli.md$
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v2.7.2
|
||||
rev: v2.10.1
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
@@ -22,7 +22,7 @@ repos:
|
||||
exclude: '^(docs/versioned_docs/.*|CHANGELOG.md)$'
|
||||
language_version: 22.15.1
|
||||
- repo: https://github.com/mrtazz/checkmake
|
||||
rev: 0.2.2
|
||||
rev: v0.3.2
|
||||
hooks:
|
||||
- id: checkmake
|
||||
exclude: '^docker/Dockerfile.make$' # actually a Dockerfile and not a makefile
|
||||
@@ -31,11 +31,11 @@ repos:
|
||||
hooks:
|
||||
- id: hadolint
|
||||
- repo: https://github.com/rbubley/mirrors-prettier
|
||||
rev: v3.7.4
|
||||
rev: v3.8.1
|
||||
hooks:
|
||||
- id: prettier
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.37.1
|
||||
rev: v1.38.0
|
||||
hooks:
|
||||
- id: yamllint
|
||||
args: [--strict, -c=.yamllint.yaml]
|
||||
|
||||
@@ -7,9 +7,9 @@ when:
|
||||
- .woodpecker/binaries.yaml
|
||||
|
||||
variables:
|
||||
- &golang_image 'docker.io/golang:1.25'
|
||||
- &golang_image 'docker.io/golang:1.26'
|
||||
- &node_image 'docker.io/node:24-alpine'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.25.x'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.26.x'
|
||||
|
||||
# cspell:words bindata netgo
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
variables:
|
||||
- &golang_image 'docker.io/golang:1.25'
|
||||
- &golang_image 'docker.io/golang:1.26'
|
||||
- &node_image 'docker.io/node:24-alpine'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.25.x'
|
||||
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:6.0.3'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.26.x'
|
||||
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:6.0.4'
|
||||
- &platforms_release 'linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/386,linux/amd64,linux/ppc64le,linux/riscv64,linux/s390x,freebsd/arm64,freebsd/amd64,openbsd/arm64,openbsd/amd64'
|
||||
- &platforms_server 'linux/arm/v7,linux/arm64/v8,linux/amd64,linux/ppc64le,linux/riscv64'
|
||||
- &platforms_preview 'linux/amd64'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
variables:
|
||||
- &golang_image 'docker.io/golang:1.25'
|
||||
- &golang_image 'docker.io/golang:1.26'
|
||||
- &node_image 'docker.io/node:24-alpine'
|
||||
- &alpine_image 'docker.io/alpine:3.23'
|
||||
- path: &when_path
|
||||
@@ -79,7 +79,7 @@ steps:
|
||||
- event: manual
|
||||
|
||||
- name: deploy-preview
|
||||
image: docker.io/woodpeckerci/plugin-surge-preview:1.4.1
|
||||
image: docker.io/woodpeckerci/plugin-surge-preview:1.4.2
|
||||
settings:
|
||||
path: 'docs/build/'
|
||||
surge_token:
|
||||
|
||||
@@ -4,7 +4,7 @@ when:
|
||||
|
||||
steps:
|
||||
- name: links
|
||||
image: docker.io/lycheeverse/lychee:0.22.0
|
||||
image: docker.io/lycheeverse/lychee:0.23.0
|
||||
failure: ignore
|
||||
depends_on: []
|
||||
commands:
|
||||
|
||||
@@ -6,7 +6,7 @@ when:
|
||||
|
||||
steps:
|
||||
- name: release-helper
|
||||
image: docker.io/woodpeckerci/plugin-ready-release-go:3.4.0
|
||||
image: docker.io/woodpeckerci/plugin-ready-release-go:3.4.1
|
||||
settings:
|
||||
release_branch: ${CI_COMMIT_BRANCH}
|
||||
forge_type: github
|
||||
|
||||
@@ -34,6 +34,7 @@ steps:
|
||||
services:
|
||||
server:
|
||||
image: *trivy_plugin
|
||||
failure: ignore # as we don't care about the exit code
|
||||
settings:
|
||||
service: true
|
||||
db-repository: mirror.gcr.io/aquasec/trivy-db:2
|
||||
|
||||
@@ -3,7 +3,7 @@ when:
|
||||
|
||||
steps:
|
||||
- name: lint-editorconfig
|
||||
image: docker.io/woodpeckerci/plugin-editorconfig-checker:0.3.2
|
||||
image: docker.io/woodpeckerci/plugin-editorconfig-checker:0.3.3
|
||||
depends_on: []
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
variables:
|
||||
- &golang_image 'docker.io/golang:1.25'
|
||||
- &golang_image 'docker.io/golang:1.26'
|
||||
- &when
|
||||
- path: &when_path # related config files
|
||||
- '.woodpecker/test.yaml'
|
||||
@@ -11,6 +11,7 @@ variables:
|
||||
- 'pipeline/schema/**'
|
||||
# tools updates
|
||||
- Makefile
|
||||
- 'codecov.yaml'
|
||||
event: pull_request
|
||||
|
||||
when:
|
||||
@@ -55,7 +56,7 @@ steps:
|
||||
lint:
|
||||
depends_on:
|
||||
- vendor
|
||||
image: golangci/golangci-lint:v2.7.2
|
||||
image: golangci/golangci-lint:v2.10.1
|
||||
commands:
|
||||
- make lint
|
||||
when: *when
|
||||
@@ -127,7 +128,7 @@ steps:
|
||||
- test
|
||||
- sqlite
|
||||
pull: true
|
||||
image: docker.io/woodpeckerci/plugin-codecov:2.1.6
|
||||
image: docker.io/woodpeckerci/plugin-codecov:2.2.2
|
||||
settings:
|
||||
files:
|
||||
- agent-coverage.out
|
||||
@@ -151,7 +152,7 @@ services:
|
||||
when: *when
|
||||
|
||||
mysql:
|
||||
image: docker.io/mysql:9.5.0
|
||||
image: docker.io/mysql:9.6.0
|
||||
ports: ['3306']
|
||||
environment:
|
||||
MYSQL_DATABASE: test
|
||||
|
||||
+171
@@ -1,5 +1,176 @@
|
||||
# Changelog
|
||||
|
||||
## [3.13.0](https://github.com/woodpecker-ci/woodpecker/releases/tag/v3.13.0) - 2026-01-14
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@6543, @Javex, @KhalidAlansary, @MartinSchmidt, @abhiyerra, @anbraten, @bentasker, @gjuoun, @gsaslis, @henkka, @jolheiser, @mogsie, @qwerty287, @sloonz, @sugar700, @tuxmainy, @xoxys
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- Update quic-go/qpack & quic-go/quic-go [[#5885](https://github.com/woodpecker-ci/woodpecker/pull/5885)]
|
||||
- fix: updateRepoPermissions to cleanup old permissions [[#5790](https://github.com/woodpecker-ci/woodpecker/pull/5790)]
|
||||
|
||||
### ✨ Features
|
||||
|
||||
- Add cli contexts [[#5929](https://github.com/woodpecker-ci/woodpecker/pull/5929)]
|
||||
|
||||
### 📈 Enhancement
|
||||
|
||||
- Allow to add a note to secrets [[#5898](https://github.com/woodpecker-ci/woodpecker/pull/5898)]
|
||||
- Log addon errors [[#5923](https://github.com/woodpecker-ci/woodpecker/pull/5923)]
|
||||
- Custom vars for crons [[#5897](https://github.com/woodpecker-ci/woodpecker/pull/5897)]
|
||||
- Allow to disable a cron [[#5896](https://github.com/woodpecker-ci/woodpecker/pull/5896)]
|
||||
- Add background to status icons [[#5880](https://github.com/woodpecker-ci/woodpecker/pull/5880)]
|
||||
- Fix dead page and cleanup router [[#5519](https://github.com/woodpecker-ci/woodpecker/pull/5519)]
|
||||
- feat(kubernetes): add support for pod affinity and anti-affinity configurations [[#5854](https://github.com/woodpecker-ci/woodpecker/pull/5854)]
|
||||
- Public key endpoint [[#5860](https://github.com/woodpecker-ci/woodpecker/pull/5860)]
|
||||
- Allow untrusted repo to still drop network for steps [[#5820](https://github.com/woodpecker-ci/woodpecker/pull/5820)]
|
||||
- Add support for headless Kubernetes services [[#5764](https://github.com/woodpecker-ci/woodpecker/pull/5764)]
|
||||
- server/forge: rename var to be more descriptive and test value [[#5806](https://github.com/woodpecker-ci/woodpecker/pull/5806)]
|
||||
- add events query parameter to badge url [[#5728](https://github.com/woodpecker-ci/woodpecker/pull/5728)]
|
||||
- Extract default step-builder options into server [[#5785](https://github.com/woodpecker-ci/woodpecker/pull/5785)]
|
||||
- feat: include CI_COMMIT_TAG env in deployment events [[#5773](https://github.com/woodpecker-ci/woodpecker/pull/5773)]
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Use repo-user for api call of cron [[#5967](https://github.com/woodpecker-ci/woodpecker/pull/5967)]
|
||||
- Close opened file on LogFind [[#5961](https://github.com/woodpecker-ci/woodpecker/pull/5961)]
|
||||
- Delete/Deactivate repo ignores missing repo at forge [[#5953](https://github.com/woodpecker-ci/woodpecker/pull/5953)]
|
||||
- Correctly update repo permissions [[#5928](https://github.com/woodpecker-ci/woodpecker/pull/5928)]
|
||||
- Revert repos pagination for GH and BB [[#5924](https://github.com/woodpecker-ci/woodpecker/pull/5924)]
|
||||
- fix: send correct argument to rpc call for name/url [[#5922](https://github.com/woodpecker-ci/woodpecker/pull/5922)]
|
||||
- fix: secrets-file flag [[#5909](https://github.com/woodpecker-ci/woodpecker/pull/5909)]
|
||||
- Do not run crons for disabled repos [[#5884](https://github.com/woodpecker-ci/woodpecker/pull/5884)]
|
||||
- Show warning if there is no workflow to run [[#5883](https://github.com/woodpecker-ci/woodpecker/pull/5883)]
|
||||
- fix(datastore): fix pagination bug in workflowsDelete skipping records [[#5881](https://github.com/woodpecker-ci/woodpecker/pull/5881)]
|
||||
- Remove rounded corners in fullscreen log view [[#5879](https://github.com/woodpecker-ci/woodpecker/pull/5879)]
|
||||
- Fix some ListItems and Queue view background in dark mode [[#5878](https://github.com/woodpecker-ci/woodpecker/pull/5878)]
|
||||
- Make disabled checkboxes match overall style [[#5869](https://github.com/woodpecker-ci/woodpecker/pull/5869)]
|
||||
- Fix CLI trusted updating [[#5861](https://github.com/woodpecker-ci/woodpecker/pull/5861)]
|
||||
- Send configuration as part of the request for external configuration [[#5831](https://github.com/woodpecker-ci/woodpecker/pull/5831)]
|
||||
- fix(bitbucketdatacenter): fix CI_COMMIT_PULL_REQUEST [[#5769](https://github.com/woodpecker-ci/woodpecker/pull/5769)]
|
||||
- On set/get of repo make sure forge_id is set and on fetch respected [[#5717](https://github.com/woodpecker-ci/woodpecker/pull/5717)]
|
||||
- Improve repair endpoints [[#5767](https://github.com/woodpecker-ci/woodpecker/pull/5767)]
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- chore(deps): lock file maintenance [[#5963](https://github.com/woodpecker-ci/woodpecker/pull/5963)]
|
||||
- chore(deps): update dependency @types/node to v24.10.7 [[#5954](https://github.com/woodpecker-ci/woodpecker/pull/5954)]
|
||||
- chore(deps): update dependency @types/react to v19.2.8 [[#5941](https://github.com/woodpecker-ci/woodpecker/pull/5941)]
|
||||
- chore(deps): update dependency @types/node to v24.10.6 [[#5935](https://github.com/woodpecker-ci/woodpecker/pull/5935)]
|
||||
- chore(deps): update dependency @types/node to v24.10.5 [[#5933](https://github.com/woodpecker-ci/woodpecker/pull/5933)]
|
||||
- fix(docs): update woodpecker-cli secret command [[#5927](https://github.com/woodpecker-ci/woodpecker/pull/5927)]
|
||||
- Update Docs and nix-flake to reflect current dev environment [[#5926](https://github.com/woodpecker-ci/woodpecker/pull/5926)]
|
||||
- Update Helm chart installation command [[#5872](https://github.com/woodpecker-ci/woodpecker/pull/5872)]
|
||||
- docs: add BunnyCDN Cache Purge Plugin [[#5906](https://github.com/woodpecker-ci/woodpecker/pull/5906)]
|
||||
- chore(deps): update dependency isomorphic-dompurify to v2.35.0 [[#5904](https://github.com/woodpecker-ci/woodpecker/pull/5904)]
|
||||
- chore(deps): update dependency @types/node to v24.10.4 [[#5862](https://github.com/woodpecker-ci/woodpecker/pull/5862)]
|
||||
- chore(deps): update docs npm deps non-major [[#5855](https://github.com/woodpecker-ci/woodpecker/pull/5855)]
|
||||
- chore(deps): update docs npm deps non-major [[#5829](https://github.com/woodpecker-ci/woodpecker/pull/5829)]
|
||||
- Update link for Codeberg Pages Deploy plugin [[#5811](https://github.com/woodpecker-ci/woodpecker/pull/5811)]
|
||||
- chore(deps): update dependency yaml to v2.8.2 [[#5803](https://github.com/woodpecker-ci/woodpecker/pull/5803)]
|
||||
- chore(deps): update dependency prettier to v3.7.3 [[#5799](https://github.com/woodpecker-ci/woodpecker/pull/5799)]
|
||||
- chore(deps): update docs npm deps non-major [[#5791](https://github.com/woodpecker-ci/woodpecker/pull/5791)]
|
||||
- chore(deps): update dependency isomorphic-dompurify to v2.33.0 [[#5778](https://github.com/woodpecker-ci/woodpecker/pull/5778)]
|
||||
- chore(deps): update docs npm deps non-major [[#5774](https://github.com/woodpecker-ci/woodpecker/pull/5774)]
|
||||
|
||||
### 📦️ Dependency
|
||||
|
||||
- fix(deps): update module gitlab.com/gitlab-org/api/client-go to v1.14.0 [[#5969](https://github.com/woodpecker-ci/woodpecker/pull/5969)]
|
||||
- fix(deps): update golang-packages [[#5966](https://github.com/woodpecker-ci/woodpecker/pull/5966)]
|
||||
- fix(deps): update module gitlab.com/gitlab-org/api/client-go to v1.12.0 [[#5962](https://github.com/woodpecker-ci/woodpecker/pull/5962)]
|
||||
- chore(deps): update dependency simple-icons to v16.5.0 [[#5957](https://github.com/woodpecker-ci/woodpecker/pull/5957)]
|
||||
- fix(deps): update golang-packages [[#5956](https://github.com/woodpecker-ci/woodpecker/pull/5956)]
|
||||
- chore(deps): update dependency @types/node to v24.10.7 [[#5955](https://github.com/woodpecker-ci/woodpecker/pull/5955)]
|
||||
- fix(deps): update module github.com/google/go-github/v80 to v81 [[#5946](https://github.com/woodpecker-ci/woodpecker/pull/5946)]
|
||||
- chore(deps): update woodpeckerci/plugin-git docker tag to v2.8.0 [[#5945](https://github.com/woodpecker-ci/woodpecker/pull/5945)]
|
||||
- chore(deps): update golangci/golangci-lint docker tag to v2.8.0 [[#5944](https://github.com/woodpecker-ci/woodpecker/pull/5944)]
|
||||
- chore(deps): update docker.io/woodpeckerci/plugin-codecov docker tag to v2.2.0 [[#5943](https://github.com/woodpecker-ci/woodpecker/pull/5943)]
|
||||
- chore(deps): update web npm deps non-major [[#5942](https://github.com/woodpecker-ci/woodpecker/pull/5942)]
|
||||
- chore(deps): update docker.io/woodpeckerci/plugin-surge-preview docker tag to v1.4.2 [[#5938](https://github.com/woodpecker-ci/woodpecker/pull/5938)]
|
||||
- chore(deps): update docker.io/woodpeckerci/plugin-ready-release-go docker tag to v3.4.1 [[#5937](https://github.com/woodpecker-ci/woodpecker/pull/5937)]
|
||||
- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v6.0.4 [[#5936](https://github.com/woodpecker-ci/woodpecker/pull/5936)]
|
||||
- chore(deps): update docker.io/woodpeckerci/plugin-editorconfig-checker docker tag to v0.3.3 [[#5934](https://github.com/woodpecker-ci/woodpecker/pull/5934)]
|
||||
- fix(deps): update module gitlab.com/gitlab-org/api/client-go to v1.11.0 [[#5919](https://github.com/woodpecker-ci/woodpecker/pull/5919)]
|
||||
- chore(deps): lock file maintenance [[#5916](https://github.com/woodpecker-ci/woodpecker/pull/5916)]
|
||||
- chore(deps): update dependency simple-icons to v16.4.0 [[#5915](https://github.com/woodpecker-ci/woodpecker/pull/5915)]
|
||||
- fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.33 [[#5910](https://github.com/woodpecker-ci/woodpecker/pull/5910)]
|
||||
- chore(deps): lock file maintenance [[#5913](https://github.com/woodpecker-ci/woodpecker/pull/5913)]
|
||||
- chore(deps): lock file maintenance [[#5907](https://github.com/woodpecker-ci/woodpecker/pull/5907)]
|
||||
- chore(deps): update dependency simple-icons to v16.3.0 [[#5905](https://github.com/woodpecker-ci/woodpecker/pull/5905)]
|
||||
- chore(deps): update web npm deps non-major [[#5903](https://github.com/woodpecker-ci/woodpecker/pull/5903)]
|
||||
- fix(deps): update module google.golang.org/grpc to v1.78.0 [[#5901](https://github.com/woodpecker-ci/woodpecker/pull/5901)]
|
||||
- chore(deps): lock file maintenance [[#5895](https://github.com/woodpecker-ci/woodpecker/pull/5895)]
|
||||
- fix(deps): update module github.com/tink-crypto/tink-go/v2 to v2.6.0 [[#5894](https://github.com/woodpecker-ci/woodpecker/pull/5894)]
|
||||
- chore(deps): update dependency @antfu/eslint-config to v6.7.2 [[#5893](https://github.com/woodpecker-ci/woodpecker/pull/5893)]
|
||||
- chore(deps): update dependency vue-i18n to v11.2.7 [[#5892](https://github.com/woodpecker-ci/woodpecker/pull/5892)]
|
||||
- chore(deps): update dependency vue-tsc to v3.2.0 [[#5891](https://github.com/woodpecker-ci/woodpecker/pull/5891)]
|
||||
- Migrate to maintained tink-go [[#5886](https://github.com/woodpecker-ci/woodpecker/pull/5886)]
|
||||
- chore(deps): update web npm deps non-major [[#5887](https://github.com/woodpecker-ci/woodpecker/pull/5887)]
|
||||
- fix(deps): update module gitlab.com/gitlab-org/api/client-go to v1.10.0 [[#5888](https://github.com/woodpecker-ci/woodpecker/pull/5888)]
|
||||
- fix(deps): update golang-packages [[#5877](https://github.com/woodpecker-ci/woodpecker/pull/5877)]
|
||||
- fix(deps): update module gitlab.com/gitlab-org/api/client-go to v1.9.0 [[#5873](https://github.com/woodpecker-ci/woodpecker/pull/5873)]
|
||||
- fix(deps): update golang-packages [[#5870](https://github.com/woodpecker-ci/woodpecker/pull/5870)]
|
||||
- chore(deps): lock file maintenance [[#5868](https://github.com/woodpecker-ci/woodpecker/pull/5868)]
|
||||
- fix(deps): update module github.com/gdgvda/cron to v0.6.0 [[#5867](https://github.com/woodpecker-ci/woodpecker/pull/5867)]
|
||||
- chore(deps): update dependency @intlify/unplugin-vue-i18n to v11.0.3 [[#5866](https://github.com/woodpecker-ci/woodpecker/pull/5866)]
|
||||
- chore(deps): update dependency @antfu/eslint-config to v6.7.1 [[#5865](https://github.com/woodpecker-ci/woodpecker/pull/5865)]
|
||||
- chore(deps): update web npm deps non-major [[#5864](https://github.com/woodpecker-ci/woodpecker/pull/5864)]
|
||||
- chore(deps): update dependency @types/node to v24.10.4 [[#5863](https://github.com/woodpecker-ci/woodpecker/pull/5863)]
|
||||
- chore(deps): update web npm deps non-major [[#5859](https://github.com/woodpecker-ci/woodpecker/pull/5859)]
|
||||
- chore(deps): update pre-commit hook igorshubovych/markdownlint-cli to v0.47.0 [[#5858](https://github.com/woodpecker-ci/woodpecker/pull/5858)]
|
||||
- fix(deps): update golang-packages [[#5856](https://github.com/woodpecker-ci/woodpecker/pull/5856)]
|
||||
- fix(deps): update golang-packages [[#5851](https://github.com/woodpecker-ci/woodpecker/pull/5851)]
|
||||
- fix(deps): update golang-packages [[#5849](https://github.com/woodpecker-ci/woodpecker/pull/5849)]
|
||||
- chore(deps): lock file maintenance [[#5847](https://github.com/woodpecker-ci/woodpecker/pull/5847)]
|
||||
- chore(deps): update web npm deps non-major [[#5837](https://github.com/woodpecker-ci/woodpecker/pull/5837)]
|
||||
- chore(deps): update dependency golangci/golangci-lint to v2.7.2 [[#5845](https://github.com/woodpecker-ci/woodpecker/pull/5845)]
|
||||
- chore(deps): update pre-commit hook golangci/golangci-lint to v2.7.2 [[#5846](https://github.com/woodpecker-ci/woodpecker/pull/5846)]
|
||||
- fix(deps): update module gitlab.com/gitlab-org/api/client-go to v1.7.0 [[#5840](https://github.com/woodpecker-ci/woodpecker/pull/5840)]
|
||||
- fix(deps): update module github.com/google/go-github/v79 to v80 [[#5838](https://github.com/woodpecker-ci/woodpecker/pull/5838)]
|
||||
- chore(deps): update pre-commit non-major [[#5836](https://github.com/woodpecker-ci/woodpecker/pull/5836)]
|
||||
- chore(deps): update docker.io/lycheeverse/lychee docker tag to v0.22.0 [[#5833](https://github.com/woodpecker-ci/woodpecker/pull/5833)]
|
||||
- chore(deps): update dependency golangci/golangci-lint to v2.7.1 [[#5832](https://github.com/woodpecker-ci/woodpecker/pull/5832)]
|
||||
- chore(deps): update docker.io/alpine docker tag to v3.23 [[#5830](https://github.com/woodpecker-ci/woodpecker/pull/5830)]
|
||||
- chore(deps): update docker.io/woodpeckerci/plugin-trivy docker tag to v1.4.4 [[#5828](https://github.com/woodpecker-ci/woodpecker/pull/5828)]
|
||||
- chore(deps): update dependency golang to v1.25.5 [[#5827](https://github.com/woodpecker-ci/woodpecker/pull/5827)]
|
||||
- fix(deps): update golang-packages [[#5816](https://github.com/woodpecker-ci/woodpecker/pull/5816)]
|
||||
- fix(deps): update module gitlab.com/gitlab-org/api/client-go to v1.3.1 [[#5812](https://github.com/woodpecker-ci/woodpecker/pull/5812)]
|
||||
- fix(deps): update module gitlab.com/gitlab-org/api/client-go to v1.3.0 [[#5807](https://github.com/woodpecker-ci/woodpecker/pull/5807)]
|
||||
- chore(deps): lock file maintenance [[#5808](https://github.com/woodpecker-ci/woodpecker/pull/5808)]
|
||||
- chore(deps): update pre-commit hook rbubley/mirrors-prettier to v3.7.3 [[#5804](https://github.com/woodpecker-ci/woodpecker/pull/5804)]
|
||||
- fix(deps): update dependency simple-icons to v16 [[#5802](https://github.com/woodpecker-ci/woodpecker/pull/5802)]
|
||||
- fix(deps): update module github.com/docker/cli to v29.1.1+incompatible [[#5801](https://github.com/woodpecker-ci/woodpecker/pull/5801)]
|
||||
- chore(deps): update dependency prettier to v3.7.3 [[#5800](https://github.com/woodpecker-ci/woodpecker/pull/5800)]
|
||||
- chore(deps): update pre-commit hook rbubley/mirrors-prettier to v3.7.2 [[#5795](https://github.com/woodpecker-ci/woodpecker/pull/5795)]
|
||||
- fix(deps): update module gitlab.com/gitlab-org/api/client-go to v1 [[#5794](https://github.com/woodpecker-ci/woodpecker/pull/5794)]
|
||||
- chore(deps): update web npm deps non-major [[#5792](https://github.com/woodpecker-ci/woodpecker/pull/5792)]
|
||||
- chore(deps): update pre-commit hook rbubley/mirrors-prettier to v3.7.1 [[#5793](https://github.com/woodpecker-ci/woodpecker/pull/5793)]
|
||||
- fix(deps): update module github.com/docker/cli to v29.1.0+incompatible [[#5789](https://github.com/woodpecker-ci/woodpecker/pull/5789)]
|
||||
- fix(deps): update golang-packages [[#5787](https://github.com/woodpecker-ci/woodpecker/pull/5787)]
|
||||
- chore(deps): lock file maintenance [[#5784](https://github.com/woodpecker-ci/woodpecker/pull/5784)]
|
||||
- chore(deps): update dependency simple-icons to v15.22.0 [[#5782](https://github.com/woodpecker-ci/woodpecker/pull/5782)]
|
||||
- chore(deps): update dependency vue-tsc to v3.1.5 [[#5781](https://github.com/woodpecker-ci/woodpecker/pull/5781)]
|
||||
- chore(deps): update dependency @types/lodash to v4.17.21 [[#5780](https://github.com/woodpecker-ci/woodpecker/pull/5780)]
|
||||
- chore(deps): update dependency vue-i18n to v11.2.1 [[#5779](https://github.com/woodpecker-ci/woodpecker/pull/5779)]
|
||||
- chore(deps): update pre-commit hook igorshubovych/markdownlint-cli to v0.46.0 [[#5776](https://github.com/woodpecker-ci/woodpecker/pull/5776)]
|
||||
- chore(deps): update web npm deps non-major [[#5775](https://github.com/woodpecker-ci/woodpecker/pull/5775)]
|
||||
- fix(deps): update golang-packages [[#5770](https://github.com/woodpecker-ci/woodpecker/pull/5770)]
|
||||
- fix(deps): update golang-packages [[#5765](https://github.com/woodpecker-ci/woodpecker/pull/5765)]
|
||||
|
||||
### Misc
|
||||
|
||||
- Revert "Send configuration as part of the request for external configuration" [[#5835](https://github.com/woodpecker-ci/woodpecker/pull/5835)]
|
||||
- Allow packagers to set WebUI root to custom path [[#5809](https://github.com/woodpecker-ci/woodpecker/pull/5809)]
|
||||
- fix(queue): force agent cancellation on lease expiration [[#5823](https://github.com/woodpecker-ci/woodpecker/pull/5823)]
|
||||
- Extract interval into composition [[#5818](https://github.com/woodpecker-ci/woodpecker/pull/5818)]
|
||||
- Fix outdated Makefile target [[#5817](https://github.com/woodpecker-ci/woodpecker/pull/5817)]
|
||||
- Makefile: add target to generate man pages [[#5810](https://github.com/woodpecker-ci/woodpecker/pull/5810)]
|
||||
- Split make install targets [[#5796](https://github.com/woodpecker-ci/woodpecker/pull/5796)]
|
||||
- Use golangci docker image [[#5797](https://github.com/woodpecker-ci/woodpecker/pull/5797)]
|
||||
- Clarify envvars documentation [[#5788](https://github.com/woodpecker-ci/woodpecker/pull/5788)]
|
||||
|
||||
## [3.12.0](https://github.com/woodpecker-ci/woodpecker/releases/tag/v3.12.0) - 2025-11-18
|
||||
|
||||
### ❤️ Thanks to all contributors! ❤️
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# renovate: datasource=github-releases depName=mvdan/gofumpt
|
||||
GOFUMPT_VERSION := v0.9.2
|
||||
# renovate: datasource=github-releases depName=golangci/golangci-lint
|
||||
GOLANGCI_LINT_VERSION := v2.7.2
|
||||
GOLANGCI_LINT_VERSION := v2.10.1
|
||||
# renovate: datasource=docker depName=docker.io/techknowlogick/xgo
|
||||
XGO_VERSION := go-1.25.x
|
||||
XGO_VERSION := go-1.26.x
|
||||
|
||||
GO_PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
|
||||
|
||||
@@ -123,7 +123,7 @@ generate: install-mockery generate-openapi ## Run all code generations
|
||||
CGO_ENABLED=0 go generate ./...
|
||||
|
||||
generate-openapi: ## Run openapi code generation and format it
|
||||
CGO_ENABLED=0 go run github.com/swaggo/swag/cmd/swag fmt --exclude pipeline/rpc/proto
|
||||
CGO_ENABLED=0 go run github.com/swaggo/swag/cmd/swag fmt --exclude rpc/proto
|
||||
CGO_ENABLED=0 go generate cmd/server/openapi.go
|
||||
|
||||
generate-license-header: install-addlicense
|
||||
@@ -162,6 +162,7 @@ install-protoc-gen-go:
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest; \
|
||||
fi
|
||||
|
||||
.PHONY: install-tools
|
||||
install-tools: install-golangci-lint install-gofumpt install-addlicense install-mockery install-protoc-gen-go ## Install development tools
|
||||
|
||||
ui-dependencies: ## Install UI dependencies
|
||||
@@ -233,6 +234,7 @@ build-tarball: ## Build tar archive
|
||||
.PHONY: build
|
||||
build: build-agent build-server build-cli ## Build all binaries
|
||||
|
||||
.PHONY: release-frontend
|
||||
release-frontend: build-ui ## Build frontend
|
||||
|
||||
cross-compile-server: ## Cross compile the server
|
||||
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/shared"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc"
|
||||
)
|
||||
|
||||
// LineWriter sends logs to the client.
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/log"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/mocks"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/agent/log"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc/mocks"
|
||||
)
|
||||
|
||||
func TestLineWriter(t *testing.T) {
|
||||
+7
-6
@@ -20,13 +20,15 @@ import (
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/agent/log"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline"
|
||||
backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/log"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/logging"
|
||||
pipeline_utils "go.woodpecker-ci.org/woodpecker/v3/pipeline/utils"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc"
|
||||
)
|
||||
|
||||
func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, workflow *rpc.Workflow) pipeline.Logger {
|
||||
func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, workflow *rpc.Workflow) logging.Logger {
|
||||
return func(step *backend.Step, rc io.ReadCloser) error {
|
||||
defer rc.Close()
|
||||
|
||||
@@ -35,6 +37,7 @@ func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, w
|
||||
Logger()
|
||||
|
||||
uploads.Add(1)
|
||||
defer uploads.Done()
|
||||
|
||||
var secrets []string
|
||||
for _, secret := range workflow.Config.Secrets {
|
||||
@@ -44,13 +47,11 @@ func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, w
|
||||
logger.Debug().Msg("log stream opened")
|
||||
|
||||
logStream := log.NewLineWriter(r.client, step.UUID, secrets...)
|
||||
if err := log.CopyLineByLine(logStream, rc, pipeline.MaxLogLineLength); err != nil {
|
||||
if err := pipeline_utils.CopyLineByLine(logStream, rc, pipeline.MaxLogLineLength); err != nil {
|
||||
logger.Error().Err(err).Msg("copy limited logStream part")
|
||||
}
|
||||
|
||||
logger.Debug().Msg("log stream copied, close ...")
|
||||
uploads.Done()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc/proto"
|
||||
)
|
||||
|
||||
const authClientTimeout = time.Second * 5
|
||||
|
||||
+16
-13
@@ -28,8 +28,8 @@ import (
|
||||
grpcproto "google.golang.org/protobuf/proto"
|
||||
|
||||
backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -148,15 +148,16 @@ func (c *client) Next(ctx context.Context, filter rpc.Filter) (*rpc.Workflow, er
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// Wait blocks until the workflow is complete.
|
||||
func (c *client) Wait(ctx context.Context, workflowID string) (err error) {
|
||||
// Wait blocks until the workflow with the given ID is marked as completed or canceled by the server.
|
||||
func (c *client) Wait(ctx context.Context, workflowID string) (canceled bool, err error) {
|
||||
retry := c.newBackOff()
|
||||
req := new(proto.WaitRequest)
|
||||
req.Id = workflowID
|
||||
for {
|
||||
_, err = c.client.Wait(ctx, req)
|
||||
resp, err := c.client.Wait(ctx, req)
|
||||
if err == nil {
|
||||
break
|
||||
// wait block was released normally as expected by server
|
||||
return resp.GetCanceled(), nil
|
||||
}
|
||||
|
||||
switch status.Code(err) {
|
||||
@@ -164,10 +165,10 @@ func (c *client) Wait(ctx context.Context, workflowID string) (err error) {
|
||||
if ctx.Err() != nil {
|
||||
// expected as context was canceled
|
||||
log.Debug().Err(err).Msgf("grpc error: wait(): context canceled")
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
|
||||
return err
|
||||
return false, err
|
||||
case
|
||||
codes.Aborted,
|
||||
codes.DataLoss,
|
||||
@@ -178,16 +179,15 @@ func (c *client) Wait(ctx context.Context, workflowID string) (err error) {
|
||||
log.Warn().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
|
||||
default:
|
||||
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(retry.NextBackOff()):
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
return false, ctx.Err()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init signals the workflow is initialized.
|
||||
@@ -199,6 +199,7 @@ func (c *client) Init(ctx context.Context, workflowID string, state rpc.Workflow
|
||||
req.State.Started = state.Started
|
||||
req.State.Finished = state.Finished
|
||||
req.State.Error = state.Error
|
||||
req.State.Canceled = state.Canceled
|
||||
for {
|
||||
_, err = c.client.Init(ctx, req)
|
||||
if err == nil {
|
||||
@@ -238,7 +239,7 @@ func (c *client) Init(ctx context.Context, workflowID string, state rpc.Workflow
|
||||
return nil
|
||||
}
|
||||
|
||||
// Done signals the workflow is complete.
|
||||
// Done let agent signal to server the workflow has stopped.
|
||||
func (c *client) Done(ctx context.Context, workflowID string, state rpc.WorkflowState) (err error) {
|
||||
retry := c.newBackOff()
|
||||
req := new(proto.DoneRequest)
|
||||
@@ -247,6 +248,7 @@ func (c *client) Done(ctx context.Context, workflowID string, state rpc.Workflow
|
||||
req.State.Started = state.Started
|
||||
req.State.Finished = state.Finished
|
||||
req.State.Error = state.Error
|
||||
req.State.Canceled = state.Canceled
|
||||
for {
|
||||
_, err = c.client.Done(ctx, req)
|
||||
if err == nil {
|
||||
@@ -330,7 +332,7 @@ func (c *client) Extend(ctx context.Context, workflowID string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates the workflow state.
|
||||
// Update let agent updates the step state at the server.
|
||||
func (c *client) Update(ctx context.Context, workflowID string, state rpc.StepState) (err error) {
|
||||
retry := c.newBackOff()
|
||||
req := new(proto.UpdateRequest)
|
||||
@@ -342,6 +344,7 @@ func (c *client) Update(ctx context.Context, workflowID string, state rpc.StepSt
|
||||
req.State.Exited = state.Exited
|
||||
req.State.ExitCode = int32(state.ExitCode)
|
||||
req.State.Error = state.Error
|
||||
req.State.Canceled = state.Canceled
|
||||
for {
|
||||
_, err = c.client.Update(ctx, req)
|
||||
if err == nil {
|
||||
|
||||
+69
-55
@@ -25,9 +25,10 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline"
|
||||
backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
|
||||
pipeline_errors "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors"
|
||||
pipeline_runtime "go.woodpecker-ci.org/woodpecker/v3/pipeline/runtime"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/shared/constant"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/shared/utils"
|
||||
)
|
||||
@@ -50,13 +51,15 @@ func NewRunner(workEngine rpc.Peer, f rpc.Filter, h string, state *State, backen
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) Run(runnerCtx, shutdownCtx context.Context) error { //nolint:contextcheck
|
||||
// Run executes a workflow using a backend, tracks its state and reports the state back to the server.
|
||||
func (r *Runner) Run(runnerCtx, shutdownCtx context.Context) error {
|
||||
log.Debug().Msg("request next execution")
|
||||
|
||||
// Preserve metadata AND cancellation from runnerCtx.
|
||||
meta, _ := metadata.FromOutgoingContext(runnerCtx)
|
||||
ctxMeta := metadata.NewOutgoingContext(context.Background(), meta)
|
||||
ctxMeta := metadata.NewOutgoingContext(shutdownCtx, meta)
|
||||
|
||||
// get the next workflow from the queue
|
||||
// Fetch next workflow from the queue
|
||||
workflow, err := r.client.Next(runnerCtx, r.filter)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -65,6 +68,7 @@ func (r *Runner) Run(runnerCtx, shutdownCtx context.Context) error { //nolint:co
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compute workflow timeout
|
||||
timeout := time.Hour
|
||||
if minutes := workflow.Timeout; minutes != 0 {
|
||||
timeout = time.Duration(minutes) * time.Minute
|
||||
@@ -73,12 +77,8 @@ func (r *Runner) Run(runnerCtx, shutdownCtx context.Context) error { //nolint:co
|
||||
repoName := extractRepositoryName(workflow.Config) // hack
|
||||
pipelineNumber := extractPipelineNumber(workflow.Config) // hack
|
||||
|
||||
r.counter.Add(
|
||||
workflow.ID,
|
||||
timeout,
|
||||
repoName,
|
||||
pipelineNumber,
|
||||
)
|
||||
// Track workflow execution in runner state
|
||||
r.counter.Add(workflow.ID, timeout, repoName, pipelineNumber)
|
||||
defer r.counter.Done(workflow.ID)
|
||||
|
||||
logger := log.With().
|
||||
@@ -89,63 +89,79 @@ func (r *Runner) Run(runnerCtx, shutdownCtx context.Context) error { //nolint:co
|
||||
|
||||
logger.Debug().Msg("received execution")
|
||||
|
||||
workflowCtx, cancel := context.WithTimeout(ctxMeta, timeout)
|
||||
defer cancel()
|
||||
// Workflow execution context.
|
||||
// This context is the SINGLE source of truth for cancellation.
|
||||
workflowCtx, _ := context.WithTimeout(ctxMeta, timeout) //nolint:govet
|
||||
workflowCtx, cancelWorkflowCtx := context.WithCancelCause(workflowCtx)
|
||||
defer cancelWorkflowCtx(nil)
|
||||
|
||||
// Add sigterm support for internal context.
|
||||
// Required when the pipeline is terminated by external signals
|
||||
// like kubernetes.
|
||||
// Required to be able to terminate the running workflow by external signals.
|
||||
workflowCtx = utils.WithContextSigtermCallback(workflowCtx, func() {
|
||||
logger.Error().Msg("Received sigterm termination signal")
|
||||
logger.Error().Msg("received sigterm termination signal")
|
||||
// WithContextSigtermCallback would cancel the context too, but we want our own custom error
|
||||
cancelWorkflowCtx(pipeline_errors.ErrCancel)
|
||||
})
|
||||
|
||||
canceled := false
|
||||
// Listen for remote cancel events (UI / API).
|
||||
// When canceled, we MUST cancel the workflow context
|
||||
// so that workflow execution stop immediately.
|
||||
go func() {
|
||||
logger.Debug().Msg("listen for cancel signal")
|
||||
logger.Debug().Msg("start listening for server side cancel signal")
|
||||
|
||||
if err := r.client.Wait(workflowCtx, workflow.ID); err != nil {
|
||||
canceled = true
|
||||
logger.Warn().Err(err).Msg("cancel signal received")
|
||||
cancel()
|
||||
if canceled, err := r.client.Wait(workflowCtx, workflow.ID); err != nil {
|
||||
logger.Error().Err(err).Msg("server returned unexpected err while waiting for workflow to finish run")
|
||||
cancelWorkflowCtx(err)
|
||||
} else {
|
||||
logger.Debug().Msg("done listening for cancel signal")
|
||||
if canceled {
|
||||
logger.Debug().Err(err).Msg("server side cancel signal received")
|
||||
cancelWorkflowCtx(pipeline_errors.ErrCancel)
|
||||
}
|
||||
// Wait returned without error, meaning the workflow finished normally
|
||||
logger.Debug().Msg("cancel listener exited normally")
|
||||
}
|
||||
}()
|
||||
|
||||
// Periodically extend the workflow lease while running
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-workflowCtx.Done():
|
||||
logger.Debug().Msg("pipeline done")
|
||||
logger.Debug().Msg("workflow context done")
|
||||
return
|
||||
|
||||
case <-time.After(constant.TaskTimeout / 3):
|
||||
logger.Debug().Msg("pipeline lease renewed")
|
||||
logger.Debug().Msg("renewing workflow lease")
|
||||
if err := r.client.Extend(workflowCtx, workflow.ID); err != nil {
|
||||
log.Error().Err(err).Msg("extending pipeline deadline failed")
|
||||
logger.Error().Err(err).Msg("failed to extend workflow lease")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
state := rpc.WorkflowState{}
|
||||
state.Started = time.Now().Unix()
|
||||
state := rpc.WorkflowState{
|
||||
Started: time.Now().Unix(),
|
||||
}
|
||||
|
||||
err = r.client.Init(runnerCtx, workflow.ID, state)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("workflow initialization failed")
|
||||
// TODO: should we return here?
|
||||
if err := r.client.Init(runnerCtx, workflow.ID, state); err != nil {
|
||||
logger.Error().Err(err).Msg("signaling workflow initialization to server failed")
|
||||
// We have an error, maybe the server is currently unreachable or other server-side errors occurred.
|
||||
// So let's clean up and end this not yet started workflow run.
|
||||
cancelWorkflowCtx(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var uploads sync.WaitGroup
|
||||
//nolint:contextcheck
|
||||
err = pipeline.New(workflow.Config,
|
||||
pipeline.WithContext(workflowCtx),
|
||||
pipeline.WithTaskUUID(fmt.Sprint(workflow.ID)),
|
||||
pipeline.WithLogger(r.createLogger(logger, &uploads, workflow)),
|
||||
pipeline.WithTracer(r.createTracer(ctxMeta, &uploads, logger, workflow)),
|
||||
pipeline.WithBackend(*r.backend),
|
||||
pipeline.WithDescription(map[string]string{
|
||||
|
||||
// Run pipeline
|
||||
err = pipeline_runtime.New(
|
||||
workflow.Config,
|
||||
pipeline_runtime.WithContext(workflowCtx),
|
||||
pipeline_runtime.WithTaskUUID(fmt.Sprint(workflow.ID)),
|
||||
pipeline_runtime.WithLogger(r.createLogger(logger, &uploads, workflow)),
|
||||
pipeline_runtime.WithTracer(r.createTracer(ctxMeta, &uploads, logger, workflow)),
|
||||
pipeline_runtime.WithBackend(*r.backend),
|
||||
pipeline_runtime.WithDescription(map[string]string{
|
||||
"workflow_id": workflow.ID,
|
||||
"repo": repoName,
|
||||
"pipeline_number": pipelineNumber,
|
||||
@@ -154,37 +170,35 @@ func (r *Runner) Run(runnerCtx, shutdownCtx context.Context) error { //nolint:co
|
||||
|
||||
state.Finished = time.Now().Unix()
|
||||
|
||||
if errors.Is(err, pipeline.ErrCancel) {
|
||||
canceled = true
|
||||
} else if canceled {
|
||||
err = errors.Join(err, pipeline.ErrCancel)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
state.Error = err.Error()
|
||||
if errors.Is(err, pipeline_errors.ErrCancel) {
|
||||
state.Canceled = true
|
||||
// cleanup joined error messages
|
||||
state.Error = pipeline_errors.ErrCancel.Error()
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debug().
|
||||
Str("error", state.Error).
|
||||
Bool("canceled", canceled).
|
||||
Bool("canceled", state.Canceled).
|
||||
Msg("workflow finished")
|
||||
|
||||
logger.Debug().Msg("uploading logs and traces / states ...")
|
||||
// Ensure all logs/traces are uploaded before finishing
|
||||
logger.Debug().Msg("waiting for logs and traces upload")
|
||||
uploads.Wait()
|
||||
logger.Debug().Msg("uploaded logs and traces / states")
|
||||
|
||||
logger.Debug().
|
||||
Str("error", state.Error).
|
||||
Msg("updating workflow status")
|
||||
logger.Debug().Msg("logs and traces uploaded")
|
||||
|
||||
// Update workflow state
|
||||
doneCtx := runnerCtx
|
||||
if doneCtx.Err() != nil {
|
||||
doneCtx = shutdownCtx
|
||||
}
|
||||
|
||||
if err := r.client.Done(doneCtx, workflow.ID, state); err != nil {
|
||||
logger.Error().Err(err).Msg("updating workflow status failed")
|
||||
logger.Error().Err(err).Msg("failed to update workflow status")
|
||||
} else {
|
||||
logger.Debug().Msg("updating workflow status complete")
|
||||
logger.Debug().Msg("signaling workflow stopped done")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+13
-7
@@ -16,6 +16,7 @@ package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -23,13 +24,16 @@ import (
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
|
||||
pipeline_errors "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/state"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/tracing"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc"
|
||||
)
|
||||
|
||||
func (r *Runner) createTracer(ctxMeta context.Context, uploads *sync.WaitGroup, logger zerolog.Logger, workflow *rpc.Workflow) pipeline.TraceFunc {
|
||||
return func(state *pipeline.State) error {
|
||||
func (r *Runner) createTracer(ctxMeta context.Context, uploads *sync.WaitGroup, logger zerolog.Logger, workflow *rpc.Workflow) tracing.TraceFunc {
|
||||
return func(state *state.State) error {
|
||||
uploads.Add(1)
|
||||
defer uploads.Done()
|
||||
|
||||
stepLogger := logger.With().
|
||||
Str("image", state.Pipeline.Step.Image).
|
||||
@@ -43,12 +47,15 @@ func (r *Runner) createTracer(ctxMeta context.Context, uploads *sync.WaitGroup,
|
||||
StepUUID: state.Pipeline.Step.UUID,
|
||||
Exited: state.Process.Exited,
|
||||
ExitCode: state.Process.ExitCode,
|
||||
Started: time.Now().Unix(), // TODO: do not do this
|
||||
Finished: time.Now().Unix(),
|
||||
Started: state.Process.Started,
|
||||
Canceled: errors.Is(state.Process.Error, pipeline_errors.ErrCancel),
|
||||
}
|
||||
if state.Process.Error != nil {
|
||||
stepState.Error = state.Process.Error.Error()
|
||||
}
|
||||
if state.Process.Exited {
|
||||
stepState.Finished = time.Now().Unix()
|
||||
}
|
||||
|
||||
defer func() {
|
||||
stepLogger.Debug().Msg("update step status")
|
||||
@@ -60,7 +67,6 @@ func (r *Runner) createTracer(ctxMeta context.Context, uploads *sync.WaitGroup,
|
||||
}
|
||||
|
||||
stepLogger.Debug().Msg("update step status complete")
|
||||
uploads.Done()
|
||||
}()
|
||||
if state.Process.Exited {
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[maxbodylength]
|
||||
disabled = true
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/internal/config"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/output"
|
||||
)
|
||||
|
||||
// Command exports the context command set.
|
||||
var Command = &cli.Command{
|
||||
Name: "context",
|
||||
Aliases: []string{"ctx"},
|
||||
Usage: "manage contexts",
|
||||
Description: "Contexts can be used to manage users on one or multiple servers.\nTo create a new context run the setup command",
|
||||
Commands: []*cli.Command{
|
||||
listCommand,
|
||||
useCommand,
|
||||
deleteCommand,
|
||||
renameCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var listCommand = &cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list all contexts",
|
||||
Flags: append(common.OutputFlags("table"), []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "output-no-headers",
|
||||
Usage: "do not print headers in output",
|
||||
},
|
||||
}...),
|
||||
Action: listContexts,
|
||||
}
|
||||
|
||||
var useCommand = &cli.Command{
|
||||
Name: "use",
|
||||
Usage: "set the current context",
|
||||
ArgsUsage: "<context-name>",
|
||||
Action: useContext,
|
||||
}
|
||||
|
||||
var deleteCommand = &cli.Command{
|
||||
Name: "delete",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "delete a context",
|
||||
ArgsUsage: "<context-name>",
|
||||
Action: deleteContext,
|
||||
}
|
||||
|
||||
var renameCommand = &cli.Command{
|
||||
Name: "rename",
|
||||
Usage: "rename a context",
|
||||
ArgsUsage: "<old-name> <new-name>",
|
||||
Action: renameContext,
|
||||
}
|
||||
|
||||
func listContexts(_ context.Context, c *cli.Command) error {
|
||||
contexts, err := config.LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(contexts.Contexts) == 0 {
|
||||
fmt.Println("No contexts found. Run 'woodpecker-cli setup' to create one.")
|
||||
return nil
|
||||
}
|
||||
|
||||
_, outOpt := output.ParseOutputOptions(c.String("output"))
|
||||
out := os.Stdout
|
||||
noHeader := c.Bool("output-no-headers")
|
||||
table := output.NewTable(out)
|
||||
|
||||
// Add custom field mapping
|
||||
table.AddFieldFn("Name", func(obj any) string {
|
||||
c, ok := obj.(config.Context)
|
||||
if !ok {
|
||||
return "???"
|
||||
}
|
||||
|
||||
if contexts.CurrentContext == c.Name {
|
||||
return c.Name + " *"
|
||||
}
|
||||
|
||||
return c.Name
|
||||
})
|
||||
table.AddFieldAlias("ServerURL", "Server URL")
|
||||
table.AddFieldAlias("LogLevel", "Log Level")
|
||||
table.AddFieldAlias("Name", "Name (selected)")
|
||||
|
||||
cols := []string{"Name (selected)", "Server URL"}
|
||||
|
||||
if len(outOpt) > 0 {
|
||||
cols = outOpt
|
||||
}
|
||||
if !noHeader {
|
||||
table.WriteHeader(cols)
|
||||
}
|
||||
for _, c := range contexts.Contexts {
|
||||
if err := table.Write(cols, c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return table.Flush()
|
||||
}
|
||||
|
||||
func useContext(_ context.Context, c *cli.Command) error {
|
||||
contextName := c.Args().First()
|
||||
if contextName == "" {
|
||||
return fmt.Errorf("context name is required")
|
||||
}
|
||||
|
||||
err := config.SetCurrentContext(contextName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msgf("Switched to context '%s'", contextName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteContext(_ context.Context, c *cli.Command) error {
|
||||
contextName := c.Args().First()
|
||||
if contextName == "" {
|
||||
return fmt.Errorf("context name is required")
|
||||
}
|
||||
|
||||
err := config.DeleteContext(c, contextName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msgf("Context '%s' deleted", contextName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func renameContext(_ context.Context, c *cli.Command) error {
|
||||
if c.Args().Len() < 2 { //nolint:mnd // min args
|
||||
return fmt.Errorf("both old name and new name are required")
|
||||
}
|
||||
|
||||
oldName := c.Args().Get(0)
|
||||
newName := c.Args().Get(1)
|
||||
|
||||
err := config.RenameContext(oldName, newName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msgf("Context renamed from '%s' to '%s'", oldName, newName)
|
||||
return nil
|
||||
}
|
||||
+12
-9
@@ -45,7 +45,10 @@ import (
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/compiler"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/matrix"
|
||||
pipelineLog "go.woodpecker-ci.org/woodpecker/v3/pipeline/log"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/logging"
|
||||
pipeline_runtime "go.woodpecker-ci.org/woodpecker/v3/pipeline/runtime"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/tracing"
|
||||
pipeline_utils "go.woodpecker-ci.org/woodpecker/v3/pipeline/utils"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/shared/constant"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/shared/utils"
|
||||
)
|
||||
@@ -318,12 +321,12 @@ func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, ax
|
||||
fmt.Printf("ctrl+c received, terminating current pipeline '%s'\n", confStr)
|
||||
})
|
||||
|
||||
return pipeline.New(compiled,
|
||||
pipeline.WithContext(pipelineCtx), //nolint:contextcheck
|
||||
pipeline.WithTracer(pipeline.DefaultTracer),
|
||||
pipeline.WithLogger(defaultLogger),
|
||||
pipeline.WithBackend(backendEngine),
|
||||
pipeline.WithDescription(map[string]string{
|
||||
return pipeline_runtime.New(compiled,
|
||||
pipeline_runtime.WithContext(pipelineCtx), //nolint:contextcheck
|
||||
pipeline_runtime.WithTracer(tracing.DefaultTracer),
|
||||
pipeline_runtime.WithLogger(defaultLogger),
|
||||
pipeline_runtime.WithBackend(backendEngine),
|
||||
pipeline_runtime.WithDescription(map[string]string{
|
||||
"CLI": "exec",
|
||||
}),
|
||||
).Run(ctx)
|
||||
@@ -348,7 +351,7 @@ func convertPathForWindows(path string) string {
|
||||
return filepath.ToSlash(path)
|
||||
}
|
||||
|
||||
var defaultLogger = pipeline.Logger(func(step *backend_types.Step, rc io.ReadCloser) error {
|
||||
var defaultLogger = logging.Logger(func(step *backend_types.Step, rc io.ReadCloser) error {
|
||||
logWriter := NewLineWriter(step.Name, step.UUID)
|
||||
return pipelineLog.CopyLineByLine(logWriter, rc, pipeline.MaxLogLineLength)
|
||||
return pipeline_utils.CopyLineByLine(logWriter, rc, pipeline.MaxLogLineLength)
|
||||
})
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ var flags = []cli.Flag{
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_SECRETS_FILE"),
|
||||
Name: "secrets",
|
||||
Name: "secrets-file",
|
||||
Usage: "path to yaml file with secrets map",
|
||||
},
|
||||
|
||||
|
||||
@@ -31,20 +31,46 @@ func (c *Config) MergeIfNotSet(c2 *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
var skipSetupForCommands = []string{"setup", "help", "h", "version", "update", "lint", "exec", "completion", ""}
|
||||
var skipSetupForCommands = []string{"setup", "help", "h", "version", "update", "lint", "exec", "completion", "", "context", "ctx"}
|
||||
|
||||
func Load(ctx context.Context, c *cli.Command) error {
|
||||
if firstArg := c.Args().First(); slices.Contains(skipSetupForCommands, firstArg) {
|
||||
return nil
|
||||
}
|
||||
|
||||
contextConfig, contextErr := GetCurrentContext(ctx, c)
|
||||
if contextErr == nil {
|
||||
if !c.IsSet("server") {
|
||||
err := c.Set("server", contextConfig.ServerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !c.IsSet("token") {
|
||||
err := c.Set("token", contextConfig.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !c.IsSet("log-level") && contextConfig.LogLevel != "" {
|
||||
err := c.Set("log-level", contextConfig.LogLevel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Debug().Any("config", contextConfig).Msg("loaded config from context")
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: remove with next major release
|
||||
// Fallback: try legacy config file (for backward compatibility)
|
||||
config, err := Get(ctx, c, c.String("config"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.ServerURL == "" || config.Token == "" {
|
||||
log.Info().Msg("woodpecker-cli is not set up, run `woodpecker-cli setup` or provide required environment variables/flags")
|
||||
log.Info().Msg("woodpecker-cli is not set up, run `woodpecker-cli setup` to create a context")
|
||||
return errors.New("woodpecker-cli is not configured")
|
||||
}
|
||||
|
||||
@@ -63,7 +89,7 @@ func Load(ctx context.Context, c *cli.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug().Any("config", config).Msg("loaded config")
|
||||
log.Debug().Any("config", config).Msg("loaded config from legacy file")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
"github.com/zalando/go-keyring"
|
||||
)
|
||||
|
||||
// Context represents a single CLI context with its connection details.
|
||||
type Context struct {
|
||||
Name string `json:"name"`
|
||||
ServerURL string `json:"server_url"`
|
||||
LogLevel string `json:"log_level,omitempty"`
|
||||
}
|
||||
|
||||
// Contexts holds all contexts and tracks the current active one.
|
||||
type Contexts struct {
|
||||
CurrentContext string `json:"current_context"`
|
||||
Contexts map[string]Context `json:"contexts"`
|
||||
}
|
||||
|
||||
func getContextsPath() (string, error) {
|
||||
configPath, err := xdg.ConfigFile("woodpecker/contexts.json")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return configPath, nil
|
||||
}
|
||||
|
||||
// LoadContexts loads all contexts from the contexts file.
|
||||
func LoadContexts() (*Contexts, error) {
|
||||
contextsPath, err := getContextsPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(contextsPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return &Contexts{
|
||||
Contexts: make(map[string]Context),
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var contexts Contexts
|
||||
err = json.Unmarshal(content, &contexts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if contexts.Contexts == nil {
|
||||
contexts.Contexts = make(map[string]Context)
|
||||
}
|
||||
|
||||
return &contexts, nil
|
||||
}
|
||||
|
||||
// SaveContexts saves all contexts to the contexts file.
|
||||
func SaveContexts(contexts *Contexts) error {
|
||||
data, err := json.MarshalIndent(contexts, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contextsPath, err := getContextsPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure the directory exists.
|
||||
dir := filepath.Dir(contextsPath)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(contextsPath, data, 0o600)
|
||||
}
|
||||
|
||||
// GetCurrentContext returns the current active context.
|
||||
func GetCurrentContext(ctx context.Context, c *cli.Command) (*Config, error) {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if contexts.CurrentContext == "" {
|
||||
return nil, errors.New("no context is currently set")
|
||||
}
|
||||
|
||||
context, exists := contexts.Contexts[contexts.CurrentContext]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("current context '%s' not found", contexts.CurrentContext)
|
||||
}
|
||||
|
||||
return GetContextConfig(c, &context)
|
||||
}
|
||||
|
||||
// GetContextConfig loads the config for a specific context including the token from keyring.
|
||||
func GetContextConfig(c *cli.Command, ctx *Context) (*Config, error) {
|
||||
conf := &Config{
|
||||
ServerURL: ctx.ServerURL,
|
||||
LogLevel: ctx.LogLevel,
|
||||
}
|
||||
|
||||
// Load token from keyring
|
||||
service := c.Root().Name
|
||||
secret, err := keyring.Get(service, ctx.ServerURL)
|
||||
if errors.Is(err, keyring.ErrUnsupportedPlatform) {
|
||||
log.Warn().Msg("keyring is not supported on this platform")
|
||||
return conf, nil
|
||||
}
|
||||
if errors.Is(err, keyring.ErrNotFound) {
|
||||
return nil, fmt.Errorf("token not found in keyring for context '%s'", ctx.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf.Token = secret
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// AddOrUpdateContext adds or updates a context and optionally sets it as current.
|
||||
func AddOrUpdateContext(c *cli.Command, name, serverURL, token, logLevel string, setCurrent bool) error {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contexts.Contexts[name] = Context{
|
||||
Name: name,
|
||||
ServerURL: serverURL,
|
||||
LogLevel: logLevel,
|
||||
}
|
||||
|
||||
if setCurrent || contexts.CurrentContext == "" {
|
||||
contexts.CurrentContext = name
|
||||
}
|
||||
|
||||
// Save token to keyring
|
||||
service := c.Root().Name
|
||||
err = keyring.Set(service, serverURL, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return SaveContexts(contexts)
|
||||
}
|
||||
|
||||
// DeleteContext removes a context.
|
||||
func DeleteContext(c *cli.Command, name string) error {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context, exists := contexts.Contexts[name]
|
||||
if !exists {
|
||||
return fmt.Errorf("context '%s' not found", name)
|
||||
}
|
||||
|
||||
// Try to delete token from keyring
|
||||
service := c.Root().Name
|
||||
err = keyring.Delete(service, context.ServerURL)
|
||||
if err != nil && !errors.Is(err, keyring.ErrNotFound) {
|
||||
log.Warn().Err(err).Msg("failed to delete token from keyring")
|
||||
}
|
||||
|
||||
delete(contexts.Contexts, name)
|
||||
|
||||
// If we deleted the current context, unset it
|
||||
if contexts.CurrentContext == name {
|
||||
contexts.CurrentContext = ""
|
||||
}
|
||||
|
||||
return SaveContexts(contexts)
|
||||
}
|
||||
|
||||
// SetCurrentContext sets the current active context.
|
||||
func SetCurrentContext(name string) error {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, exists := contexts.Contexts[name]; !exists {
|
||||
return fmt.Errorf("context '%s' not found", name)
|
||||
}
|
||||
|
||||
contexts.CurrentContext = name
|
||||
return SaveContexts(contexts)
|
||||
}
|
||||
|
||||
// RenameContext renames an existing context.
|
||||
func RenameContext(oldName, newName string) error {
|
||||
contexts, err := LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context, exists := contexts.Contexts[oldName]
|
||||
if !exists {
|
||||
return fmt.Errorf("context '%s' not found", oldName)
|
||||
}
|
||||
|
||||
if _, exists := contexts.Contexts[newName]; exists {
|
||||
return fmt.Errorf("context '%s' already exists", newName)
|
||||
}
|
||||
|
||||
// Update the name in the context
|
||||
context.Name = newName
|
||||
contexts.Contexts[newName] = context
|
||||
delete(contexts.Contexts, oldName)
|
||||
|
||||
// Update current context if necessary
|
||||
if contexts.CurrentContext == oldName {
|
||||
contexts.CurrentContext = newName
|
||||
}
|
||||
|
||||
return SaveContexts(contexts)
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestContextManagement(t *testing.T) {
|
||||
// Create a temporary directory for test contexts
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Override xdg directories for testing
|
||||
t.Setenv("HOME", tmpDir)
|
||||
xdg.Reload()
|
||||
contextsFile, err := xdg.ConfigFile("woodpecker/contexts.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("LoadContexts returns empty when file doesn't exist", func(t *testing.T) {
|
||||
contexts, err := LoadContexts()
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, contexts)
|
||||
assert.Empty(t, contexts.Contexts)
|
||||
assert.Empty(t, contexts.CurrentContext)
|
||||
})
|
||||
|
||||
t.Run("SaveContexts creates valid JSON", func(t *testing.T) {
|
||||
contexts := &Contexts{
|
||||
CurrentContext: "test",
|
||||
Contexts: map[string]Context{
|
||||
"test": {
|
||||
Name: "test",
|
||||
ServerURL: "https://test.example.com",
|
||||
LogLevel: "info",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := SaveContexts(contexts)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify file exists and contains valid JSON
|
||||
data, err := os.ReadFile(contextsFile)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(data), "test.example.com")
|
||||
})
|
||||
|
||||
t.Run("LoadContexts reads saved contexts", func(t *testing.T) {
|
||||
contexts, err := LoadContexts()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "test", contexts.CurrentContext)
|
||||
assert.Len(t, contexts.Contexts, 1)
|
||||
assert.Equal(t, "https://test.example.com", contexts.Contexts["test"].ServerURL)
|
||||
})
|
||||
|
||||
t.Run("SetCurrentContext updates current context", func(t *testing.T) {
|
||||
contexts := &Contexts{
|
||||
CurrentContext: "test",
|
||||
Contexts: map[string]Context{
|
||||
"test": {
|
||||
Name: "test",
|
||||
ServerURL: "https://test.example.com",
|
||||
},
|
||||
"prod": {
|
||||
Name: "prod",
|
||||
ServerURL: "https://prod.example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := SaveContexts(contexts)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = SetCurrentContext("prod")
|
||||
require.NoError(t, err)
|
||||
|
||||
contexts, err = LoadContexts()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "prod", contexts.CurrentContext)
|
||||
})
|
||||
|
||||
t.Run("SetCurrentContext fails for non-existent context", func(t *testing.T) {
|
||||
err := SetCurrentContext("nonexistent")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not found")
|
||||
})
|
||||
|
||||
t.Run("RenameContext updates context name", func(t *testing.T) {
|
||||
contexts := &Contexts{
|
||||
CurrentContext: "old",
|
||||
Contexts: map[string]Context{
|
||||
"old": {
|
||||
Name: "old",
|
||||
ServerURL: "https://test.example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := SaveContexts(contexts)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = RenameContext("old", "new")
|
||||
require.NoError(t, err)
|
||||
|
||||
contexts, err = LoadContexts()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "new", contexts.CurrentContext)
|
||||
assert.Contains(t, contexts.Contexts, "new")
|
||||
assert.NotContains(t, contexts.Contexts, "old")
|
||||
assert.Equal(t, "new", contexts.Contexts["new"].Name)
|
||||
})
|
||||
|
||||
t.Run("RenameContext fails if target exists", func(t *testing.T) {
|
||||
contexts := &Contexts{
|
||||
Contexts: map[string]Context{
|
||||
"ctx1": {Name: "ctx1", ServerURL: "https://test1.example.com"},
|
||||
"ctx2": {Name: "ctx2", ServerURL: "https://test2.example.com"},
|
||||
},
|
||||
}
|
||||
err := SaveContexts(contexts)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = RenameContext("ctx1", "ctx2")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "already exists")
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package output
|
||||
|
||||
import (
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package output
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseOutputOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
in string
|
||||
out string
|
||||
opts []string
|
||||
}{
|
||||
{
|
||||
in: "output",
|
||||
out: "output",
|
||||
},
|
||||
{
|
||||
in: "output=a",
|
||||
out: "output",
|
||||
opts: []string{"a"},
|
||||
},
|
||||
{
|
||||
in: "output=",
|
||||
out: "output",
|
||||
},
|
||||
{
|
||||
in: "output=a,b",
|
||||
out: "output",
|
||||
opts: []string{"a", "b"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
out, opts := ParseOutputOptions(tc.in)
|
||||
assert.Equal(t, tc.out, out)
|
||||
assert.Equal(t, tc.opts, opts)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package output
|
||||
|
||||
import (
|
||||
|
||||
+36
-25
@@ -1,10 +1,25 @@
|
||||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package output
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type writerFlusherStub struct {
|
||||
@@ -18,6 +33,7 @@ func (s writerFlusherStub) Flush() error {
|
||||
type testFieldsStruct struct {
|
||||
Name string
|
||||
Number int
|
||||
Bool bool
|
||||
}
|
||||
|
||||
func TestTableOutput(t *testing.T) {
|
||||
@@ -27,49 +43,44 @@ func TestTableOutput(t *testing.T) {
|
||||
|
||||
t.Run("AddAllowedFields", func(t *testing.T) {
|
||||
_, _ = to.AddAllowedFields(testFieldsStruct{})
|
||||
if _, ok := to.allowedFields["name"]; !ok {
|
||||
t.Error("name should be a allowed field")
|
||||
}
|
||||
_, ok := to.allowedFields["name"]
|
||||
assert.True(t, ok)
|
||||
})
|
||||
t.Run("AddFieldAlias", func(t *testing.T) {
|
||||
to.AddFieldAlias("WoodpeckerCI", "wp")
|
||||
if alias, ok := to.fieldAlias["wp"]; !ok || alias != "WoodpeckerCI" {
|
||||
t.Errorf("'wp' alias should resolve to 'WoodpeckerCI', is: %v", alias)
|
||||
}
|
||||
alias, ok := to.fieldAlias["wp"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "WoodpeckerCI", alias)
|
||||
})
|
||||
t.Run("AddFieldOutputFn", func(t *testing.T) {
|
||||
to.AddFieldFn("WoodpeckerCI", FieldFn(func(_ any) string {
|
||||
return "WOODPECKER CI!!!"
|
||||
}))
|
||||
if _, ok := to.fieldMapping["woodpeckerci"]; !ok {
|
||||
t.Errorf("'WoodpeckerCI' field output fn should be set")
|
||||
}
|
||||
_, ok := to.fieldMapping["woodpeckerci"]
|
||||
assert.True(t, ok)
|
||||
})
|
||||
t.Run("ValidateColumns", func(t *testing.T) {
|
||||
err := to.ValidateColumns([]string{"non-existent", "NAME"})
|
||||
if err == nil ||
|
||||
strings.Contains(err.Error(), "name") ||
|
||||
!strings.Contains(err.Error(), "non-existent") {
|
||||
t.Errorf("error should contain 'non-existent' but not 'name': %v", err)
|
||||
}
|
||||
assert.Error(t, err)
|
||||
assert.ErrorContains(t, err, "non-existent")
|
||||
assert.NotContains(t, err.Error(), "name")
|
||||
|
||||
assert.NoError(t, to.ValidateColumns([]string{"name"}))
|
||||
})
|
||||
t.Run("WriteHeader", func(t *testing.T) {
|
||||
to.WriteHeader([]string{"wp", "name"})
|
||||
if wfs.String() != "WP\tNAME\n" {
|
||||
t.Errorf("written header should be 'WOODPECKER CI\\tNAME\\n', is: %q", wfs.String())
|
||||
}
|
||||
assert.Equal(t, "WP\tNAME\n", wfs.String())
|
||||
wfs.Reset()
|
||||
})
|
||||
t.Run("WriteLine", func(t *testing.T) {
|
||||
_ = to.Write([]string{"wp", "name", "number"}, &testFieldsStruct{"test123", 1000000000})
|
||||
if wfs.String() != "WOODPECKER CI!!!\ttest123\t1000000000\n" {
|
||||
t.Errorf("written line should be 'WOODPECKER CI!!!\\ttest123\\t1000000000\\n', is: %q", wfs.String())
|
||||
}
|
||||
err := to.Write([]string{"wp", "name", "number", "bool"}, &testFieldsStruct{"test123", 1000000000, true})
|
||||
assert.NoError(t, err)
|
||||
err = to.Write([]string{"wp", "name", "number", "bool"}, &testFieldsStruct{"", 1000000000, false})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "WOODPECKER CI!!!\ttest123\t1000000000\tyes\nWOODPECKER CI!!!\t-\t1000000000\tno\n", wfs.String())
|
||||
wfs.Reset()
|
||||
})
|
||||
t.Run("Columns", func(t *testing.T) {
|
||||
if len(to.Columns()) != 3 {
|
||||
t.Errorf("unexpected number of columns: %v", to.Columns())
|
||||
}
|
||||
assert.Len(t, to.Columns(), 4)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -73,6 +73,20 @@ func pipelineOutput(c *cli.Command, pipelines []*woodpecker.Pipeline, fd ...io.W
|
||||
if err := tmpl.Execute(out, pipelines); err != nil {
|
||||
return err
|
||||
}
|
||||
case "go-format":
|
||||
if len(outOpt) < 1 {
|
||||
return fmt.Errorf("%w: missing template", output.ErrOutputOptionRequired)
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(outOpt[0] + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range pipelines {
|
||||
if err := tmpl.Execute(out, p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case "table":
|
||||
fallthrough
|
||||
default:
|
||||
|
||||
@@ -40,6 +40,11 @@ func TestPipelineOutput(t *testing.T) {
|
||||
args: []string{"output", "--output", "go-template={{range . }}{{.Number}} {{.Status}} {{.Branch}}{{end}}"},
|
||||
expected: "1 success main\n",
|
||||
},
|
||||
{
|
||||
name: "go-format output",
|
||||
args: []string{"output", "--output", "go-format={{.Number}} {{.Status}} {{.Branch}}"},
|
||||
expected: "1 success main\n",
|
||||
},
|
||||
{
|
||||
name: "invalid go-template",
|
||||
args: []string{"output", "--output", "go-template={{.InvalidField}}"},
|
||||
|
||||
@@ -80,6 +80,20 @@ func repoOutput(c *cli.Command, repos []*woodpecker.Repo, fd ...io.Writer) error
|
||||
if err := tmpl.Execute(out, repos); err != nil {
|
||||
return err
|
||||
}
|
||||
case "go-format":
|
||||
if len(outOpt) < 1 {
|
||||
return fmt.Errorf("%w: missing template", output.ErrOutputOptionRequired)
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(outOpt[0] + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range repos {
|
||||
if err := tmpl.Execute(out, r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case "table":
|
||||
fallthrough
|
||||
default:
|
||||
|
||||
@@ -40,6 +40,16 @@ func TestRepoOutput(t *testing.T) {
|
||||
args: []string{"output", "--output", "go-template={{range . }}{{.Name}} {{.ForgeURL}} {{.Trusted.Network}}{{end}}"},
|
||||
expected: "repo1 git.example.com true\n",
|
||||
},
|
||||
{
|
||||
name: "go-format output",
|
||||
args: []string{"output", "--output", "go-format={{.Name}} {{.ForgeURL}} {{.Trusted.Network}}"},
|
||||
expected: "repo1 git.example.com true\n",
|
||||
},
|
||||
{
|
||||
name: "invalid go-template",
|
||||
args: []string{"output", "--output", "go-template={{.InvalidField}}"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
repos := []*woodpecker.Repo{
|
||||
|
||||
+20
-9
@@ -3,6 +3,7 @@ package setup
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
@@ -26,16 +27,29 @@ var Command = &cli.Command{
|
||||
Name: "token",
|
||||
Usage: "token to authenticate with the woodpecker server",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "context",
|
||||
Aliases: []string{"ctx"},
|
||||
Usage: "name for the context (defaults to 'default')",
|
||||
},
|
||||
},
|
||||
Action: setup,
|
||||
}
|
||||
|
||||
func setup(ctx context.Context, c *cli.Command) error {
|
||||
_config, err := config.Get(ctx, c, c.String("config"))
|
||||
contextName := c.String("context")
|
||||
if contextName == "" {
|
||||
contextName = "default"
|
||||
}
|
||||
|
||||
// Check if context already exists
|
||||
contexts, err := config.LoadContexts()
|
||||
if err != nil {
|
||||
return err
|
||||
} else if _config != nil {
|
||||
setupAgain, err := ui.Confirm("The woodpecker-cli was already configured. Do you want to configure it again?")
|
||||
}
|
||||
|
||||
if existingCtx, exists := contexts.Contexts[contextName]; exists {
|
||||
setupAgain, err := ui.Confirm(fmt.Sprintf("Context '%s' already exists (server: %s). Do you want to reconfigure it?", contextName, existingCtx.ServerURL))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,16 +92,13 @@ func setup(ctx context.Context, c *cli.Command) error {
|
||||
}
|
||||
}
|
||||
|
||||
err = config.Save(ctx, c, c.String("config"), &config.Config{
|
||||
ServerURL: serverURL,
|
||||
Token: token,
|
||||
LogLevel: "info",
|
||||
})
|
||||
// Save as context
|
||||
err = config.AddOrUpdateContext(c, contextName, serverURL, token, "info", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info().Msg("woodpecker-cli has been successfully setup")
|
||||
log.Info().Msgf("Context '%s' has been successfully created and set as current", contextName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ import (
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/backend"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/shared/logger"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/shared/utils"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/version"
|
||||
@@ -296,7 +296,7 @@ func run(ctx context.Context, c *cli.Command, backends []types.Backend) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debug().Msg("polling new steps")
|
||||
log.Debug().Msg("polling new workflow")
|
||||
if err := runner.Run(agentCtx, shutdownCtx); err != nil {
|
||||
log.Error().Err(err).Msg("runner error, retrying...")
|
||||
// Check if context is canceled
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestReadAgentIDFileNotExists(t *testing.T) {
|
||||
@@ -27,16 +28,12 @@ func TestReadAgentIDFileNotExists(t *testing.T) {
|
||||
|
||||
func TestReadAgentIDFileExists(t *testing.T) {
|
||||
tmpF, errTmpF := os.CreateTemp(t.TempDir(), "tmp_")
|
||||
if !assert.NoError(t, errTmpF) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, errTmpF)
|
||||
defer os.Remove(tmpF.Name())
|
||||
|
||||
// there is an existing config
|
||||
errWrite := os.WriteFile(tmpF.Name(), []byte(`{"agent_id":3}`), 0o644)
|
||||
if !assert.NoError(t, errWrite) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, errWrite)
|
||||
|
||||
// read existing config
|
||||
actual := readAgentConfig(tmpF.Name())
|
||||
@@ -49,9 +46,7 @@ func TestReadAgentIDFileExists(t *testing.T) {
|
||||
assert.EqualValues(t, 33, actual.AgentID)
|
||||
|
||||
tmpF2, errTmpF := os.CreateTemp(t.TempDir(), "tmp_")
|
||||
if !assert.NoError(t, errTmpF) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, errTmpF)
|
||||
defer os.Remove(tmpF2.Name())
|
||||
|
||||
// write new config
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/admin"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/context"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/exec"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/info"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/cli/lint"
|
||||
@@ -47,6 +48,7 @@ func newApp() *cli.Command {
|
||||
}
|
||||
app.Commands = []*cli.Command{
|
||||
admin.Command,
|
||||
context.Command,
|
||||
exec.Command,
|
||||
info.Command,
|
||||
lint.Command,
|
||||
|
||||
+6
-1
@@ -276,7 +276,12 @@ var flags = append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_CONFIG_SERVICE_ENDPOINT"),
|
||||
Name: "config-service-endpoint",
|
||||
Usage: "url used for calling configuration service endpoint",
|
||||
Usage: "url used for calling global configuration service endpoint",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_CONFIG_SERVICE_EXCLUSIVE"),
|
||||
Name: "config-service-exclusive",
|
||||
Usage: "whether global configuration service endpoint should be exclusive (skip forge)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_EXTENSIONS_ALLOWED_HOSTS"),
|
||||
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/rpc/proto"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server"
|
||||
woodpeckerGrpcServer "go.woodpecker-ci.org/woodpecker/v3/server/grpc"
|
||||
woodpeckerGrpcServer "go.woodpecker-ci.org/woodpecker/v3/server/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/store"
|
||||
)
|
||||
|
||||
|
||||
+136
-51
@@ -1729,7 +1729,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Secret"
|
||||
"$ref": "#/definitions/SecretPatch"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -2681,7 +2681,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Cron"
|
||||
"$ref": "#/definitions/CronPatch"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -3921,7 +3921,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Secret"
|
||||
"$ref": "#/definitions/SecretPatch"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -4113,7 +4113,7 @@ const docTemplate = `{
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Secret"
|
||||
"$ref": "#/definitions/SecretPatch"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -4678,6 +4678,17 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"CancelInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"canceled_by_user": {
|
||||
"type": "string"
|
||||
},
|
||||
"superseded_by": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -4705,8 +4716,12 @@ const docTemplate = `{
|
||||
"type": "integer"
|
||||
},
|
||||
"creator_id": {
|
||||
"description": "TODO: drop with next major version",
|
||||
"type": "integer"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -4722,6 +4737,35 @@ const docTemplate = `{
|
||||
"schedule": {
|
||||
"description": "@weekly,\t3min, ...",
|
||||
"type": "string"
|
||||
},
|
||||
"variables": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"CronPatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"branch": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string"
|
||||
},
|
||||
"variables": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4949,6 +4993,9 @@ const docTemplate = `{
|
||||
"branch": {
|
||||
"type": "string"
|
||||
},
|
||||
"cancel_info": {
|
||||
"$ref": "#/definitions/CancelInfo"
|
||||
},
|
||||
"changed_files": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -4970,7 +5017,7 @@ const docTemplate = `{
|
||||
"errors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/types.PipelineError"
|
||||
"$ref": "#/definitions/errors.PipelineError"
|
||||
}
|
||||
},
|
||||
"event": {
|
||||
@@ -5190,6 +5237,9 @@ const docTemplate = `{
|
||||
"config_extension_endpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"config_extension_exclusive": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"config_file": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5283,6 +5333,9 @@ const docTemplate = `{
|
||||
"config_extension_endpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"config_extension_exclusive": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"config_file": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5367,6 +5420,9 @@ const docTemplate = `{
|
||||
"config_extension_endpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"config_extension_exclusive": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"config_file": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -5424,6 +5480,9 @@ const docTemplate = `{
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"org_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
@@ -5435,6 +5494,32 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"SecretPatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"events": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/WebhookEvent"
|
||||
}
|
||||
},
|
||||
"images": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"StatusValue": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -5651,6 +5736,52 @@ const docTemplate = `{
|
||||
"EventManual"
|
||||
]
|
||||
},
|
||||
"errors.PipelineError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"is_warning": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/errors.PipelineErrorType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors.PipelineErrorType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"linter",
|
||||
"deprecation",
|
||||
"compiler",
|
||||
"generic",
|
||||
"bad_habit"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"PipelineErrorTypeBadHabit": "some bad-habit error",
|
||||
"PipelineErrorTypeCompiler": "some error with the config semantics",
|
||||
"PipelineErrorTypeDeprecation": "using some deprecated feature",
|
||||
"PipelineErrorTypeGeneric": "some generic error",
|
||||
"PipelineErrorTypeLinter": "some error with the config syntax"
|
||||
},
|
||||
"x-enum-descriptions": [
|
||||
"some error with the config syntax",
|
||||
"using some deprecated feature",
|
||||
"some error with the config semantics",
|
||||
"some generic error",
|
||||
"some bad-habit error"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PipelineErrorTypeLinter",
|
||||
"PipelineErrorTypeDeprecation",
|
||||
"PipelineErrorTypeCompiler",
|
||||
"PipelineErrorTypeGeneric",
|
||||
"PipelineErrorTypeBadHabit"
|
||||
]
|
||||
},
|
||||
"metadata.Author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -6071,52 +6202,6 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/StatusValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"types.PipelineError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"is_warning": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/types.PipelineErrorType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"types.PipelineErrorType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"linter",
|
||||
"deprecation",
|
||||
"compiler",
|
||||
"generic",
|
||||
"bad_habit"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"PipelineErrorTypeBadHabit": "some bad-habit error",
|
||||
"PipelineErrorTypeCompiler": "some error with the config semantics",
|
||||
"PipelineErrorTypeDeprecation": "using some deprecated feature",
|
||||
"PipelineErrorTypeGeneric": "some generic error",
|
||||
"PipelineErrorTypeLinter": "some error with the config syntax"
|
||||
},
|
||||
"x-enum-descriptions": [
|
||||
"some error with the config syntax",
|
||||
"using some deprecated feature",
|
||||
"some error with the config semantics",
|
||||
"some generic error",
|
||||
"some bad-habit error"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"PipelineErrorTypeLinter",
|
||||
"PipelineErrorTypeDeprecation",
|
||||
"PipelineErrorTypeCompiler",
|
||||
"PipelineErrorTypeGeneric",
|
||||
"PipelineErrorTypeBadHabit"
|
||||
]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
+1
-1
@@ -25,8 +25,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/tink/go/subtle/random"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/tink-crypto/tink-go/v2/subtle/random"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ignore:
|
||||
- '**/mocks/mock_*.go'
|
||||
@@ -3,7 +3,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
gitea-database:
|
||||
image: postgres:18.1-alpine
|
||||
image: postgres:18.2-alpine
|
||||
environment:
|
||||
POSTGRES_USER: gitea
|
||||
POSTGRES_PASSWORD: 123456
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.25 AS build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS build
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.25 AS build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS build
|
||||
|
||||
RUN groupadd -g 1000 woodpecker && \
|
||||
useradd -u 1000 -g 1000 woodpecker && \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.25 AS build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS build
|
||||
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.25 AS build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS build
|
||||
|
||||
RUN groupadd -g 1000 woodpecker && \
|
||||
useradd -u 1000 -g 1000 woodpecker
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# docker build --rm -f docker/Dockerfile.make -t woodpecker/make:local .
|
||||
FROM docker.io/golang:1.25-alpine AS golang_image
|
||||
FROM docker.io/golang:1.26-alpine AS golang_image
|
||||
FROM docker.io/node:24-alpine
|
||||
|
||||
RUN apk add --no-cache --update make gcc binutils-gold musl-dev && \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.25 AS build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS build
|
||||
|
||||
RUN groupadd -g 1000 woodpecker && \
|
||||
useradd -u 1000 -g 1000 woodpecker && \
|
||||
|
||||
@@ -35,3 +35,63 @@ git fetch --no-tags origin +refs/heads/branch:
|
||||
```
|
||||
|
||||
(replace the url AND the branch with the correct values, use your username and password as log in values)
|
||||
|
||||
## SELinux Issues
|
||||
|
||||
When running Woodpecker on systems with SELinux enabled (such as RHEL, CentOS, Fedora, or other Enterprise Linux distributions), SELinux may prevent the agent from accessing the Docker socket.
|
||||
|
||||
### Symptoms
|
||||
|
||||
If SELinux is blocking access, you may see errors like:
|
||||
|
||||
```text
|
||||
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
|
||||
```
|
||||
|
||||
### Solutions
|
||||
|
||||
There are several ways to resolve this:
|
||||
|
||||
#### Option 1: Set SELinux to Permissive Mode (For Testing Only)
|
||||
|
||||
Set SELinux to permissive mode temporarily to verify it's the issue:
|
||||
|
||||
```bash
|
||||
setenforce 0
|
||||
```
|
||||
|
||||
To permanently set SELinux to permissive mode:
|
||||
|
||||
```bash
|
||||
# Edit /etc/selinux/config
|
||||
SELINUX=permissive
|
||||
```
|
||||
|
||||
#### Option 2: Configure SELinux Policy (Recommended)
|
||||
|
||||
Create a custom SELinux policy to allow Woodpecker agent to access Docker:
|
||||
|
||||
```bash
|
||||
# Generate the policy module
|
||||
ausearch -c 'docker' -avc | audit2allow -R -o woodpecker-docker.te
|
||||
# Build the policy module
|
||||
checkmodule -M -m -o woodpecker-docker.mod woodpecker-docker.te
|
||||
semodule_package -o woodpecker-docker.pp -m woodpecker-docker.mod
|
||||
# Load the policy module
|
||||
semodule -i woodpecker-docker.pp
|
||||
```
|
||||
|
||||
#### Option 3: Use Docker Volume with SELinux Options
|
||||
|
||||
When using Docker Compose or Docker, add the `:z` or `:Z` option to volume mounts:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:z
|
||||
```
|
||||
|
||||
The `:z` option tells Docker to automatically relabel the volume content for SELinux. Use `:Z` with caution as it relabels the volume exclusively for this container.
|
||||
|
||||
#### Option 4: Use Podman (Alternative)
|
||||
|
||||
If you prefer to avoid SELinux configuration issues, consider using Podman instead of Docker, as it has better SELinux integration.
|
||||
|
||||
@@ -37,6 +37,20 @@ services:
|
||||
- 51820/udp
|
||||
```
|
||||
|
||||
## Stopping
|
||||
|
||||
Services that are no longer needed receive a **SIGTERM** signal. If they do not respond, they are forcibly terminated with **SIGKILL**.
|
||||
If there are services that do not shut down properly and this doesn't matter, you can simply ignore the error:
|
||||
|
||||
```diff
|
||||
services:
|
||||
- name: database
|
||||
image: mysql
|
||||
+ failure: ignore # we don't care how mysql exits
|
||||
ports:
|
||||
- 3306
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Service containers generally expose environment variables to customize service startup such as default usernames, passwords and ports. Please see the official image documentation to learn more.
|
||||
@@ -46,8 +60,8 @@ Service containers generally expose environment variables to customize service s
|
||||
- name: database
|
||||
image: mysql
|
||||
+ environment:
|
||||
+ - MYSQL_DATABASE=test
|
||||
+ - MYSQL_ALLOW_EMPTY_PASSWORD=yes
|
||||
+ MYSQL_DATABASE: test
|
||||
+ MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||
|
||||
- name: cache
|
||||
image: redis
|
||||
@@ -102,8 +116,8 @@ services:
|
||||
- name: database
|
||||
image: mysql
|
||||
environment:
|
||||
- MYSQL_DATABASE=test
|
||||
- MYSQL_ROOT_PASSWORD=example
|
||||
MYSQL_DATABASE: test
|
||||
MYSQL_ROOT_PASSWORD: example
|
||||
steps:
|
||||
- name: get-version
|
||||
image: ubuntu
|
||||
|
||||
@@ -23,7 +23,7 @@ As Woodpecker will pass private information like tokens and will execute the ret
|
||||
In addition to the ability to configure the extension per repository, you can also configure a global endpoint in the Woodpecker server configuration. This can be useful if you want to use the extension for all repositories. Be careful if
|
||||
you share your Woodpecker server with others as they will also use your configuration extension.
|
||||
|
||||
The global configuration will be called before the repository specific configuration extension if both are configured.
|
||||
The global configuration will be called before the repository specific configuration extension if both are configured and the repository has not enabled the exclusive setting.
|
||||
|
||||
```ini title="Server"
|
||||
WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig
|
||||
@@ -33,6 +33,8 @@ WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig
|
||||
|
||||
When a pipeline is triggered Woodpecker will fetch the pipeline configuration from the repository, then make a HTTP POST request to the configured extension with a JSON payload containing some data like the repository, pipeline information and the current config files retrieved from the repository. The extension can then send back modified or even new pipeline configurations following Woodpeckers official yaml format that should be used.
|
||||
|
||||
You can enable the exclusive setting (both globally and on a per-repo level). Then Woodpecker will only call your extension, but nothing else. This allows you to completely skip the forge. Requests sent to the extension will not have the configuration files added.
|
||||
|
||||
### Request
|
||||
|
||||
The extension receives an HTTP POST request with the following JSON payload:
|
||||
@@ -43,6 +45,7 @@ class Request {
|
||||
pipeline: Pipeline;
|
||||
netrc: Netrc;
|
||||
configuration: {
|
||||
// list of configurations. Not send if there was none.
|
||||
name: string; // filename of the configuration file
|
||||
data: string; // content of the configuration file
|
||||
}[];
|
||||
@@ -123,7 +126,7 @@ Example request:
|
||||
"updated_at": 0,
|
||||
"verified": false
|
||||
},
|
||||
"configs": [
|
||||
"configuration": [
|
||||
{
|
||||
"name": ".woodpecker.yaml",
|
||||
"data": "steps:\n - name: backend\n image: alpine\n commands:\n - echo \"Hello there from Repo (.woodpecker.yaml)\"\n"
|
||||
|
||||
@@ -140,3 +140,14 @@ To store values in a docker secret you can use the following command:
|
||||
```bash
|
||||
echo "my_agent_secret_key" | docker secret create woodpecker-agent-secret -
|
||||
```
|
||||
|
||||
## SELinux Considerations
|
||||
|
||||
If you're running Woodpecker on a system with SELinux enabled (RHEL, CentOS, Fedora, etc.), you may need to add the `:z` or `:Z` option to volume mounts. For the Docker socket volume:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:z
|
||||
```
|
||||
|
||||
For more details and other SELinux-related solutions, see the [Troubleshooting](../../20-usage/100-troubleshooting.md#selinux-issues) page.
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
Woodpecker provides a [Helm chart](https://github.com/woodpecker-ci/helm) for Kubernetes environments:
|
||||
|
||||
```bash
|
||||
helm repo add woodpecker oci://ghcr.io/woodpecker-ci/helm
|
||||
helm install woodpecker woodpecker/woodpecker
|
||||
helm install woodpecker oci://ghcr.io/woodpecker-ci/helm/woodpecker --version <VERSION>
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
@@ -92,6 +92,7 @@ Woodpecker itself is not responsible for creating these packages. Please reach o
|
||||
- [YunoHost](https://apps.yunohost.org/app/woodpecker)
|
||||
- [Cloudron](https://www.cloudron.io/store/org.woodpecker_ci.cloudronapp.html)
|
||||
- [Easypanel](https://easypanel.io/docs/templates/woodpeckerci)
|
||||
- [Homebrew](https://formulae.brew.sh/formula/woodpecker-cli) (CLI only)
|
||||
|
||||
### NixOS
|
||||
|
||||
|
||||
@@ -402,97 +402,6 @@ woodpecker_waiting_steps 0
|
||||
woodpecker_worker_count 4
|
||||
```
|
||||
|
||||
## External Configuration API
|
||||
|
||||
To provide additional management and preprocessing capabilities for pipeline configurations Woodpecker supports an HTTP API which can be enabled to call an external config service.
|
||||
Before the run or restart of any pipeline Woodpecker will make a POST request to an external HTTP API sending the current repository, build information and all current config files retrieved from the repository. The external API can then send back new pipeline configurations that will be used immediately or respond with `HTTP 204` to tell the system to use the existing configuration.
|
||||
|
||||
Every request sent by Woodpecker is signed using a [http-signature](https://datatracker.ietf.org/doc/html/rfc9421) by a private key (ed25519) generated on the first start of the Woodpecker server. You can get the public key for the verification of the http-signature from `http(s)://your-woodpecker-server/api/signature/public-key`.
|
||||
|
||||
A simplistic example configuration service can be found here: [https://github.com/woodpecker-ci/example-config-service](https://github.com/woodpecker-ci/example-config-service)
|
||||
|
||||
:::warning
|
||||
You need to trust the external config service as it is getting secret information about the repository and pipeline and has the ability to change pipeline configs that could run malicious tasks.
|
||||
:::
|
||||
|
||||
### Configuration
|
||||
|
||||
```ini title="Server"
|
||||
WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig
|
||||
```
|
||||
|
||||
#### Example request made by Woodpecker
|
||||
|
||||
```json
|
||||
{
|
||||
"repo": {
|
||||
"id": 100,
|
||||
"uid": "",
|
||||
"user_id": 0,
|
||||
"namespace": "",
|
||||
"name": "woodpecker-test-pipe",
|
||||
"slug": "",
|
||||
"scm": "git",
|
||||
"git_http_url": "",
|
||||
"git_ssh_url": "",
|
||||
"link": "",
|
||||
"default_branch": "",
|
||||
"private": true,
|
||||
"visibility": "private",
|
||||
"active": true,
|
||||
"config": "",
|
||||
"trusted": false,
|
||||
"protected": false,
|
||||
"ignore_forks": false,
|
||||
"ignore_pulls": false,
|
||||
"cancel_pulls": false,
|
||||
"timeout": 60,
|
||||
"counter": 0,
|
||||
"synced": 0,
|
||||
"created": 0,
|
||||
"updated": 0,
|
||||
"version": 0
|
||||
},
|
||||
"pipeline": {
|
||||
"author": "myUser",
|
||||
"author_avatar": "https://myforge.com/avatars/d6b3f7787a685fcdf2a44e2c685c7e03",
|
||||
"author_email": "my@email.com",
|
||||
"branch": "main",
|
||||
"changed_files": ["some-file-name.txt"],
|
||||
"commit": "2fff90f8d288a4640e90f05049fe30e61a14fd50",
|
||||
"created_at": 0,
|
||||
"deploy_to": "",
|
||||
"enqueued_at": 0,
|
||||
"error": "",
|
||||
"event": "push",
|
||||
"finished_at": 0,
|
||||
"id": 0,
|
||||
"link_url": "https://myforge.com/myUser/woodpecker-testpipe/commit/2fff90f8d288a4640e90f05049fe30e61a14fd50",
|
||||
"message": "test old config\n",
|
||||
"number": 0,
|
||||
"parent": 0,
|
||||
"ref": "refs/heads/main",
|
||||
"refspec": "",
|
||||
"clone_url": "",
|
||||
"reviewed_at": 0,
|
||||
"reviewed_by": "",
|
||||
"sender": "myUser",
|
||||
"signed": false,
|
||||
"started_at": 0,
|
||||
"status": "",
|
||||
"timestamp": 1645962783,
|
||||
"title": "",
|
||||
"updated_at": 0,
|
||||
"verified": false
|
||||
},
|
||||
"netrc": {
|
||||
"machine": "https://example.com",
|
||||
"login": "user",
|
||||
"password": "password"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Example response structure
|
||||
|
||||
```json
|
||||
@@ -1067,7 +976,20 @@ Supported variables:
|
||||
- Name: `WOODPECKER_CONFIG_SERVICE_ENDPOINT`
|
||||
- Default: none
|
||||
|
||||
Specify a configuration service endpoint, see [Configuration Extension](#external-configuration-api)
|
||||
Specify a configuration service endpoint, see [Configuration Extension](../../20-usage/72-extensions/40-configuration-extension.md)
|
||||
|
||||
---
|
||||
|
||||
### CONFIG_SERVICE_EXCLUSIVE
|
||||
|
||||
- Name: `CONFIG_SERVICE_EXCLUSIVE`
|
||||
- Default: false
|
||||
|
||||
Whether the forge request should be skipped for the global configuration endpoint.
|
||||
|
||||
:::warning
|
||||
If you enable this, all repos will exclusively use the global config service endpoint. There is no possibility to directly define pipelines in the forge, except the extension handles this case itself as well.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -33,8 +33,29 @@ To configure the Docker network if the network's name is `gitea`, configure it l
|
||||
|
||||
## Registration
|
||||
|
||||
### User OAuth Application
|
||||
|
||||
Register your application with Gitea to create your client id and secret. You can find the OAuth applications settings of Gitea at `https://gitea.<host>/user/settings/`. It is very important that authorization callback URL matches your http(s) scheme and hostname exactly with `https://<host>/authorize` as the path.
|
||||
|
||||
### System-wide OAuth Application
|
||||
|
||||
If you are the administrator of both Gitea and Woodpecker, you may prefer to use a system-wide OAuth application instead of a user-level application. System-wide applications are managed at the Gitea site administrator level and are visible to all users.
|
||||
|
||||
To create a system-wide OAuth application in Gitea:
|
||||
|
||||
1. Navigate to the site administration settings at `https://gitea.<host>/admin/settings/applications`
|
||||
2. Create a new OAuth2 application under the "OAuth2 Applications" section
|
||||
3. Configure the application with the same settings as above (callback URL, etc.)
|
||||
4. Use the generated client id and secret for Woodpecker configuration
|
||||
|
||||
System-wide applications are particularly useful for:
|
||||
|
||||
- Shared CI/CD environments where multiple users need Woodpecker access
|
||||
- Organizations that want centralized control over OAuth applications
|
||||
- Preventing user-level application quotas from affecting CI/CD operations
|
||||
|
||||
### Local Connections
|
||||
|
||||
If you run the Woodpecker CI server on the same host as the Gitea instance, you might also need to allow local connections in Gitea, since version `v1.16`. Otherwise webhooks will fail. Add the following lines to your Gitea configuration (usually at `/etc/gitea/conf/app.ini`).
|
||||
|
||||
```ini
|
||||
|
||||
@@ -33,8 +33,29 @@ To configure the Docker network if the network's name is `forgejo`, configure it
|
||||
|
||||
## Registration
|
||||
|
||||
### User OAuth Application
|
||||
|
||||
Register your application with Forgejo to create your client id and secret. You can find the OAuth applications settings of Forgejo at `https://forgejo.<host>/user/settings/`. It is very important that authorization callback URL matches your http(s) scheme and hostname exactly with `https://<host>/authorize` as the path.
|
||||
|
||||
### System-wide OAuth Application
|
||||
|
||||
If you are the administrator of both Forgejo and Woodpecker, you may prefer to use a system-wide OAuth application instead of a user-level application. System-wide applications are managed at the Forgejo site administrator level and are visible to all users.
|
||||
|
||||
To create a system-wide OAuth application in Forgejo:
|
||||
|
||||
1. Navigate to the site administration settings at `https://forgejo.<host>/admin/settings/applications`
|
||||
2. Create a new OAuth2 application under the "OAuth2 Applications" section
|
||||
3. Configure the application with the same settings as above (callback URL, etc.)
|
||||
4. Use the generated client id and secret for Woodpecker configuration
|
||||
|
||||
System-wide applications are particularly useful for:
|
||||
|
||||
- Shared CI/CD environments where multiple users need Woodpecker access
|
||||
- Organizations that want centralized control over OAuth applications
|
||||
- Preventing user-level application quotas from affecting CI/CD operations
|
||||
|
||||
### Local Connections
|
||||
|
||||
If you run the Woodpecker CI server on the same host as the Forgejo instance, you might also need to allow local connections in Forgejo. Otherwise webhooks will fail. Add the following lines to your Forgejo configuration (usually at `/etc/forgejo/conf/app.ini`).
|
||||
|
||||
```ini
|
||||
|
||||
@@ -20,7 +20,7 @@ Start Woodpecker in Gitpod by clicking on the following badge. You can log in wi
|
||||
|
||||
### Install Go
|
||||
|
||||
Install Golang (>=1.20) as described by [this guide](https://go.dev/doc/install).
|
||||
Install Golang as described by [this guide](https://go.dev/doc/install).
|
||||
|
||||
### Install make
|
||||
|
||||
@@ -34,7 +34,7 @@ Install make on:
|
||||
|
||||
### Install Node.js & `pnpm`
|
||||
|
||||
Install [Node.js (>=20)](https://nodejs.org/en/download/package-manager) if you want to build Woodpecker's UI or documentation.
|
||||
Install [Node.js](https://nodejs.org/en/download/package-manager) if you want to build Woodpecker's UI or documentation.
|
||||
|
||||
For dependency installation (`node_modules`) of UI and documentation of Woodpecker the package manager pnpm is used.
|
||||
[This guide](https://pnpm.io/installation) describes the installation of `pnpm`.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
## Addons and extensions
|
||||
|
||||
If you are wondering whether your contribution will be accepted to be merged in the Woodpecker core, or whether it's better to write an
|
||||
[addon](../30-administration/10-configuration/100-addons.md), [extension](../30-administration/10-configuration/10-server.md#external-configuration-api) or an
|
||||
[addon](../30-administration/10-configuration/100-addons.md), [extension](../20-usage/72-extensions/40-configuration-extension.md) or an
|
||||
[external custom backend](../30-administration/10-configuration/11-backends/50-custom.md), please check these points:
|
||||
|
||||
- Is your change very specific to your setup and unlikely to be used by anyone else?
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
# Architecture
|
||||
|
||||
## Package architecture
|
||||
## Module Interactions
|
||||
|
||||

|
||||

|
||||
|
||||
<!--
|
||||
To update the graph, first look at a simple svg of all module imports:
|
||||
`go run github.com/loov/goda@latest graph 'go.woodpecker-ci.org/woodpecker/v3/...' | dot -Tsvg -o graph.svg`
|
||||
|
||||
generate a new svg of the graph using:
|
||||
`dot -Tsvg woodpecker-architecture.dot -o woodpecker-architecture.svg`
|
||||
-->
|
||||
|
||||
## System architecture
|
||||
|
||||
@@ -11,38 +19,70 @@
|
||||
| package | meaning | imports |
|
||||
| ------------------ | -------------------------------------------------------------- | ------------------------------------- |
|
||||
| `cmd/**` | parse command-line args & environment to stat server/cli/agent | all other |
|
||||
| `agent/**` | code only agent (remote worker) will need | `pipeline`, `shared` |
|
||||
| `agent/**` | code only agent (remote worker) will need | `pipeline`, `rpc`, `shared` |
|
||||
| `cli/**` | code only cli tool does need | `pipeline`, `shared`, `woodpecker-go` |
|
||||
| `server/**` | code only server will need | `pipeline`, `shared` |
|
||||
| `server/**` | code only server will need | `pipeline`, `rpc`, `shared` |
|
||||
| `pipeline/**` | core ci/cd engine from parsing to execution | `shared` |
|
||||
| `rpc/**` | RPC interface for agent-server communication | `pipeline` |
|
||||
| `shared/**` | code shared for all three main tools (go help utils) | only std and external libs |
|
||||
| `woodpecker-go/**` | go client for server rest api | std |
|
||||
|
||||
### Server
|
||||
|
||||
| package | meaning | imports |
|
||||
| -------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `server/api/**` | handle web requests from `server/router` | `pipeline`, `../badges`, `../ccmenu`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../shared`, `../store`, `shared`, (TODO: mv `server/router/middleware/session`) |
|
||||
| `server/badges/**` | generate svg badges for pipelines | `../model` |
|
||||
| `server/ccmenu/**` | generate xml ccmenu for pipelines | `../model` |
|
||||
| `server/grpc/**` | gRPC server agents can connect to | `pipeline/rpc/**`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../pipeline`, `../store` |
|
||||
| `server/logging/**` | logging lib for gPRC server to stream logs while running | std |
|
||||
| `server/model/**` | structs for store (db) and api (json) | std |
|
||||
| `server/plugins/**` | plugins for server | `../model`, `../forge` |
|
||||
| `server/pipeline/**` | orchestrate pipelines | `pipeline`, `../model`, `../pubsub`, `../queue`, `../forge`, `../store`, `../plugins` |
|
||||
| `server/pubsub/**` | pubsub lib for server to push changes to the WebUI | std |
|
||||
| `server/queue/**` | queue lib for server where agents pull new pipelines from via gRPC | `server/model` |
|
||||
| `server/forge/**` | forge lib for server to connect and handle forge specific stuff | `shared`, `server/model` |
|
||||
| `server/router/**` | handle requests to REST API (and all middleware) and serve UI and WebUI config | `shared`, `../api`, `../model`, `../forge`, `../store`, `../web` |
|
||||
| `server/store/**` | handle database | `server/model` |
|
||||
| `server/shared/**` | TODO: move and split [#974](https://github.com/woodpecker-ci/woodpecker/issues/974) | |
|
||||
| `server/web/**` | server SPA | |
|
||||
| package | meaning | imports |
|
||||
| -------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `server/api/**` | handle web requests from `server/router` | `pipeline`, `rpc`, `../badges`, `../ccmenu`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../shared`, `../store`, `shared`, (TODO: mv `server/router/middleware/session`) |
|
||||
| `server/badges/**` | generate svg badges for pipelines | `../model` |
|
||||
| `server/ccmenu/**` | generate xml ccmenu for pipelines | `../model` |
|
||||
| `server/rpc/**` | gRPC server agents can connect to | `rpc`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../pipeline`, `../store` |
|
||||
| `server/logging/**` | logging lib for gPRC server to stream logs while running | std |
|
||||
| `server/model/**` | structs for store (db) and api (json) | std |
|
||||
| `server/pipeline/**` | orchestrate pipelines (TODO: parts of it should move into /pipeline) | `pipeline`, `../model`, `../pubsub`, `../queue`, `../forge`, `../store`, `../plugins` |
|
||||
| `server/pubsub/**` | pubsub lib for server to push changes to the WebUI | std |
|
||||
| `server/queue/**` | queue lib for server where agents pull new pipelines from via gRPC | `server/model` |
|
||||
| `server/forge/**` | forge lib for server to connect and handle forge specific stuff | `shared`, `server/model` |
|
||||
| `server/router/**` | handle requests to REST API (and all middleware) and serve UI and WebUI config | `shared`, `../api`, `../model`, `../forge`, `../store`, `../web` |
|
||||
| `server/store/**` | handle database | `server/model` |
|
||||
| `server/web/**` | server SPA | |
|
||||
|
||||
- `../` = `server/`
|
||||
|
||||
### Agent
|
||||
|
||||
TODO
|
||||
| package | meaning | imports |
|
||||
| -------------- | ---------------------------------------------------- | ------------------------------------------------------ |
|
||||
| `agent/**` | agent implementation that runs workflows | `pipeline`, `rpc`, `shared` |
|
||||
| `agent/rpc/**` | gRPC client for agent-server communication | `rpc`, `pipeline/backend/types`, std and external libs |
|
||||
| `cmd/agent/**` | CLI interface for starting and configuring the agent | `agent`, std and external libs |
|
||||
|
||||
The agent is a remote worker that connects to the server via gRPC to receive pipeline execution instructions and report back execution state and logs.
|
||||
The agent polls the server's queue for new work, executes pipeline steps using the pipeline engine, and streams results back to the server.
|
||||
|
||||
TODO: Review cmd/agent/core to determine if any logic should be moved into the agent package for better separation of concerns.
|
||||
|
||||
### CLI
|
||||
|
||||
TODO
|
||||
| package | meaning | imports |
|
||||
| ------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| `cli/admin/**` | admin commands for server management (users, secrets, registries, etc.) | `../common`, `../internal`, `woodpecker-go` |
|
||||
| `cli/common/**` | shared utilities and helpers used across all CLI subcommands | `../internal/config`, `../update`, `shared` |
|
||||
| `cli/context/**` | manage multiple server contexts (connections to different servers) | `../common`, `../internal/config`, `../output` |
|
||||
| `cli/exec/**` | execute pipelines locally without server orchestration | `pipeline`, `../common`, `../lint`, `shared` |
|
||||
| `cli/info/**` | display information about the current user | `../common`, `../internal` |
|
||||
| `cli/internal/**` | internal utilities for HTTP client, auth, and server communication | `../internal/config`, `woodpecker-go`, `shared` |
|
||||
| `cli/internal/config/**` | configuration file management (load, store, credentials) | std and external libs |
|
||||
| `cli/lint/**` | validate pipeline configuration files | `pipeline/frontend/yaml`, `pipeline/frontend/yaml/linter`, `../common`, `shared` |
|
||||
| `cli/org/**` | manage organization-level resources (secrets, registries) | `../common`, `../internal`, `woodpecker-go` |
|
||||
| `cli/output/**` | formatting utilities for CLI output (tables, etc.) | std and external libs |
|
||||
| `cli/pipeline/**` | manage pipeline operations (start, stop, approve, logs, etc.) | `../common`, `../internal`, `../output`, `woodpecker-go`, `shared` |
|
||||
| `cli/repo/**` | manage repository-level resources (repos, crons, secrets, registries) | `../common`, `../internal`, `../output`, `woodpecker-go` |
|
||||
| `cli/setup/**` | interactive first-time setup wizard for CLI configuration | `../internal/config` |
|
||||
| `cli/update/**` | self-updater for the CLI binary | std and external libs |
|
||||
| `cmd/cli/**` | CLI entry point and command structure | `cli/**` |
|
||||
|
||||
The CLI provides a command-line interface for interacting with Woodpecker servers.
|
||||
Each subcommand is organized into its own package under `cli/<subcommand>/`.
|
||||
|
||||
The `cli/exec` subcommand allows local pipeline execution for testing and development by combining pipeline parsing and execution without requiring a running server or agent.
|
||||
|
||||
- `../` = `cli/`
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Deprecation Policy
|
||||
|
||||
## Pipeline Configuration Changes
|
||||
|
||||
Pipeline configuration (YAML syntax) changes follow a strict deprecation process to ensure users have sufficient time to migrate.
|
||||
|
||||
### Process Timeline
|
||||
|
||||
1. **Minor Version N.x - Add Deprecation Warning**
|
||||
- Linter shows a warning (not an error)
|
||||
- Old syntax remains functional
|
||||
- Documentation is updated to reflect the new syntax
|
||||
- Warning message includes guidance on required changes
|
||||
|
||||
2. **Major Version (N+1).0 - Warning Becomes Error**
|
||||
- Linter issues an error (pipeline fails)
|
||||
- Old syntax is no longer supported
|
||||
- Breaking change is documented in the migration guide
|
||||
- Users **must** update their configurations
|
||||
|
||||
3. **Minor Version (N+1).x - Code Cleanup**
|
||||
- Deprecated code paths are removed
|
||||
- Implementation is simplified/refactored
|
||||
- Parser no longer recognizes the old syntax
|
||||
|
||||
### Example
|
||||
|
||||
Old syntax: `secrets: [token]`
|
||||
New syntax: `environment: { TOKEN: { from_secret: token } }`
|
||||
|
||||
- **v2.5.0:** Deprecation warning added in linter; both syntaxes work
|
||||
- **v2.6-2.9:** Warning persists; both syntaxes remain functional
|
||||
- **v3.0.0:** Linter error; old syntax fails (breaking change)
|
||||
- **v3.1.0:** Deprecated code paths removed; parser simplified
|
||||
|
||||
### Implementation Checklist
|
||||
|
||||
When deprecating pipeline configuration syntax, ensure the following:
|
||||
|
||||
- [ ] Add linter warning in `/pipeline/frontend/yaml/linter/`
|
||||
- [ ] Update JSON schema in `/pipeline/frontend/yaml/linter/schema`
|
||||
- [ ] Add test cases for deprecated syntax
|
||||
- [ ] Update documentation to reflect the new syntax
|
||||
@@ -0,0 +1,157 @@
|
||||
digraph WoodpeckerArchitecture {
|
||||
graph [
|
||||
rankdir=TB,
|
||||
splines=ortho,
|
||||
nodesep=0.5,
|
||||
ranksep=0.8,
|
||||
fontname="Helvetica"
|
||||
]
|
||||
|
||||
node [
|
||||
shape=box,
|
||||
style="rounded,filled",
|
||||
fillcolor="#2b2b2b",
|
||||
fontcolor="white",
|
||||
fontname="Helvetica"
|
||||
]
|
||||
|
||||
edge [
|
||||
color="#bdbdbd",
|
||||
arrowsize=0.7
|
||||
]
|
||||
|
||||
/* ===================== UI ===================== */
|
||||
subgraph cluster_ui {
|
||||
label="UI"
|
||||
fillcolor="#c7efe9"
|
||||
fontcolor="black"
|
||||
style="rounded,filled"
|
||||
|
||||
ui_web [label="web/"]
|
||||
}
|
||||
|
||||
/* ===================== SDK ===================== */
|
||||
subgraph cluster_sdk {
|
||||
label="SDK (woodpecker-go)"
|
||||
fillcolor="#e8f5e9"
|
||||
fontcolor="black"
|
||||
style="rounded,filled"
|
||||
|
||||
sdk [label="woodpecker-go"]
|
||||
}
|
||||
|
||||
/* ===================== CLI ===================== */
|
||||
subgraph cluster_cli {
|
||||
label="woodpecker-cli"
|
||||
fillcolor="#bfe9e0"
|
||||
fontcolor="black"
|
||||
style="rounded,filled"
|
||||
|
||||
cli_cmd [label="cmd/cli/"]
|
||||
cli_core [label="cli/"]
|
||||
}
|
||||
|
||||
/* ===================== Agent ===================== */
|
||||
subgraph cluster_agent {
|
||||
label="woodpecker-agent"
|
||||
fillcolor="#ffe0c7"
|
||||
fontcolor="black"
|
||||
style="rounded,filled"
|
||||
|
||||
agent_cmd [label="cmd/agent/"]
|
||||
agent_core [label="agent/"]
|
||||
}
|
||||
|
||||
/* ===================== Pipelines ===================== */
|
||||
subgraph cluster_pipelines {
|
||||
label="Pipelines"
|
||||
fillcolor="#ffe8d6"
|
||||
fontcolor="black"
|
||||
style="rounded,filled"
|
||||
|
||||
pipe_core [label="pipeline/"]
|
||||
pipe_frontend [label="pipeline/frontend/\n(yaml)"]
|
||||
pipe_backend [label="pipeline/backend/\n(exec engines)"]
|
||||
}
|
||||
|
||||
/* ===================== Server ===================== */
|
||||
subgraph cluster_server {
|
||||
label="woodpecker-server"
|
||||
fillcolor="#dbe9ff"
|
||||
fontcolor="black"
|
||||
style="rounded,filled"
|
||||
|
||||
srv_cmd [label="cmd/server/"]
|
||||
srv_router [label="server/router/"]
|
||||
srv_api [label="server/api/"]
|
||||
srv_grpc [label="server/rpc/"]
|
||||
srv_queue [label="server/queue/"]
|
||||
srv_pubsub [label="server/pubsub/"]
|
||||
srv_store [label="server/store/"]
|
||||
srv_model [label="server/model/"]
|
||||
srv_forge [label="server/forge/"]
|
||||
}
|
||||
|
||||
/* ===================== Shared Libs ===================== */
|
||||
subgraph cluster_shared {
|
||||
label="Shared Libs"
|
||||
fillcolor="#eeeeee"
|
||||
fontcolor="black"
|
||||
style="rounded,filled"
|
||||
|
||||
shared_util [label="shared/util/"]
|
||||
shared_token [label="shared/token/"]
|
||||
shared_http [label="shared/httputil/"]
|
||||
shared_log [label="shared/logger/"]
|
||||
}
|
||||
|
||||
/* ===================== External ===================== */
|
||||
subgraph cluster_external {
|
||||
label="External Systems"
|
||||
style="rounded,dashed"
|
||||
fontcolor="white"
|
||||
|
||||
ext_scm [label="SCM Providers", shape=cloud]
|
||||
ext_db [label="Database", shape=cylinder]
|
||||
}
|
||||
|
||||
/* ===================== Runtime Interactions ===================== */
|
||||
|
||||
/* UI */
|
||||
ui_web -> srv_router [xlabel="HTTP"]
|
||||
ui_web -> srv_api [xlabel="REST API"]
|
||||
|
||||
/* CLI */
|
||||
cli_cmd -> cli_core
|
||||
cli_core -> sdk
|
||||
sdk -> srv_api [xlabel="REST API"]
|
||||
|
||||
/* Agent */
|
||||
agent_cmd -> agent_core
|
||||
agent_core -> srv_grpc [xlabel="gRPC connect"]
|
||||
agent_core -> srv_queue [xlabel="poll work"]
|
||||
agent_core -> pipe_backend [xlabel="execute steps"]
|
||||
|
||||
/* Pipelines */
|
||||
pipe_frontend -> pipe_core
|
||||
pipe_core -> pipe_backend
|
||||
|
||||
/* Server internal flow */
|
||||
srv_cmd -> srv_router
|
||||
srv_router -> srv_api
|
||||
srv_api -> srv_store
|
||||
srv_api -> srv_pubsub
|
||||
srv_api -> srv_queue
|
||||
srv_grpc -> srv_queue
|
||||
srv_store -> srv_model
|
||||
|
||||
/* External integrations */
|
||||
srv_forge -> ext_scm [xlabel="SCM API"]
|
||||
srv_store -> ext_db [xlabel="SQL"]
|
||||
|
||||
/* Shared libs usage (consumer -> library) */
|
||||
srv_router -> shared_token
|
||||
srv_api -> shared_http
|
||||
srv_grpc -> shared_log
|
||||
pipe_core -> shared_util
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 12.2.1 (0)
|
||||
-->
|
||||
<!-- Title: WoodpeckerArchitecture Pages: 1 -->
|
||||
<svg width="1112pt" height="577pt"
|
||||
viewBox="0.00 0.00 1112.00 576.75" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 572.75)">
|
||||
<title>WoodpeckerArchitecture</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-572.75 1108,-572.75 1108,4 -4,4"/>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_ui</title>
|
||||
<path fill="#c7efe9" stroke="black" d="M411,-389.25C411,-389.25 457,-389.25 457,-389.25 463,-389.25 469,-395.25 469,-401.25 469,-401.25 469,-453 469,-453 469,-459 463,-465 457,-465 457,-465 411,-465 411,-465 405,-465 399,-459 399,-453 399,-453 399,-401.25 399,-401.25 399,-395.25 405,-389.25 411,-389.25"/>
|
||||
<text text-anchor="middle" x="434" y="-447.7" font-family="Helvetica,sans-Serif" font-size="14.00">UI</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_sdk</title>
|
||||
<path fill="#e8f5e9" stroke="black" d="M411,-293.5C411,-293.5 535,-293.5 535,-293.5 541,-293.5 547,-299.5 547,-305.5 547,-305.5 547,-357.25 547,-357.25 547,-363.25 541,-369.25 535,-369.25 535,-369.25 411,-369.25 411,-369.25 405,-369.25 399,-363.25 399,-357.25 399,-357.25 399,-305.5 399,-305.5 399,-299.5 405,-293.5 411,-293.5"/>
|
||||
<text text-anchor="middle" x="473" y="-351.95" font-family="Helvetica,sans-Serif" font-size="14.00">SDK (woodpecker-go)</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_cli</title>
|
||||
<path fill="#bfe9e0" stroke="black" d="M493,-389.25C493,-389.25 575,-389.25 575,-389.25 581,-389.25 587,-395.25 587,-401.25 587,-401.25 587,-548.75 587,-548.75 587,-554.75 581,-560.75 575,-560.75 575,-560.75 493,-560.75 493,-560.75 487,-560.75 481,-554.75 481,-548.75 481,-548.75 481,-401.25 481,-401.25 481,-395.25 487,-389.25 493,-389.25"/>
|
||||
<text text-anchor="middle" x="534" y="-543.45" font-family="Helvetica,sans-Serif" font-size="14.00">woodpecker-cli</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_agent</title>
|
||||
<path fill="#ffe0c7" stroke="black" d="M636,-293.5C636,-293.5 739,-293.5 739,-293.5 745,-293.5 751,-299.5 751,-305.5 751,-305.5 751,-453 751,-453 751,-459 745,-465 739,-465 739,-465 636,-465 636,-465 630,-465 624,-459 624,-453 624,-453 624,-305.5 624,-305.5 624,-299.5 630,-293.5 636,-293.5"/>
|
||||
<text text-anchor="middle" x="687.5" y="-447.7" font-family="Helvetica,sans-Serif" font-size="14.00">woodpecker-agent</text>
|
||||
</g>
|
||||
<g id="clust5" class="cluster">
|
||||
<title>cluster_pipelines</title>
|
||||
<path fill="#ffe8d6" stroke="black" d="M816,-196C816,-196 930,-196 930,-196 936,-196 942,-202 942,-208 942,-208 942,-454.75 942,-454.75 942,-460.75 936,-466.75 930,-466.75 930,-466.75 816,-466.75 816,-466.75 810,-466.75 804,-460.75 804,-454.75 804,-454.75 804,-208 804,-208 804,-202 810,-196 816,-196"/>
|
||||
<text text-anchor="middle" x="873" y="-449.45" font-family="Helvetica,sans-Serif" font-size="14.00">Pipelines</text>
|
||||
</g>
|
||||
<g id="clust6" class="cluster">
|
||||
<title>cluster_server</title>
|
||||
<path fill="#dbe9ff" stroke="black" d="M20,-8C20,-8 379,-8 379,-8 385,-8 391,-14 391,-20 391,-20 391,-453 391,-453 391,-459 385,-465 379,-465 379,-465 20,-465 20,-465 14,-465 8,-459 8,-453 8,-453 8,-20 8,-20 8,-14 14,-8 20,-8"/>
|
||||
<text text-anchor="middle" x="199.5" y="-447.7" font-family="Helvetica,sans-Serif" font-size="14.00">woodpecker-server</text>
|
||||
</g>
|
||||
<g id="clust7" class="cluster">
|
||||
<title>cluster_shared</title>
|
||||
<path fill="#eeeeee" stroke="black" d="M598,-102C598,-102 1084,-102 1084,-102 1090,-102 1096,-108 1096,-114 1096,-114 1096,-165.75 1096,-165.75 1096,-171.75 1090,-177.75 1084,-177.75 1084,-177.75 598,-177.75 598,-177.75 592,-177.75 586,-171.75 586,-165.75 586,-165.75 586,-114 586,-114 586,-108 592,-102 598,-102"/>
|
||||
<text text-anchor="middle" x="841" y="-160.45" font-family="Helvetica,sans-Serif" font-size="14.00">Shared Libs</text>
|
||||
</g>
|
||||
<g id="clust8" class="cluster">
|
||||
<title>cluster_external</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M411,-8C411,-8 621,-8 621,-8 627,-8 633,-14 633,-20 633,-20 633,-71.75 633,-71.75 633,-77.75 627,-83.75 621,-83.75 621,-83.75 411,-83.75 411,-83.75 405,-83.75 399,-77.75 399,-71.75 399,-71.75 399,-20 399,-20 399,-14 405,-8 411,-8"/>
|
||||
<text text-anchor="middle" x="516" y="-66.45" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">External Systems</text>
|
||||
</g>
|
||||
<!-- ui_web -->
|
||||
<g id="node1" class="node">
|
||||
<title>ui_web</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M449,-433.25C449,-433.25 419,-433.25 419,-433.25 413,-433.25 407,-427.25 407,-421.25 407,-421.25 407,-409.25 407,-409.25 407,-403.25 413,-397.25 419,-397.25 419,-397.25 449,-397.25 449,-397.25 455,-397.25 461,-403.25 461,-409.25 461,-409.25 461,-421.25 461,-421.25 461,-427.25 455,-433.25 449,-433.25"/>
|
||||
<text text-anchor="middle" x="434" y="-409.82" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">web/</text>
|
||||
</g>
|
||||
<!-- srv_router -->
|
||||
<g id="node11" class="node">
|
||||
<title>srv_router</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M269.12,-337.5C269.12,-337.5 196.88,-337.5 196.88,-337.5 190.88,-337.5 184.88,-331.5 184.88,-325.5 184.88,-325.5 184.88,-313.5 184.88,-313.5 184.88,-307.5 190.88,-301.5 196.88,-301.5 196.88,-301.5 269.12,-301.5 269.12,-301.5 275.12,-301.5 281.12,-307.5 281.12,-313.5 281.12,-313.5 281.12,-325.5 281.12,-325.5 281.12,-331.5 275.12,-337.5 269.12,-337.5"/>
|
||||
<text text-anchor="middle" x="233" y="-314.07" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">server/router/</text>
|
||||
</g>
|
||||
<!-- ui_web->srv_router -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>ui_web->srv_router</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M406.58,-415C400.15,-415 395.12,-415 395.12,-415 395.12,-415 395.12,-326 395.12,-326 395.12,-326 289.72,-326 289.72,-326"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="289.72,-323.55 282.72,-326 289.72,-328.45 289.72,-323.55"/>
|
||||
<text text-anchor="middle" x="375.77" y="-329.2" font-family="Times,serif" font-size="14.00">HTTP</text>
|
||||
</g>
|
||||
<!-- srv_api -->
|
||||
<g id="node12" class="node">
|
||||
<title>srv_api</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M255.5,-241.75C255.5,-241.75 200.5,-241.75 200.5,-241.75 194.5,-241.75 188.5,-235.75 188.5,-229.75 188.5,-229.75 188.5,-217.75 188.5,-217.75 188.5,-211.75 194.5,-205.75 200.5,-205.75 200.5,-205.75 255.5,-205.75 255.5,-205.75 261.5,-205.75 267.5,-211.75 267.5,-217.75 267.5,-217.75 267.5,-229.75 267.5,-229.75 267.5,-235.75 261.5,-241.75 255.5,-241.75"/>
|
||||
<text text-anchor="middle" x="228" y="-218.32" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">server/api/</text>
|
||||
</g>
|
||||
<!-- ui_web->srv_api -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>ui_web->srv_api</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M434.94,-396.82C434.94,-379.75 434.94,-357 434.94,-357 434.94,-357 290.88,-357 290.88,-357 290.88,-357 290.88,-230 290.88,-230 290.88,-230 276.33,-230 276.33,-230"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="276.33,-227.55 269.33,-230 276.33,-232.45 276.33,-227.55"/>
|
||||
<text text-anchor="middle" x="283.17" y="-360.2" font-family="Times,serif" font-size="14.00">REST API</text>
|
||||
</g>
|
||||
<!-- sdk -->
|
||||
<g id="node2" class="node">
|
||||
<title>sdk</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M505.12,-337.5C505.12,-337.5 420.88,-337.5 420.88,-337.5 414.88,-337.5 408.88,-331.5 408.88,-325.5 408.88,-325.5 408.88,-313.5 408.88,-313.5 408.88,-307.5 414.88,-301.5 420.88,-301.5 420.88,-301.5 505.12,-301.5 505.12,-301.5 511.12,-301.5 517.12,-307.5 517.12,-313.5 517.12,-313.5 517.12,-325.5 517.12,-325.5 517.12,-331.5 511.12,-337.5 505.12,-337.5"/>
|
||||
<text text-anchor="middle" x="463" y="-314.07" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">woodpecker-go</text>
|
||||
</g>
|
||||
<!-- sdk->srv_api -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>sdk->srv_api</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M408.65,-314C360.23,-314 297,-314 297,-314 297,-314 297,-218 297,-218 297,-218 276.25,-218 276.25,-218"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="276.25,-215.55 269.25,-218 276.25,-220.45 276.25,-215.55"/>
|
||||
<text text-anchor="middle" x="325.88" y="-314.65" font-family="Times,serif" font-size="14.00">REST API</text>
|
||||
</g>
|
||||
<!-- cli_cmd -->
|
||||
<g id="node3" class="node">
|
||||
<title>cli_cmd</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M543.25,-529C543.25,-529 504.75,-529 504.75,-529 498.75,-529 492.75,-523 492.75,-517 492.75,-517 492.75,-505 492.75,-505 492.75,-499 498.75,-493 504.75,-493 504.75,-493 543.25,-493 543.25,-493 549.25,-493 555.25,-499 555.25,-505 555.25,-505 555.25,-517 555.25,-517 555.25,-523 549.25,-529 543.25,-529"/>
|
||||
<text text-anchor="middle" x="524" y="-505.57" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">cmd/cli/</text>
|
||||
</g>
|
||||
<!-- cli_core -->
|
||||
<g id="node4" class="node">
|
||||
<title>cli_core</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M539,-433.25C539,-433.25 509,-433.25 509,-433.25 503,-433.25 497,-427.25 497,-421.25 497,-421.25 497,-409.25 497,-409.25 497,-403.25 503,-397.25 509,-397.25 509,-397.25 539,-397.25 539,-397.25 545,-397.25 551,-403.25 551,-409.25 551,-409.25 551,-421.25 551,-421.25 551,-427.25 545,-433.25 539,-433.25"/>
|
||||
<text text-anchor="middle" x="524" y="-409.82" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">cli/</text>
|
||||
</g>
|
||||
<!-- cli_cmd->cli_core -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>cli_cmd->cli_core</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M524,-492.54C524,-492.54 524,-442.17 524,-442.17"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="526.45,-442.17 524,-435.17 521.55,-442.17 526.45,-442.17"/>
|
||||
</g>
|
||||
<!-- cli_core->sdk -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>cli_core->sdk</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M507.06,-396.79C507.06,-396.79 507.06,-346.42 507.06,-346.42"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="509.51,-346.42 507.06,-339.42 504.61,-346.42 509.51,-346.42"/>
|
||||
</g>
|
||||
<!-- agent_cmd -->
|
||||
<g id="node5" class="node">
|
||||
<title>agent_cmd</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M716.75,-433.25C716.75,-433.25 657.25,-433.25 657.25,-433.25 651.25,-433.25 645.25,-427.25 645.25,-421.25 645.25,-421.25 645.25,-409.25 645.25,-409.25 645.25,-403.25 651.25,-397.25 657.25,-397.25 657.25,-397.25 716.75,-397.25 716.75,-397.25 722.75,-397.25 728.75,-403.25 728.75,-409.25 728.75,-409.25 728.75,-421.25 728.75,-421.25 728.75,-427.25 722.75,-433.25 716.75,-433.25"/>
|
||||
<text text-anchor="middle" x="687" y="-409.82" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">cmd/agent/</text>
|
||||
</g>
|
||||
<!-- agent_core -->
|
||||
<g id="node6" class="node">
|
||||
<title>agent_core</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M702,-337.5C702,-337.5 672,-337.5 672,-337.5 666,-337.5 660,-331.5 660,-325.5 660,-325.5 660,-313.5 660,-313.5 660,-307.5 666,-301.5 672,-301.5 672,-301.5 702,-301.5 702,-301.5 708,-301.5 714,-307.5 714,-313.5 714,-313.5 714,-325.5 714,-325.5 714,-331.5 708,-337.5 702,-337.5"/>
|
||||
<text text-anchor="middle" x="687" y="-314.07" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">agent/</text>
|
||||
</g>
|
||||
<!-- agent_cmd->agent_core -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>agent_cmd->agent_core</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M687,-396.79C687,-396.79 687,-346.42 687,-346.42"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="689.45,-346.42 687,-339.42 684.55,-346.42 689.45,-346.42"/>
|
||||
</g>
|
||||
<!-- pipe_backend -->
|
||||
<g id="node9" class="node">
|
||||
<title>pipe_backend</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M921.5,-243.5C921.5,-243.5 824.5,-243.5 824.5,-243.5 818.5,-243.5 812.5,-237.5 812.5,-231.5 812.5,-231.5 812.5,-216 812.5,-216 812.5,-210 818.5,-204 824.5,-204 824.5,-204 921.5,-204 921.5,-204 927.5,-204 933.5,-210 933.5,-216 933.5,-216 933.5,-231.5 933.5,-231.5 933.5,-237.5 927.5,-243.5 921.5,-243.5"/>
|
||||
<text text-anchor="middle" x="873" y="-226.2" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">pipeline/backend/</text>
|
||||
<text text-anchor="middle" x="873" y="-210.45" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">(exec engines)</text>
|
||||
</g>
|
||||
<!-- agent_core->pipe_backend -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>agent_core->pipe_backend</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M706.12,-301.09C706.12,-273.34 706.12,-224 706.12,-224 706.12,-224 803.77,-224 803.77,-224"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="803.77,-226.45 810.77,-224 803.77,-221.55 803.77,-226.45"/>
|
||||
<text text-anchor="middle" x="680.4" y="-227.2" font-family="Times,serif" font-size="14.00">execute steps</text>
|
||||
</g>
|
||||
<!-- srv_grpc -->
|
||||
<g id="node13" class="node">
|
||||
<title>srv_grpc</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M370.88,-241.75C370.88,-241.75 315.12,-241.75 315.12,-241.75 309.12,-241.75 303.12,-235.75 303.12,-229.75 303.12,-229.75 303.12,-217.75 303.12,-217.75 303.12,-211.75 309.12,-205.75 315.12,-205.75 315.12,-205.75 370.88,-205.75 370.88,-205.75 376.88,-205.75 382.88,-211.75 382.88,-217.75 382.88,-217.75 382.88,-229.75 382.88,-229.75 382.88,-235.75 376.88,-241.75 370.88,-241.75"/>
|
||||
<text text-anchor="middle" x="343" y="-218.32" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">server/rpc/</text>
|
||||
</g>
|
||||
<!-- agent_core->srv_grpc -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>agent_core->srv_grpc</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M679.12,-301.16C679.12,-275.76 679.12,-233 679.12,-233 679.12,-233 391.72,-233 391.72,-233"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="391.72,-230.55 384.72,-233 391.72,-235.45 391.72,-230.55"/>
|
||||
<text text-anchor="middle" x="530.5" y="-236.2" font-family="Times,serif" font-size="14.00">gRPC connect</text>
|
||||
</g>
|
||||
<!-- srv_queue -->
|
||||
<g id="node14" class="node">
|
||||
<title>srv_queue</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M371.25,-146C371.25,-146 296.75,-146 296.75,-146 290.75,-146 284.75,-140 284.75,-134 284.75,-134 284.75,-122 284.75,-122 284.75,-116 290.75,-110 296.75,-110 296.75,-110 371.25,-110 371.25,-110 377.25,-110 383.25,-116 383.25,-122 383.25,-122 383.25,-134 383.25,-134 383.25,-140 377.25,-146 371.25,-146"/>
|
||||
<text text-anchor="middle" x="334" y="-122.58" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">server/queue/</text>
|
||||
</g>
|
||||
<!-- agent_core->srv_queue -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>agent_core->srv_queue</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M659.65,-320C617.06,-320 539.75,-320 539.75,-320 539.75,-320 539.75,-128 539.75,-128 539.75,-128 392.06,-128 392.06,-128"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="392.06,-125.55 385.06,-128 392.06,-130.45 392.06,-125.55"/>
|
||||
<text text-anchor="middle" x="513.5" y="-213.3" font-family="Times,serif" font-size="14.00">poll work</text>
|
||||
</g>
|
||||
<!-- pipe_core -->
|
||||
<g id="node7" class="node">
|
||||
<title>pipe_core</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M894.12,-337.5C894.12,-337.5 851.88,-337.5 851.88,-337.5 845.88,-337.5 839.88,-331.5 839.88,-325.5 839.88,-325.5 839.88,-313.5 839.88,-313.5 839.88,-307.5 845.88,-301.5 851.88,-301.5 851.88,-301.5 894.12,-301.5 894.12,-301.5 900.12,-301.5 906.12,-307.5 906.12,-313.5 906.12,-313.5 906.12,-325.5 906.12,-325.5 906.12,-331.5 900.12,-337.5 894.12,-337.5"/>
|
||||
<text text-anchor="middle" x="873" y="-314.07" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">pipeline/</text>
|
||||
</g>
|
||||
<!-- pipe_core->pipe_backend -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>pipe_core->pipe_backend</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M873,-301.04C873,-301.04 873,-252.32 873,-252.32"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="875.45,-252.32 873,-245.32 870.55,-252.32 875.45,-252.32"/>
|
||||
</g>
|
||||
<!-- shared_util -->
|
||||
<g id="node19" class="node">
|
||||
<title>shared_util</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M1076,-146C1076,-146 1018,-146 1018,-146 1012,-146 1006,-140 1006,-134 1006,-134 1006,-122 1006,-122 1006,-116 1012,-110 1018,-110 1018,-110 1076,-110 1076,-110 1082,-110 1088,-116 1088,-122 1088,-122 1088,-134 1088,-134 1088,-140 1082,-146 1076,-146"/>
|
||||
<text text-anchor="middle" x="1047" y="-122.58" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">shared/util/</text>
|
||||
</g>
|
||||
<!-- pipe_core->shared_util -->
|
||||
<g id="edge24" class="edge">
|
||||
<title>pipe_core->shared_util</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M906.55,-320C957.11,-320 1047,-320 1047,-320 1047,-320 1047,-154.92 1047,-154.92"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="1049.45,-154.92 1047,-147.92 1044.55,-154.92 1049.45,-154.92"/>
|
||||
</g>
|
||||
<!-- pipe_frontend -->
|
||||
<g id="node8" class="node">
|
||||
<title>pipe_frontend</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M920.75,-435C920.75,-435 825.25,-435 825.25,-435 819.25,-435 813.25,-429 813.25,-423 813.25,-423 813.25,-407.5 813.25,-407.5 813.25,-401.5 819.25,-395.5 825.25,-395.5 825.25,-395.5 920.75,-395.5 920.75,-395.5 926.75,-395.5 932.75,-401.5 932.75,-407.5 932.75,-407.5 932.75,-423 932.75,-423 932.75,-429 926.75,-435 920.75,-435"/>
|
||||
<text text-anchor="middle" x="873" y="-417.7" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">pipeline/frontend/</text>
|
||||
<text text-anchor="middle" x="873" y="-401.95" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">(yaml)</text>
|
||||
</g>
|
||||
<!-- pipe_frontend->pipe_core -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>pipe_frontend->pipe_core</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M873,-395.41C873,-395.41 873,-346.21 873,-346.21"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="875.45,-346.21 873,-339.21 870.55,-346.21 875.45,-346.21"/>
|
||||
</g>
|
||||
<!-- srv_cmd -->
|
||||
<g id="node10" class="node">
|
||||
<title>srv_cmd</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M230.62,-433.25C230.62,-433.25 167.38,-433.25 167.38,-433.25 161.38,-433.25 155.38,-427.25 155.38,-421.25 155.38,-421.25 155.38,-409.25 155.38,-409.25 155.38,-403.25 161.38,-397.25 167.38,-397.25 167.38,-397.25 230.62,-397.25 230.62,-397.25 236.62,-397.25 242.62,-403.25 242.62,-409.25 242.62,-409.25 242.62,-421.25 242.62,-421.25 242.62,-427.25 236.62,-433.25 230.62,-433.25"/>
|
||||
<text text-anchor="middle" x="199" y="-409.82" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">cmd/server/</text>
|
||||
</g>
|
||||
<!-- srv_cmd->srv_router -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>srv_cmd->srv_router</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M213.75,-396.79C213.75,-396.79 213.75,-346.42 213.75,-346.42"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="216.2,-346.42 213.75,-339.42 211.3,-346.42 216.2,-346.42"/>
|
||||
</g>
|
||||
<!-- srv_router->srv_api -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>srv_router->srv_api</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M228,-301.04C228,-301.04 228,-250.67 228,-250.67"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="230.45,-250.67 228,-243.67 225.55,-250.67 230.45,-250.67"/>
|
||||
</g>
|
||||
<!-- shared_token -->
|
||||
<g id="node20" class="node">
|
||||
<title>shared_token</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M957.88,-146C957.88,-146 884.12,-146 884.12,-146 878.12,-146 872.12,-140 872.12,-134 872.12,-134 872.12,-122 872.12,-122 872.12,-116 878.12,-110 884.12,-110 884.12,-110 957.88,-110 957.88,-110 963.88,-110 969.88,-116 969.88,-122 969.88,-122 969.88,-134 969.88,-134 969.88,-140 963.88,-146 957.88,-146"/>
|
||||
<text text-anchor="middle" x="921" y="-122.58" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">shared/token/</text>
|
||||
</g>
|
||||
<!-- srv_router->shared_token -->
|
||||
<g id="edge21" class="edge">
|
||||
<title>srv_router->shared_token</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M274.31,-301.08C274.31,-264.24 274.31,-185 274.31,-185 274.31,-185 902.81,-185 902.81,-185 902.81,-185 902.81,-154.86 902.81,-154.86"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="905.26,-154.86 902.81,-147.86 900.36,-154.86 905.26,-154.86"/>
|
||||
</g>
|
||||
<!-- srv_api->srv_queue -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>srv_api->srv_queue</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M259.88,-205.59C259.88,-179.36 259.88,-134 259.88,-134 259.88,-134 276.04,-134 276.04,-134"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="276.04,-136.45 283.04,-134 276.04,-131.55 276.04,-136.45"/>
|
||||
</g>
|
||||
<!-- srv_pubsub -->
|
||||
<g id="node15" class="node">
|
||||
<title>srv_pubsub</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M109.62,-146C109.62,-146 28.38,-146 28.38,-146 22.38,-146 16.38,-140 16.38,-134 16.38,-134 16.38,-122 16.38,-122 16.38,-116 22.38,-110 28.38,-110 28.38,-110 109.62,-110 109.62,-110 115.62,-110 121.62,-116 121.62,-122 121.62,-122 121.62,-134 121.62,-134 121.62,-140 115.62,-146 109.62,-146"/>
|
||||
<text text-anchor="middle" x="69" y="-122.58" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">server/pubsub/</text>
|
||||
</g>
|
||||
<!-- srv_api->srv_pubsub -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>srv_api->srv_pubsub</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M188.29,-224C141.19,-224 69,-224 69,-224 69,-224 69,-154.57 69,-154.57"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="71.45,-154.57 69,-147.57 66.55,-154.57 71.45,-154.57"/>
|
||||
</g>
|
||||
<!-- srv_store -->
|
||||
<g id="node16" class="node">
|
||||
<title>srv_store</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M236.5,-146C236.5,-146 169.5,-146 169.5,-146 163.5,-146 157.5,-140 157.5,-134 157.5,-134 157.5,-122 157.5,-122 157.5,-116 163.5,-110 169.5,-110 169.5,-110 236.5,-110 236.5,-110 242.5,-110 248.5,-116 248.5,-122 248.5,-122 248.5,-134 248.5,-134 248.5,-140 242.5,-146 236.5,-146"/>
|
||||
<text text-anchor="middle" x="203" y="-122.58" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">server/store/</text>
|
||||
</g>
|
||||
<!-- srv_api->srv_store -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>srv_api->srv_store</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M218.5,-205.29C218.5,-205.29 218.5,-154.92 218.5,-154.92"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="220.95,-154.92 218.5,-147.92 216.05,-154.92 220.95,-154.92"/>
|
||||
</g>
|
||||
<!-- shared_http -->
|
||||
<g id="node21" class="node">
|
||||
<title>shared_http</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M686.25,-146C686.25,-146 605.75,-146 605.75,-146 599.75,-146 593.75,-140 593.75,-134 593.75,-134 593.75,-122 593.75,-122 593.75,-116 599.75,-110 605.75,-110 605.75,-110 686.25,-110 686.25,-110 692.25,-110 698.25,-116 698.25,-122 698.25,-122 698.25,-134 698.25,-134 698.25,-140 692.25,-146 686.25,-146"/>
|
||||
<text text-anchor="middle" x="646" y="-122.58" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">shared/httputil/</text>
|
||||
</g>
|
||||
<!-- srv_api->shared_http -->
|
||||
<g id="edge22" class="edge">
|
||||
<title>srv_api->shared_http</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M263.69,-205.48C263.69,-188.24 263.69,-165 263.69,-165 263.69,-165 619.5,-165 619.5,-165 619.5,-165 619.5,-155.01 619.5,-155.01"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="621.95,-155.01 619.5,-148.01 617.05,-155.01 621.95,-155.01"/>
|
||||
</g>
|
||||
<!-- srv_grpc->srv_queue -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>srv_grpc->srv_queue</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M343,-205.29C343,-205.29 343,-154.92 343,-154.92"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="345.45,-154.92 343,-147.92 340.55,-154.92 345.45,-154.92"/>
|
||||
</g>
|
||||
<!-- shared_log -->
|
||||
<g id="node22" class="node">
|
||||
<title>shared_log</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M824.12,-146C824.12,-146 745.88,-146 745.88,-146 739.88,-146 733.88,-140 733.88,-134 733.88,-134 733.88,-122 733.88,-122 733.88,-116 739.88,-110 745.88,-110 745.88,-110 824.12,-110 824.12,-110 830.12,-110 836.12,-116 836.12,-122 836.12,-122 836.12,-134 836.12,-134 836.12,-140 830.12,-146 824.12,-146"/>
|
||||
<text text-anchor="middle" x="785" y="-122.58" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">shared/logger/</text>
|
||||
</g>
|
||||
<!-- srv_grpc->shared_log -->
|
||||
<g id="edge23" class="edge">
|
||||
<title>srv_grpc->shared_log</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M383.08,-215C489.88,-215 773.19,-215 773.19,-215 773.19,-215 773.19,-154.96 773.19,-154.96"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="775.64,-154.96 773.19,-147.96 770.74,-154.96 775.64,-154.96"/>
|
||||
</g>
|
||||
<!-- srv_model -->
|
||||
<g id="node17" class="node">
|
||||
<title>srv_model</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M240.25,-52C240.25,-52 165.75,-52 165.75,-52 159.75,-52 153.75,-46 153.75,-40 153.75,-40 153.75,-28 153.75,-28 153.75,-22 159.75,-16 165.75,-16 165.75,-16 240.25,-16 240.25,-16 246.25,-16 252.25,-22 252.25,-28 252.25,-28 252.25,-40 252.25,-40 252.25,-46 246.25,-52 240.25,-52"/>
|
||||
<text text-anchor="middle" x="203" y="-28.57" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">server/model/</text>
|
||||
</g>
|
||||
<!-- srv_store->srv_model -->
|
||||
<g id="edge18" class="edge">
|
||||
<title>srv_store->srv_model</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M203,-109.88C203,-109.88 203,-60.9 203,-60.9"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="205.45,-60.9 203,-53.9 200.55,-60.9 205.45,-60.9"/>
|
||||
</g>
|
||||
<!-- ext_db -->
|
||||
<g id="node24" class="node">
|
||||
<title>ext_db</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M480.88,-48.73C480.88,-50.53 464.35,-52 444,-52 423.65,-52 407.12,-50.53 407.12,-48.73 407.12,-48.73 407.12,-19.27 407.12,-19.27 407.12,-17.47 423.65,-16 444,-16 464.35,-16 480.88,-17.47 480.88,-19.27 480.88,-19.27 480.88,-48.73 480.88,-48.73"/>
|
||||
<path fill="none" stroke="black" d="M480.88,-48.73C480.88,-46.92 464.35,-45.45 444,-45.45 423.65,-45.45 407.12,-46.92 407.12,-48.73"/>
|
||||
<text text-anchor="middle" x="444" y="-28.57" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">Database</text>
|
||||
</g>
|
||||
<!-- srv_store->ext_db -->
|
||||
<g id="edge20" class="edge">
|
||||
<title>srv_store->ext_db</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M248.68,-122C253.15,-122 256.06,-122 256.06,-122 256.06,-122 256.06,-34 256.06,-34 256.06,-34 398.36,-34 398.36,-34"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="398.36,-36.45 405.36,-34 398.36,-31.55 398.36,-36.45"/>
|
||||
<text text-anchor="middle" x="266.77" y="-37.2" font-family="Times,serif" font-size="14.00">SQL</text>
|
||||
</g>
|
||||
<!-- srv_forge -->
|
||||
<g id="node18" class="node">
|
||||
<title>srv_forge</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M358.88,-433.25C358.88,-433.25 291.12,-433.25 291.12,-433.25 285.12,-433.25 279.12,-427.25 279.12,-421.25 279.12,-421.25 279.12,-409.25 279.12,-409.25 279.12,-403.25 285.12,-397.25 291.12,-397.25 291.12,-397.25 358.88,-397.25 358.88,-397.25 364.88,-397.25 370.88,-403.25 370.88,-409.25 370.88,-409.25 370.88,-421.25 370.88,-421.25 370.88,-427.25 364.88,-433.25 358.88,-433.25"/>
|
||||
<text text-anchor="middle" x="325" y="-409.82" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">server/forge/</text>
|
||||
</g>
|
||||
<!-- ext_scm -->
|
||||
<g id="node23" class="node">
|
||||
<title>ext_scm</title>
|
||||
<path fill="#2b2b2b" stroke="black" d="M612.75,-52C612.75,-52 529.25,-52 529.25,-52 523.25,-52 517.25,-46 517.25,-40 517.25,-40 517.25,-28 517.25,-28 517.25,-22 523.25,-16 529.25,-16 529.25,-16 612.75,-16 612.75,-16 618.75,-16 624.75,-22 624.75,-28 624.75,-28 624.75,-40 624.75,-40 624.75,-46 618.75,-52 612.75,-52"/>
|
||||
<text text-anchor="middle" x="571" y="-28.57" font-family="Helvetica,sans-Serif" font-size="14.00" fill="white">SCM Providers</text>
|
||||
</g>
|
||||
<!-- srv_forge->ext_scm -->
|
||||
<g id="edge19" class="edge">
|
||||
<title>srv_forge->ext_scm</title>
|
||||
<path fill="none" stroke="#bdbdbd" d="M337,-397C337,-386.75 337,-376 337,-376 337,-376 528.5,-376 528.5,-376 528.5,-376 528.5,-60.9 528.5,-60.9"/>
|
||||
<polygon fill="#bdbdbd" stroke="#bdbdbd" points="530.95,-60.9 528.5,-53.9 526.05,-60.9 530.95,-60.9"/>
|
||||
<text text-anchor="middle" x="555.5" y="-327.9" font-family="Times,serif" font-size="14.00">SCM API</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 27 KiB |
@@ -32,7 +32,6 @@ const config = {
|
||||
url: 'https://woodpecker-ci.org',
|
||||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'throw',
|
||||
onBrokenAnchors: 'throw',
|
||||
onDuplicateRoutes: 'throw',
|
||||
organizationName: 'woodpecker-ci',
|
||||
@@ -319,6 +318,10 @@ const config = {
|
||||
],
|
||||
markdown: {
|
||||
format: 'detect',
|
||||
hooks: {
|
||||
onBrokenMarkdownLinks: 'throw',
|
||||
onBrokenMarkdownImages: 'throw',
|
||||
},
|
||||
},
|
||||
future: {
|
||||
experimental_faster: true,
|
||||
|
||||
+20
-12
@@ -16,15 +16,15 @@
|
||||
"format:check": "prettier -c ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.9.1",
|
||||
"@docusaurus/faster": "^3.9.1",
|
||||
"@docusaurus/plugin-content-blog": "^3.9.1",
|
||||
"@docusaurus/preset-classic": "^3.9.1",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.52.1",
|
||||
"@docusaurus/core": "^3.9.2",
|
||||
"@docusaurus/faster": "^3.9.2",
|
||||
"@docusaurus/plugin-content-blog": "^3.9.2",
|
||||
"@docusaurus/preset-classic": "^3.9.2",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.55.0",
|
||||
"clsx": "^2.1.1",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"redocusaurus": "^2.5.0"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -40,15 +40,23 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.9.1",
|
||||
"@docusaurus/module-type-aliases": "^3.9.2",
|
||||
"@docusaurus/tsconfig": "3.9.2",
|
||||
"@docusaurus/types": "^3.9.1",
|
||||
"@docusaurus/types": "^3.9.2",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/node": "^25.1.0",
|
||||
"@types/react": "^19.2.10",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"lodash": "^4.17.23",
|
||||
"fast-xml-parser": "^5.3.4",
|
||||
"ajv": "^6.14.0",
|
||||
"minimatch": "^10.2.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,25 +10,25 @@
|
||||
"style": "mkdir -p dist/theme/ && cp src/theme/style.css dist/theme/style.css"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.7.0",
|
||||
"@docusaurus/theme-classic": "^3.7.0",
|
||||
"@docusaurus/types": "^3.7.0",
|
||||
"@tsconfig/docusaurus": "^2.0.3",
|
||||
"@types/node": "^24.0.0",
|
||||
"axios": "^1.7.9",
|
||||
"concurrently": "^9.1.2",
|
||||
"isomorphic-dompurify": "^2.19.0",
|
||||
"marked": "^17.0.0",
|
||||
"@docusaurus/module-type-aliases": "^3.9.2",
|
||||
"@docusaurus/theme-classic": "^3.9.2",
|
||||
"@docusaurus/types": "^3.9.2",
|
||||
"@tsconfig/docusaurus": "^2.0.7",
|
||||
"@types/node": "^24.10.9",
|
||||
"axios": "^1.13.4",
|
||||
"concurrently": "^9.2.1",
|
||||
"isomorphic-dompurify": "^3.0.0",
|
||||
"marked": "^17.0.1",
|
||||
"slugify": "^1.6.6",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.7.2"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"fuse.js": "^7.0.0",
|
||||
"yaml": "^2.7.0"
|
||||
"fuse.js": "^7.1.0",
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +299,16 @@
|
||||
"name": "Github Comment",
|
||||
"docs": "https://raw.githubusercontent.com/yyewolf/woodpecker-plugins/refs/heads/main/github-comment/docs.md",
|
||||
"verified": false
|
||||
},
|
||||
{
|
||||
"name": "BunnyCDN Cache Purge",
|
||||
"docs": "https://codeberg.org/bentasker/woodpecker-ci-bunnycdn-cache-flush/raw/branch/main/docs.md",
|
||||
"verified": false
|
||||
},
|
||||
{
|
||||
"name": "Buildah",
|
||||
"docs": "https://raw.githubusercontent.com/404systems/plugin-buildah/refs/heads/main/docs.md",
|
||||
"verified": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+2347
-2042
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB |
@@ -106,7 +106,7 @@ woodpecker-cli repo secret add \
|
||||
Create the secret and limit it to a single image:
|
||||
|
||||
```diff
|
||||
woodpecker-cli secret add \
|
||||
woodpecker-cli repo secret add \
|
||||
--repository octocat/hello-world \
|
||||
+ --image woodpeckerci/plugin-s3 \
|
||||
--name aws_access_key_id \
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
Woodpecker provides a [Helm chart](https://github.com/woodpecker-ci/helm) for Kubernetes environments:
|
||||
|
||||
```bash
|
||||
helm repo add woodpecker oci://ghcr.io/woodpecker-ci/helm
|
||||
helm install woodpecker woodpecker/woodpecker
|
||||
helm install woodpecker oci://ghcr.io/woodpecker-ci/helm/woodpecker --version <VERSION>
|
||||
```
|
||||
|
||||
## Metrics
|
||||
|
||||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
+1
-1
@@ -393,7 +393,7 @@ when:
|
||||
#### `path`
|
||||
|
||||
:::info
|
||||
Path conditions are applied only to **push** and **pull_request** events. This feature is currently available for all forges except Bitbucket Cloud.
|
||||
Path conditions are applied only to **push** and **pull_request** events.
|
||||
:::
|
||||
|
||||
Execute a step only on a pipeline with certain files being changed:
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
# Configuration extension
|
||||
|
||||
The configuration extension can be used to modify or generate Woodpeckers pipeline configurations. You can configure an HTTP endpoint in the repository settings in the extensions tab.
|
||||
|
||||
Using such an extension can be useful if you want to:
|
||||
|
||||
<!-- cSpell:words templating,Starlark,Jsonnet -->
|
||||
|
||||
- Preprocess the original configuration file with something like Go templating
|
||||
- Convert custom attributes to Woodpecker attributes
|
||||
- Add defaults to the configuration like default steps
|
||||
- Convert configuration files from a totally different format like Gitlab CI config, Starlark, Jsonnet, ...
|
||||
- Centralize configuration for multiple repositories in one place
|
||||
|
||||
## Security
|
||||
|
||||
:::warning
|
||||
As Woodpecker will pass private information like tokens and will execute the returned configuration, it is extremely important to secure the external extension. Therefore Woodpecker signs every request. Read more about it in the [security section](./index.md#security).
|
||||
:::
|
||||
|
||||
## Global configuration
|
||||
|
||||
In addition to the ability to configure the extension per repository, you can also configure a global endpoint in the Woodpecker server configuration. This can be useful if you want to use the extension for all repositories. Be careful if
|
||||
you share your Woodpecker server with others as they will also use your configuration extension.
|
||||
|
||||
The global configuration will be called before the repository specific configuration extension if both are configured.
|
||||
|
||||
```ini title="Server"
|
||||
WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
When a pipeline is triggered Woodpecker will fetch the pipeline configuration from the repository, then make a HTTP POST request to the configured extension with a JSON payload containing some data like the repository, pipeline information and the current config files retrieved from the repository. The extension can then send back modified or even new pipeline configurations following Woodpeckers official yaml format that should be used.
|
||||
|
||||
### Request
|
||||
|
||||
The extension receives an HTTP POST request with the following JSON payload:
|
||||
|
||||
```ts
|
||||
class Request {
|
||||
repo: Repo;
|
||||
pipeline: Pipeline;
|
||||
netrc: Netrc;
|
||||
}
|
||||
```
|
||||
|
||||
Checkout the following models for more information:
|
||||
|
||||
- [repo model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/repo.go)
|
||||
- [pipeline model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/pipeline.go)
|
||||
- [netrc model](https://github.com/woodpecker-ci/woodpecker/blob/main/server/model/netrc.go)
|
||||
|
||||
:::tip
|
||||
The `netrc` data is pretty powerful as it contains credentials to access the repository. You can use this to fetch files or other information (like changed files, issues) from the repository using the forge api or even clone the repository.
|
||||
:::
|
||||
|
||||
Example request:
|
||||
|
||||
```json
|
||||
// Please check the latest structure in the models mentioned above.
|
||||
// This example is likely outdated.
|
||||
|
||||
{
|
||||
"repo": {
|
||||
"id": 100,
|
||||
"uid": "",
|
||||
"user_id": 0,
|
||||
"namespace": "",
|
||||
"name": "woodpecker-test-pipeline",
|
||||
"slug": "",
|
||||
"scm": "git",
|
||||
"git_http_url": "",
|
||||
"git_ssh_url": "",
|
||||
"link": "",
|
||||
"default_branch": "",
|
||||
"private": true,
|
||||
"visibility": "private",
|
||||
"active": true,
|
||||
"config": "",
|
||||
"trusted": false,
|
||||
"protected": false,
|
||||
"ignore_forks": false,
|
||||
"ignore_pulls": false,
|
||||
"cancel_pulls": false,
|
||||
"timeout": 60,
|
||||
"counter": 0,
|
||||
"synced": 0,
|
||||
"created": 0,
|
||||
"updated": 0,
|
||||
"version": 0
|
||||
},
|
||||
"pipeline": {
|
||||
"author": "myUser",
|
||||
"author_avatar": "https://myforge.com/avatars/d6b3f7787a685fcdf2a44e2c685c7e03",
|
||||
"author_email": "my@email.com",
|
||||
"branch": "main",
|
||||
"changed_files": ["some-filename.txt"],
|
||||
"commit": "2fff90f8d288a4640e90f05049fe30e61a14fd50",
|
||||
"created_at": 0,
|
||||
"deploy_to": "",
|
||||
"enqueued_at": 0,
|
||||
"error": "",
|
||||
"event": "push",
|
||||
"finished_at": 0,
|
||||
"id": 0,
|
||||
"link_url": "https://myforge.com/myUser/woodpecker-testpipe/commit/2fff90f8d288a4640e90f05049fe30e61a14fd50",
|
||||
"message": "test old config\n",
|
||||
"number": 0,
|
||||
"parent": 0,
|
||||
"ref": "refs/heads/main",
|
||||
"refspec": "",
|
||||
"clone_url": "",
|
||||
"reviewed_at": 0,
|
||||
"reviewed_by": "",
|
||||
"sender": "myUser",
|
||||
"signed": false,
|
||||
"started_at": 0,
|
||||
"status": "",
|
||||
"timestamp": 1645962783,
|
||||
"title": "",
|
||||
"updated_at": 0,
|
||||
"verified": false
|
||||
},
|
||||
"netrc": {
|
||||
"machine": "myforge.com",
|
||||
"login": "myUser",
|
||||
"password": "myPassword",
|
||||
"type": "forge"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
The extension should respond with a JSON payload containing the new configuration files in Woodpecker's official YAML format.
|
||||
If the extension wants to keep the existing configuration files, it can respond with HTTP status `204 No Content`.
|
||||
|
||||
```ts
|
||||
class Response {
|
||||
configs: {
|
||||
name: string; // filename of the configuration file
|
||||
data: string; // content of the configuration file
|
||||
}[];
|
||||
}
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"configs": [
|
||||
{
|
||||
"name": "central-override",
|
||||
"data": "steps:\n - name: backend\n image: alpine\n commands:\n - echo \"Hello there from ConfigAPI\"\n"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
label: 'Extensions'
|
||||
# position: 3
|
||||
collapsible: true
|
||||
collapsed: true
|
||||
link:
|
||||
type: 'doc'
|
||||
id: 'index'
|
||||
@@ -0,0 +1,34 @@
|
||||
# Extensions
|
||||
|
||||
Woodpecker allows you to replace internal logic with external extensions by using pre-defined http endpoints.
|
||||
|
||||
There is currently one type of extension available:
|
||||
|
||||
- [Configuration extension](./40-configuration-extension.md) to modify or generate pipeline configurations on the fly.
|
||||
|
||||
## Security
|
||||
|
||||
:::warning
|
||||
You need to trust the extensions as they are receiving private information like secrets and tokens and might return harmful
|
||||
data like malicious pipeline configurations that could be executed.
|
||||
:::
|
||||
|
||||
To prevent your extensions from such attacks, Woodpecker is signing all HTTP requests using [HTTP signatures](https://tools.ietf.org/html/draft-cavage-http-signatures). Woodpecker therefore uses a public-private ed25519 key pair.
|
||||
To verify the requests your extension has to verify the signature of all request using the public key with some library like [httpsign](https://github.com/yaronf/httpsign).
|
||||
You can get the public Woodpecker key by opening `http://my-woodpecker.tld/api/signature/public-key` or by visiting the Woodpecker UI, going to you repo settings and opening the extensions page.
|
||||
|
||||
## Example extensions
|
||||
|
||||
A simplistic service providing endpoints for a config and secrets extension can be found here: [https://github.com/woodpecker-ci/example-extensions](https://github.com/woodpecker-ci/example-extensions)
|
||||
|
||||
## Configuration
|
||||
|
||||
To prevent extensions from calling local services by default only external hosts / ip-addresses are allowed. You can change this behavior by setting the `WOODPECKER_EXTENSIONS_ALLOWED_HOSTS` environment variable. You can use a comma separated list of:
|
||||
|
||||
- Built-in networks:
|
||||
- `loopback`: 127.0.0.0/8 for IPv4 and ::1/128 for IPv6, localhost is included.
|
||||
- `private`: RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and RFC 4193 (FC00::/7). Also called LAN/Intranet.
|
||||
- `external`: A valid non-private unicast IP, you can access all hosts on public internet.
|
||||
- `*`: All hosts are allowed.
|
||||
- CIDR list: `1.2.3.0/8` for IPv4 and `2001:db8::/32` for IPv6
|
||||
- (Wildcard) hosts: `example.com`, `*.example.com`, `192.168.100.*`
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user