From 9d55d0bf073e547db181a8d1e4a1d23ab549480e Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 1 May 2026 11:01:28 +0200 Subject: [PATCH] Create Docs for v3.14.0 (#6163) Co-authored-by: qwerty287 --- docs/src/pages/migrations.md | 2 +- docs/src/pages/versions.md | 2 + .../20-usage/100-troubleshooting.md | 37 -- .../92-development/05-architecture.md | 48 --- .../woodpecker-architecture.png | Bin 62587 -> 0 bytes .../10-intro/index.md | 0 .../20-usage/10-intro.md | 0 .../20-usage/100-troubleshooting.md | 97 +++++ .../15-terminology/architecture.excalidraw | 0 .../20-usage/15-terminology/architecture.svg | 0 .../20-usage/15-terminology/index.md | 31 +- .../pipeline-workflow-step.excalidraw | 0 .../15-terminology/pipeline-workflow-step.svg | 0 .../20-usage/20-workflow-syntax.md | 29 +- .../20-usage/25-workflows.md | 7 +- .../20-usage/30-matrix-workflows.md | 0 .../20-usage/40-secrets.md | 2 +- .../20-usage/41-registries.md | 0 .../20-usage/45-cron.md | 0 .../20-usage/50-environment.md | 0 .../51-plugins/20-creating-plugins.md | 0 .../20-usage/51-plugins/51-overview.md | 0 .../20-usage/51-plugins/_category_.yaml | 0 .../20-usage/60-services.md | 14 + .../20-usage/70-volumes.md | 0 .../40-configuration-extension.md | 24 +- .../72-extensions/50-registry-extension.md | 160 ++++++++ .../72-extensions/55-secret-extension.md | 166 +++++++++ .../20-usage/72-extensions/_category_.yaml | 0 .../20-usage/72-extensions/index.md | 2 + .../20-usage/72-linter.md | 0 .../20-usage/75-project-settings.md | 0 .../20-usage/80-badges.md | 8 +- .../20-usage/90-advanced-usage.md | 0 .../20-usage/_category_.yaml | 0 .../20-usage/cron-settings.png | Bin .../20-usage/linter-warnings-errors.png | Bin .../20-usage/pipeline.png | Bin .../20-usage/project-settings.png | Bin .../20-usage/repo-new.png | Bin .../20-usage/secrets-plugins-filter.png | Bin .../30-administration/00-general.md | 0 .../05-installation/10-docker-compose.md | 11 + .../05-installation/20-helm-chart.md | 3 +- .../05-installation/30-packages.md | 1 + .../05-installation/_category_.yaml | 0 .../10-configuration/10-server.md | 191 +++++----- .../10-configuration/100-addons.md | 42 +++ .../10-configuration/11-backends/10-docker.md | 0 .../11-backends/20-kubernetes.md | 146 +++++++- .../10-configuration/11-backends/30-local.md | 0 .../10-configuration/11-backends/50-custom.md | 0 .../11-backends/_category_.yaml | 0 .../10-configuration/12-forges/11-overview.md | 4 +- .../10-configuration/12-forges/20-github.md | 0 .../10-configuration/12-forges/30-gitea.md | 21 ++ .../10-configuration/12-forges/35-forgejo.md | 21 ++ .../10-configuration/12-forges/40-gitlab.md | 0 .../12-forges/50-bitbucket.md | 0 .../12-forges/60-bitbucket_datacenter.md | 0 .../12-forges/_category_.yaml | 0 .../12-forges/bitbucket_oauth.png | Bin .../12-forges/bitbucket_permissions.png | Bin .../12-forges/gitea_oauth.gif | Bin .../12-forges/github_oauth.png | Bin .../10-configuration/30-agent.md | 26 ++ .../10-configuration/40-autoscaler.md | 0 .../10-configuration/_category_.yaml | 0 .../10-configuration/new-agent-connected.png | Bin .../10-configuration/new-agent-created.png | Bin .../new-agent-registration.png | Bin .../30-administration/_category_.yaml | 0 .../{version-3.11 => version-3.14}/40-cli.md | 314 +++++++--------- .../92-development/01-getting-started.md | 4 +- .../92-development/02-core-ideas.md | 2 +- .../92-development/03-ui.md | 0 .../92-development/04-docs.md | 0 .../92-development/05-architecture.md | 98 +++++ .../92-development/06-conventions.md | 2 +- .../92-development/07-guides.md | 0 .../92-development/08-translations.md | 0 .../92-development/09-openapi.md | 0 .../92-development/09-testing.md | 2 +- .../92-development/10-packaging.md | 18 + .../92-development/100-addons.md} | 39 +- .../92-development/40-deprecations.md | 43 +++ .../92-development/_category_.yaml | 0 .../92-development/ui-proxy.svg | 0 .../92-development/vscode-debug.png | Bin .../92-development/vscode-run-test.png | Bin .../woodpecker-architecture.dot | 157 ++++++++ .../woodpecker-architecture.svg | 352 ++++++++++++++++++ ...debars.json => version-3.14-sidebars.json} | 0 docs/versions.json | 2 +- 94 files changed, 1694 insertions(+), 434 deletions(-) delete mode 100644 docs/versioned_docs/version-3.11/20-usage/100-troubleshooting.md delete mode 100644 docs/versioned_docs/version-3.11/92-development/05-architecture.md delete mode 100644 docs/versioned_docs/version-3.11/92-development/woodpecker-architecture.png rename docs/versioned_docs/{version-3.11 => version-3.14}/10-intro/index.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/10-intro.md (100%) create mode 100644 docs/versioned_docs/version-3.14/20-usage/100-troubleshooting.md rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/15-terminology/architecture.excalidraw (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/15-terminology/architecture.svg (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/15-terminology/index.md (95%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/15-terminology/pipeline-workflow-step.excalidraw (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/15-terminology/pipeline-workflow-step.svg (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/20-workflow-syntax.md (95%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/25-workflows.md (94%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/30-matrix-workflows.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/40-secrets.md (99%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/41-registries.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/45-cron.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/50-environment.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/51-plugins/20-creating-plugins.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/51-plugins/51-overview.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/51-plugins/_category_.yaml (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/60-services.md (86%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/70-volumes.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/72-extensions/40-configuration-extension.md (84%) create mode 100644 docs/versioned_docs/version-3.14/20-usage/72-extensions/50-registry-extension.md create mode 100644 docs/versioned_docs/version-3.14/20-usage/72-extensions/55-secret-extension.md rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/72-extensions/_category_.yaml (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/72-extensions/index.md (91%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/72-linter.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/75-project-settings.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/80-badges.md (54%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/90-advanced-usage.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/_category_.yaml (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/cron-settings.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/linter-warnings-errors.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/pipeline.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/project-settings.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/repo-new.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/20-usage/secrets-plugins-filter.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/00-general.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/05-installation/10-docker-compose.md (92%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/05-installation/20-helm-chart.md (91%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/05-installation/30-packages.md (98%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/05-installation/_category_.yaml (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/10-server.md (89%) create mode 100644 docs/versioned_docs/version-3.14/30-administration/10-configuration/100-addons.md rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/11-backends/10-docker.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/11-backends/20-kubernetes.md (72%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/11-backends/30-local.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/11-backends/50-custom.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/11-backends/_category_.yaml (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/11-overview.md (90%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/20-github.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/30-gitea.md (77%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/35-forgejo.md (77%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/40-gitlab.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/50-bitbucket.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/60-bitbucket_datacenter.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/_category_.yaml (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/bitbucket_oauth.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/bitbucket_permissions.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/gitea_oauth.gif (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/12-forges/github_oauth.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/30-agent.md (89%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/40-autoscaler.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/_category_.yaml (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/new-agent-connected.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/new-agent-created.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/10-configuration/new-agent-registration.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/30-administration/_category_.yaml (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/40-cli.md (69%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/01-getting-started.md (96%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/02-core-ideas.md (85%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/03-ui.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/04-docs.md (100%) create mode 100644 docs/versioned_docs/version-3.14/92-development/05-architecture.md rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/06-conventions.md (56%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/07-guides.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/08-translations.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/09-openapi.md (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/09-testing.md (98%) create mode 100644 docs/versioned_docs/version-3.14/92-development/10-packaging.md rename docs/versioned_docs/{version-3.11/30-administration/10-configuration/12-forges/100-addon.md => version-3.14/92-development/100-addons.md} (54%) create mode 100644 docs/versioned_docs/version-3.14/92-development/40-deprecations.md rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/_category_.yaml (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/ui-proxy.svg (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/vscode-debug.png (100%) rename docs/versioned_docs/{version-3.11 => version-3.14}/92-development/vscode-run-test.png (100%) create mode 100644 docs/versioned_docs/version-3.14/92-development/woodpecker-architecture.dot create mode 100644 docs/versioned_docs/version-3.14/92-development/woodpecker-architecture.svg rename docs/versioned_sidebars/{version-3.11-sidebars.json => version-3.14-sidebars.json} (100%) diff --git a/docs/src/pages/migrations.md b/docs/src/pages/migrations.md index d95ae2cd3..37f38fd01 100644 --- a/docs/src/pages/migrations.md +++ b/docs/src/pages/migrations.md @@ -255,7 +255,7 @@ Read more about it in [#4213](https://github.com/woodpecker-ci/woodpecker/pull/4 ## 1.0.0 -- The signature used to verify extension calls (like those used for the [config-extension](/docs/administration/configuration/server#external-configuration-api)) done by the Woodpecker server switched from using a shared-secret HMac to an ed25519 key-pair. Read more about it at the [config-extensions](/docs/administration/configuration/server#external-configuration-api) documentation. +- The signature used to verify extension calls (like those used for the [config-extension](/docs/next/usage/extensions/configuration-extension)) done by the Woodpecker server switched from using a shared-secret HMac to an ed25519 key-pair. Read more about it at the [config-extensions](/docs/next/usage/extensions/configuration-extension) documentation. - Refactored support for old agent filter labels and expressions. Learn how to use the new [filter](/docs/usage/workflow-syntax#labels) - Renamed step environment variable `CI_SYSTEM_ARCH` to `CI_SYSTEM_PLATFORM`. Same applies for the cli exec variable. - Renamed environment variables `CI_BUILD_*` and `CI_PREV_BUILD_*` to `CI_PIPELINE_*` and `CI_PREV_PIPELINE_*`, old ones are still available but deprecated diff --git a/docs/src/pages/versions.md b/docs/src/pages/versions.md index 6c5064680..3f1f1eef9 100644 --- a/docs/src/pages/versions.md +++ b/docs/src/pages/versions.md @@ -33,6 +33,8 @@ Here you can find documentation for previous versions of Woodpecker. | | | | | ------- | ---------- | ------------------------------------------------------------------------------------- | +| 3.14.0 | 2026-xx-xx | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v3.14.0/docs/docs/) | +| 3.13.0 | 2026-01-14 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v3.13.0/docs/docs/) | | 3.12.0 | 2025-11-18 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v3.12.0/docs/docs/) | | 3.11.0 | 2025-10-19 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v3.11.0/docs/docs/) | | 3.10.0 | 2025-09-28 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v3.10.0/docs/docs/) | diff --git a/docs/versioned_docs/version-3.11/20-usage/100-troubleshooting.md b/docs/versioned_docs/version-3.11/20-usage/100-troubleshooting.md deleted file mode 100644 index b961530f4..000000000 --- a/docs/versioned_docs/version-3.11/20-usage/100-troubleshooting.md +++ /dev/null @@ -1,37 +0,0 @@ -# Troubleshooting - -## How to debug clone issues - -(And what to do with an error message like `fatal: could not read Username for 'https://': No such device or address`) - -This error can have multiple causes. If you use internal repositories you might have to enable `WOODPECKER_AUTHENTICATE_PUBLIC_REPOS`: - -```ini -WOODPECKER_AUTHENTICATE_PUBLIC_REPOS=true -``` - -If that does not work, try to make sure the container can reach your git server. In order to do that disable git checkout and make the container "hang": - -```yaml -skip_clone: true - -steps: - build: - image: debian:stable-backports - commands: - - apt update - - apt install -y inetutils-ping wget - - ping -c 4 git.example.com - - wget git.example.com - - sleep 9999999 -``` - -Get the container id using `docker ps` and copy the id from the first column. Enter the container with: `docker exec -it 1234asdf bash` (replace `1234asdf` with the docker id). Then try to clone the git repository with the commands from the failing pipeline: - -```bash -git init -git remote add origin https://git.example.com/username/repo.git -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) diff --git a/docs/versioned_docs/version-3.11/92-development/05-architecture.md b/docs/versioned_docs/version-3.11/92-development/05-architecture.md deleted file mode 100644 index 535ec6a80..000000000 --- a/docs/versioned_docs/version-3.11/92-development/05-architecture.md +++ /dev/null @@ -1,48 +0,0 @@ -# Architecture - -## Package architecture - -![Woodpecker architecture](./woodpecker-architecture.png) - -## System architecture - -### main package hierarchy - -| 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` | -| `cli/**` | code only cli tool does need | `pipeline`, `shared`, `woodpecker-go` | -| `server/**` | code only server will need | `pipeline`, `shared` | -| `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 | | - -- `../` = `server/` - -### Agent - -TODO - -### CLI - -TODO diff --git a/docs/versioned_docs/version-3.11/92-development/woodpecker-architecture.png b/docs/versioned_docs/version-3.11/92-development/woodpecker-architecture.png deleted file mode 100644 index 22f6a054aeaa1914f3206932b88fd07ab8d69458..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62587 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 O)=<$=u2Zyn`#%7$GeGPB diff --git a/docs/versioned_docs/version-3.11/10-intro/index.md b/docs/versioned_docs/version-3.14/10-intro/index.md similarity index 100% rename from docs/versioned_docs/version-3.11/10-intro/index.md rename to docs/versioned_docs/version-3.14/10-intro/index.md diff --git a/docs/versioned_docs/version-3.11/20-usage/10-intro.md b/docs/versioned_docs/version-3.14/20-usage/10-intro.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/10-intro.md rename to docs/versioned_docs/version-3.14/20-usage/10-intro.md diff --git a/docs/versioned_docs/version-3.14/20-usage/100-troubleshooting.md b/docs/versioned_docs/version-3.14/20-usage/100-troubleshooting.md new file mode 100644 index 000000000..2885c134d --- /dev/null +++ b/docs/versioned_docs/version-3.14/20-usage/100-troubleshooting.md @@ -0,0 +1,97 @@ +# Troubleshooting + +## How to debug clone issues + +(And what to do with an error message like `fatal: could not read Username for 'https://': No such device or address`) + +This error can have multiple causes. If you use internal repositories you might have to enable `WOODPECKER_AUTHENTICATE_PUBLIC_REPOS`: + +```ini +WOODPECKER_AUTHENTICATE_PUBLIC_REPOS=true +``` + +If that does not work, try to make sure the container can reach your git server. In order to do that disable git checkout and make the container "hang": + +```yaml +skip_clone: true + +steps: + build: + image: debian:stable-backports + commands: + - apt update + - apt install -y inetutils-ping wget + - ping -c 4 git.example.com + - wget git.example.com + - sleep 9999999 +``` + +Get the container id using `docker ps` and copy the id from the first column. Enter the container with: `docker exec -it 1234asdf bash` (replace `1234asdf` with the docker id). Then try to clone the git repository with the commands from the failing pipeline: + +```bash +git init +git remote add origin https://git.example.com/username/repo.git +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. diff --git a/docs/versioned_docs/version-3.11/20-usage/15-terminology/architecture.excalidraw b/docs/versioned_docs/version-3.14/20-usage/15-terminology/architecture.excalidraw similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/15-terminology/architecture.excalidraw rename to docs/versioned_docs/version-3.14/20-usage/15-terminology/architecture.excalidraw diff --git a/docs/versioned_docs/version-3.11/20-usage/15-terminology/architecture.svg b/docs/versioned_docs/version-3.14/20-usage/15-terminology/architecture.svg similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/15-terminology/architecture.svg rename to docs/versioned_docs/version-3.14/20-usage/15-terminology/architecture.svg diff --git a/docs/versioned_docs/version-3.11/20-usage/15-terminology/index.md b/docs/versioned_docs/version-3.14/20-usage/15-terminology/index.md similarity index 95% rename from docs/versioned_docs/version-3.11/20-usage/15-terminology/index.md rename to docs/versioned_docs/version-3.14/20-usage/15-terminology/index.md index 310567f1a..222b2dc34 100644 --- a/docs/versioned_docs/version-3.11/20-usage/15-terminology/index.md +++ b/docs/versioned_docs/version-3.14/20-usage/15-terminology/index.md @@ -2,29 +2,29 @@ ## Glossary -- **Woodpecker CI**: The project name around Woodpecker. -- **Woodpecker**: An open-source tool that executes [pipelines][Pipeline] on your code. -- **Server**: The component of Woodpecker that handles webhooks from forges, orchestrates agents, and sends status back. It also serves the API and web UI for administration and configuration. - **Agent**: A component of Woodpecker that executes [pipelines][Pipeline] (specifically one or more [workflows][Workflow]) with a specific backend (e.g. [Docker][], Kubernetes, [local][Local]). It connects to the server via GRPC. - **CLI**: The Woodpecker command-line interface (CLI) is a terminal tool used to administer the server, to execute pipelines locally for debugging / testing purposes, and to perform tasks like linting pipelines. -- **[Pipeline][Pipeline]**: A sequence of [workflows][Workflow] that are executed on the code. Pipelines are triggered by events. -- **[Workflow][Workflow]**: A sequence of steps and services that are executed as part of a [pipeline][Pipeline]. Workflows are represented by YAML files. Each workflow has its own isolated [workspace][Workspace], and often additional resources like a shared network (docker). -- **Steps**: Individual commands, actions or tasks within a [workflow][Workflow]. - **Code**: Refers to the files tracked by the version control system used by the [forge][Forge]. -- **Repos**: Short for repositories, these are storage locations where code is stored. -- **[Forge][Forge]**: The hosting platform or service where the repositories are hosted. -- **[Workspace][workspace]**: A folder shared between all steps of a [workflow][Workflow] containing the repository and all the generated data from previous steps. -- **[Event][Event]**: Triggers the execution of a [pipeline][Pipeline], such as a [forge][Forge] event like `push`, or `manual` triggered manually from the UI. - **Commit**: A defined state of the code, usually associated with a version control system like Git. -- **[Matrix][Matrix]**: A configuration option that allows the execution of [workflows][Workflow] for each value in the matrix. -- **Service**: A service is a step that is executed from the start of a [workflow][Workflow] until its end. It can be accessed by name via the network from other steps within the same [workflow][Workflow]. -- **[Plugins][Plugin]**: Plugins are extensions that provide pre-defined actions or commands for a step in a [workflow][Workflow]. They can be configured via settings. - **Container**: A lightweight and isolated environment where commands are executed. -- **YAML File**: A file format used to define and configure [workflows][Workflow]. - **Dependency**: [Workflows][Workflow] can depend on each other, and if possible, they are executed in parallel. +- **[Event][Event]**: Triggers the execution of a [pipeline][Pipeline], such as a [forge][Forge] event like `push`, or `manual` triggered manually from the UI. +- **[Extension][Extension]**: Some parts of Woodpecker internal services like secrets storage or config fetcher can be replaced through extensions. +- **[Forge][Forge]**: The hosting platform or service where the repositories are hosted. +- **[Matrix][Matrix]**: A configuration option that allows the execution of [workflows][Workflow] for each value in the matrix. +- **[Pipeline][Pipeline]**: A sequence of [workflows][Workflow] that are executed on the code. Pipelines are triggered by events. +- **[Plugins][Plugin]**: Plugins are extensions that provide pre-defined actions or commands for a step in a [workflow][Workflow]. They can be configured via settings. +- **Repos**: Short for repositories, these are storage locations where code is stored. +- **Server**: The component of Woodpecker that handles webhooks from forges, orchestrates agents, and sends status back. It also serves the API and web UI for administration and configuration. +- **Service**: A service is a step that is executed from the start of a [workflow][Workflow] until its end. It can be accessed by name via the network from other steps within the same [workflow][Workflow]. - **Status**: Status refers to the outcome of a step or [workflow][Workflow] after it has been executed, determined by the internal command exit code. At the end of a [workflow][Workflow], its status is sent to the [forge][Forge]. -- **Service extension**: Some parts of Woodpecker internal services like secrets storage or config fetcher can be replaced through service extensions. +- **Steps**: Individual commands, actions or tasks within a [workflow][Workflow]. - **Task**: A task is a [workflow][Workflow] that's currently waiting for its execution in the task queue. +- **Woodpecker**: An open-source tool that executes [pipelines][Pipeline] on your code. +- **Woodpecker CI**: The project name around Woodpecker. +- **[Workflow][Workflow]**: A sequence of steps and services that are executed as part of a [pipeline][Pipeline]. Workflows are represented by YAML files. Each workflow has its own isolated [workspace][Workspace], and often additional resources like a shared network (docker). +- **[Workspace][workspace]**: A folder shared between all steps of a [workflow][Workflow] containing the repository and all the generated data from previous steps. +- **YAML File**: A file format used to define and configure [workflows][Workflow]. ## Woodpecker architecture @@ -54,3 +54,4 @@ Sometimes there are multiple terms that can be used to describe something. This [Matrix]: ../30-matrix-workflows.md [Docker]: ../../30-administration/10-configuration/11-backends/10-docker.md [Local]: ../../30-administration/10-configuration/11-backends/30-local.md +[Extension]: ../72-extensions/index.md diff --git a/docs/versioned_docs/version-3.11/20-usage/15-terminology/pipeline-workflow-step.excalidraw b/docs/versioned_docs/version-3.14/20-usage/15-terminology/pipeline-workflow-step.excalidraw similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/15-terminology/pipeline-workflow-step.excalidraw rename to docs/versioned_docs/version-3.14/20-usage/15-terminology/pipeline-workflow-step.excalidraw diff --git a/docs/versioned_docs/version-3.11/20-usage/15-terminology/pipeline-workflow-step.svg b/docs/versioned_docs/version-3.14/20-usage/15-terminology/pipeline-workflow-step.svg similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/15-terminology/pipeline-workflow-step.svg rename to docs/versioned_docs/version-3.14/20-usage/15-terminology/pipeline-workflow-step.svg diff --git a/docs/versioned_docs/version-3.11/20-usage/20-workflow-syntax.md b/docs/versioned_docs/version-3.14/20-usage/20-workflow-syntax.md similarity index 95% rename from docs/versioned_docs/version-3.11/20-usage/20-workflow-syntax.md rename to docs/versioned_docs/version-3.14/20-usage/20-workflow-syntax.md index 386a1bcec..417e74cf8 100644 --- a/docs/versioned_docs/version-3.11/20-usage/20-workflow-syntax.md +++ b/docs/versioned_docs/version-3.14/20-usage/20-workflow-syntax.md @@ -196,6 +196,8 @@ Some of the steps may be allowed to fail without causing the whole workflow and + failure: ignore ``` +If you would like to cancel the full pipeline once the step fails, you can set `failure: cancel`. + ### `when` - Conditional Execution Woodpecker supports defining a list of conditions for a step by using a `when` block. If at least one of the conditions in the `when` block evaluate to true the step is executed, otherwise it is skipped. A condition is evaluated to true if _all_ sub-conditions are true. @@ -340,7 +342,14 @@ when: #### `status` -There are use cases for executing steps on failure, such as sending notifications for failed workflow/pipeline. Use the status constraint to execute steps even when the workflow fails: +By default, steps only run when the workflow has succeeded up to that point,
+which is equivalent to `status: [ success ]`. + +The `status` filter lets you override this behavior. +The only accepted values are `success` and `failure`. + +A common use case is executing a step on failure, such as sending notifications for a failed workflow/pipeline. +To run a step regardless of outcome, list both values: ```diff steps: @@ -350,6 +359,18 @@ There are use cases for executing steps on failure, such as sending notification + - status: [ success, failure ] ``` +The filter is aware of the other filters. If you want to run on failures if the event is `tag`, but if it's a `pull_request`, run it on both success and failure: + +```diff + when: ++ - event: tag ++ status: [ failure ] ++ - event: pull_request ++ status: [ success, failure ] +``` + +If there's no matching filter at all or all matching filters don't have set `status`, it will use the default, which means it runs on success only. In the example above this will happen if the event is neither `tag` nor `pull_request`. + #### `platform` :::note @@ -393,7 +414,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: @@ -761,10 +782,6 @@ The workflow now triggers on `main`, but also if the target branch of a pull req Woodpecker supports to define multiple workflows for a repository. Those workflows will run independent from each other. To depend them on each other you can use the [`depends_on`](./25-workflows.md#flow-control) keyword. -## `runs_on` - -Workflows that should run even on failure should set the `runs_on` tag. See [here](./25-workflows.md#flow-control) for an example. - ## Advanced network options for steps :::warning diff --git a/docs/versioned_docs/version-3.11/20-usage/25-workflows.md b/docs/versioned_docs/version-3.14/20-usage/25-workflows.md similarity index 94% rename from docs/versioned_docs/version-3.11/20-usage/25-workflows.md rename to docs/versioned_docs/version-3.14/20-usage/25-workflows.md index ef09d485e..2483d07eb 100644 --- a/docs/versioned_docs/version-3.11/20-usage/25-workflows.md +++ b/docs/versioned_docs/version-3.14/20-usage/25-workflows.md @@ -97,7 +97,7 @@ The name for a `depends_on` entry is the filename without the path, leading dots + - test ``` -Workflows that need to run even on failures should set the `runs_on` tag. +Workflows that need to run even on failures should set the `status` filter. ```diff steps: @@ -109,9 +109,12 @@ Workflows that need to run even on failures should set the `runs_on` tag. depends_on: - deploy -+runs_on: [ success, failure ] ++when: ++ - status: [ success, failure ] ``` +This works just like the [`status` filter for steps](./20-workflow-syntax.md#status). + :::info Some workflows don't need the source code, like creating a notification on failure. Read more about `skip_clone` at [pipeline syntax](./20-workflow-syntax.md#skip_clone) diff --git a/docs/versioned_docs/version-3.11/20-usage/30-matrix-workflows.md b/docs/versioned_docs/version-3.14/20-usage/30-matrix-workflows.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/30-matrix-workflows.md rename to docs/versioned_docs/version-3.14/20-usage/30-matrix-workflows.md diff --git a/docs/versioned_docs/version-3.11/20-usage/40-secrets.md b/docs/versioned_docs/version-3.14/20-usage/40-secrets.md similarity index 99% rename from docs/versioned_docs/version-3.11/20-usage/40-secrets.md rename to docs/versioned_docs/version-3.14/20-usage/40-secrets.md index a38599635..6cdaef519 100644 --- a/docs/versioned_docs/version-3.11/20-usage/40-secrets.md +++ b/docs/versioned_docs/version-3.14/20-usage/40-secrets.md @@ -106,7 +106,7 @@ woodpecker-cli repo secret add \ Create the secret and limit it to a single image: ```diff - woodpecker-cli repo secret add \ + woodpecker-cli secret add \ --repository octocat/hello-world \ + --image woodpeckerci/plugin-s3 \ --name aws_access_key_id \ diff --git a/docs/versioned_docs/version-3.11/20-usage/41-registries.md b/docs/versioned_docs/version-3.14/20-usage/41-registries.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/41-registries.md rename to docs/versioned_docs/version-3.14/20-usage/41-registries.md diff --git a/docs/versioned_docs/version-3.11/20-usage/45-cron.md b/docs/versioned_docs/version-3.14/20-usage/45-cron.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/45-cron.md rename to docs/versioned_docs/version-3.14/20-usage/45-cron.md diff --git a/docs/versioned_docs/version-3.11/20-usage/50-environment.md b/docs/versioned_docs/version-3.14/20-usage/50-environment.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/50-environment.md rename to docs/versioned_docs/version-3.14/20-usage/50-environment.md diff --git a/docs/versioned_docs/version-3.11/20-usage/51-plugins/20-creating-plugins.md b/docs/versioned_docs/version-3.14/20-usage/51-plugins/20-creating-plugins.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/51-plugins/20-creating-plugins.md rename to docs/versioned_docs/version-3.14/20-usage/51-plugins/20-creating-plugins.md diff --git a/docs/versioned_docs/version-3.11/20-usage/51-plugins/51-overview.md b/docs/versioned_docs/version-3.14/20-usage/51-plugins/51-overview.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/51-plugins/51-overview.md rename to docs/versioned_docs/version-3.14/20-usage/51-plugins/51-overview.md diff --git a/docs/versioned_docs/version-3.11/20-usage/51-plugins/_category_.yaml b/docs/versioned_docs/version-3.14/20-usage/51-plugins/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/51-plugins/_category_.yaml rename to docs/versioned_docs/version-3.14/20-usage/51-plugins/_category_.yaml diff --git a/docs/versioned_docs/version-3.11/20-usage/60-services.md b/docs/versioned_docs/version-3.14/20-usage/60-services.md similarity index 86% rename from docs/versioned_docs/version-3.11/20-usage/60-services.md rename to docs/versioned_docs/version-3.14/20-usage/60-services.md index 3697fef48..fa243f70f 100644 --- a/docs/versioned_docs/version-3.11/20-usage/60-services.md +++ b/docs/versioned_docs/version-3.14/20-usage/60-services.md @@ -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. diff --git a/docs/versioned_docs/version-3.11/20-usage/70-volumes.md b/docs/versioned_docs/version-3.14/20-usage/70-volumes.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/70-volumes.md rename to docs/versioned_docs/version-3.14/20-usage/70-volumes.md diff --git a/docs/versioned_docs/version-3.11/20-usage/72-extensions/40-configuration-extension.md b/docs/versioned_docs/version-3.14/20-usage/72-extensions/40-configuration-extension.md similarity index 84% rename from docs/versioned_docs/version-3.11/20-usage/72-extensions/40-configuration-extension.md rename to docs/versioned_docs/version-3.14/20-usage/72-extensions/40-configuration-extension.md index 63d806117..f263a0693 100644 --- a/docs/versioned_docs/version-3.11/20-usage/72-extensions/40-configuration-extension.md +++ b/docs/versioned_docs/version-3.14/20-usage/72-extensions/40-configuration-extension.md @@ -23,26 +23,33 @@ 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 +WOODPECKER_CONFIG_EXTENSION_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. +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: +:::info +The `netrc` field is only included in the request when the global `WOODPECKER_CONFIG_EXTENSION_NETRC` is set to `true` (default: `false`) or the per-repo "Send netrc credentials" is checked. +::: + ```ts class Request { repo: Repo; pipeline: Pipeline; - netrc: Netrc; - configuration: { + netrc?: Netrc; // only included when netrc sending is enabled (see above) + 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,12 +130,17 @@ 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" } - ] + ], + "netrc": { + "machine": "myforge.com", + "login": "myUser", + "password": "forge-access-token" + } } ``` diff --git a/docs/versioned_docs/version-3.14/20-usage/72-extensions/50-registry-extension.md b/docs/versioned_docs/version-3.14/20-usage/72-extensions/50-registry-extension.md new file mode 100644 index 000000000..c4f460327 --- /dev/null +++ b/docs/versioned_docs/version-3.14/20-usage/72-extensions/50-registry-extension.md @@ -0,0 +1,160 @@ +# Registry extension + +Woodpecker uses the registry extension to get registry credentials. 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: + +- Centralize registry credential management +- Use an external storage for credentials +- Dynamically manage which credentials Woodpecker should use + +## 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 registry extension. + +If both the global and the repo-level extension return credentials for a registry, it will use the credentials from the repo extension. + +```ini title="Server" +WOODPECKER_REGISTRY_EXTENSION_ENDPOINT=https://example.com/ciconfig +``` + +## How it works + +When a pipeline is triggered, Woodpecker will fetch the credentials from your service. As fallback, it uses the credentials configured directly in Woodpecker. + +### Request + +The extension receives an HTTP POST request with the following JSON payload: + +:::info +The `netrc` field is only included in the request when the global `WOODPECKER_REGISTRY_EXTENSION_NETRC` is set to `true` (default: `false`) or the per-repo "Send netrc credentials" is checked. +::: + +```ts +class Request { + repo: Repo; + pipeline: Pipeline; + netrc?: Netrc; // only included when netrc sending is enabled (see above) +} +``` + +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 clone the repository or even use the forge (Github or Gitlab, ...) API to get more information about 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": "forge-access-token" + } +} +``` + +### 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 { + registries: { + address: string; // the docker registry address + username: string; // registry username + password: string; // registry password + }[]; +} +``` + +Example response: + +```json +{ + "registries": [ + { + "address": "docker.io", + "username": "woodpecker-bot", + "password": "your-pass-word-123" + } + ] +} +``` diff --git a/docs/versioned_docs/version-3.14/20-usage/72-extensions/55-secret-extension.md b/docs/versioned_docs/version-3.14/20-usage/72-extensions/55-secret-extension.md new file mode 100644 index 000000000..78f46b651 --- /dev/null +++ b/docs/versioned_docs/version-3.14/20-usage/72-extensions/55-secret-extension.md @@ -0,0 +1,166 @@ +# Secret extension + +Woodpecker uses the secret extension to get secrets from an external service. 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: + +- Centralize secret management (e.g. HashiCorp Vault, AWS Secrets Manager) +- Dynamically generate secrets per pipeline + +## 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. +::: + +## 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 secret extension. + +If both the global and the repo-level extension return a secret with the same name, it will use the secret from the repo extension. + +```ini title="Server" +WOODPECKER_SECRET_EXTENSION_ENDPOINT=https://example.com/secrets +WOODPECKER_SECRET_EXTENSION_NETRC=false +``` + +## How it works + +When a pipeline is triggered, Woodpecker will fetch secrets from your service. The extension secrets are merged with the secrets configured directly in Woodpecker, with extension secrets taking priority by name. If the extension is unavailable, Woodpecker falls back to the locally configured secrets. + +### Request + +The extension receives an HTTP POST request with the following JSON payload: + +:::info +The `netrc` field is only included in the request when the global `WOODPECKER_SECRET_EXTENSION_NETRC` is set to `true` (default: `false`) or the per-repo "Send netrc credentials" is checked. +::: + +```ts +class Request { + repo: Repo; + pipeline: Pipeline; + netrc?: Netrc; // only included when netrc sending is enabled (see above) +} +``` + +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 clone the repository or even use the forge (Github or Gitlab, ...) API to get more information about 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": "forge-access-token" + } +} +// Note: the "netrc" field is omitted when netrc sending is not enabled. +``` + +### Response + +The extension should respond with a JSON object containing a `secrets` array. +If the extension wants to keep the existing secrets without adding any, it can respond with HTTP status `204 No Content`. + +```ts +class Response { + secrets: { + name: string; // the secret name, matched by from_secret in pipeline config + value: string; // the secret value + images?: string[]; // optional: restrict to specific plugins + events?: string[]; // optional: restrict to specific pipeline events + }[]; +} +``` + +Example response: + +```json +{ + "secrets": [ + { + "name": "docker_password", + "value": "your-secret-password-123" + }, + { + "name": "deploy_token", + "value": "super-secret-token", + "events": ["push", "tag"] + } + ] +} +``` diff --git a/docs/versioned_docs/version-3.11/20-usage/72-extensions/_category_.yaml b/docs/versioned_docs/version-3.14/20-usage/72-extensions/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/72-extensions/_category_.yaml rename to docs/versioned_docs/version-3.14/20-usage/72-extensions/_category_.yaml diff --git a/docs/versioned_docs/version-3.11/20-usage/72-extensions/index.md b/docs/versioned_docs/version-3.14/20-usage/72-extensions/index.md similarity index 91% rename from docs/versioned_docs/version-3.11/20-usage/72-extensions/index.md rename to docs/versioned_docs/version-3.14/20-usage/72-extensions/index.md index e30cb53f4..4f66dd288 100644 --- a/docs/versioned_docs/version-3.11/20-usage/72-extensions/index.md +++ b/docs/versioned_docs/version-3.14/20-usage/72-extensions/index.md @@ -5,6 +5,8 @@ Woodpecker allows you to replace internal logic with external extensions by usin There is currently one type of extension available: - [Configuration extension](./40-configuration-extension.md) to modify or generate pipeline configurations on the fly. +- [Registry extension](./50-registry-extension.md) to get registry credentials from the extension. +- [Secret extension](./55-secret-extension.md) to get secrets from an external service. ## Security diff --git a/docs/versioned_docs/version-3.11/20-usage/72-linter.md b/docs/versioned_docs/version-3.14/20-usage/72-linter.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/72-linter.md rename to docs/versioned_docs/version-3.14/20-usage/72-linter.md diff --git a/docs/versioned_docs/version-3.11/20-usage/75-project-settings.md b/docs/versioned_docs/version-3.14/20-usage/75-project-settings.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/75-project-settings.md rename to docs/versioned_docs/version-3.14/20-usage/75-project-settings.md diff --git a/docs/versioned_docs/version-3.11/20-usage/80-badges.md b/docs/versioned_docs/version-3.14/20-usage/80-badges.md similarity index 54% rename from docs/versioned_docs/version-3.11/20-usage/80-badges.md rename to docs/versioned_docs/version-3.14/20-usage/80-badges.md index 1bee62a58..44ea89460 100644 --- a/docs/versioned_docs/version-3.11/20-usage/80-badges.md +++ b/docs/versioned_docs/version-3.14/20-usage/80-badges.md @@ -15,4 +15,10 @@ The status badge displays the status for the latest build to your default branch +:///api/badges//status.svg?branch= ``` -Please note status badges do not include pull request results, since the status of a pull request does not provide an accurate representation of your repository state. +By default status badges do not include pull request results, since the status of a pull request does not provide an accurate representation of your repository state. +If you'd like to respect other or further events, you can add the `events` query parameter, otherwise the badge represents only the state of the last push event: + +```diff +-:///api/badges//status.svg ++:///api/badges//status.svg?events=manual,cron +``` diff --git a/docs/versioned_docs/version-3.11/20-usage/90-advanced-usage.md b/docs/versioned_docs/version-3.14/20-usage/90-advanced-usage.md similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/90-advanced-usage.md rename to docs/versioned_docs/version-3.14/20-usage/90-advanced-usage.md diff --git a/docs/versioned_docs/version-3.11/20-usage/_category_.yaml b/docs/versioned_docs/version-3.14/20-usage/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/_category_.yaml rename to docs/versioned_docs/version-3.14/20-usage/_category_.yaml diff --git a/docs/versioned_docs/version-3.11/20-usage/cron-settings.png b/docs/versioned_docs/version-3.14/20-usage/cron-settings.png similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/cron-settings.png rename to docs/versioned_docs/version-3.14/20-usage/cron-settings.png diff --git a/docs/versioned_docs/version-3.11/20-usage/linter-warnings-errors.png b/docs/versioned_docs/version-3.14/20-usage/linter-warnings-errors.png similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/linter-warnings-errors.png rename to docs/versioned_docs/version-3.14/20-usage/linter-warnings-errors.png diff --git a/docs/versioned_docs/version-3.11/20-usage/pipeline.png b/docs/versioned_docs/version-3.14/20-usage/pipeline.png similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/pipeline.png rename to docs/versioned_docs/version-3.14/20-usage/pipeline.png diff --git a/docs/versioned_docs/version-3.11/20-usage/project-settings.png b/docs/versioned_docs/version-3.14/20-usage/project-settings.png similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/project-settings.png rename to docs/versioned_docs/version-3.14/20-usage/project-settings.png diff --git a/docs/versioned_docs/version-3.11/20-usage/repo-new.png b/docs/versioned_docs/version-3.14/20-usage/repo-new.png similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/repo-new.png rename to docs/versioned_docs/version-3.14/20-usage/repo-new.png diff --git a/docs/versioned_docs/version-3.11/20-usage/secrets-plugins-filter.png b/docs/versioned_docs/version-3.14/20-usage/secrets-plugins-filter.png similarity index 100% rename from docs/versioned_docs/version-3.11/20-usage/secrets-plugins-filter.png rename to docs/versioned_docs/version-3.14/20-usage/secrets-plugins-filter.png diff --git a/docs/versioned_docs/version-3.11/30-administration/00-general.md b/docs/versioned_docs/version-3.14/30-administration/00-general.md similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/00-general.md rename to docs/versioned_docs/version-3.14/30-administration/00-general.md diff --git a/docs/versioned_docs/version-3.11/30-administration/05-installation/10-docker-compose.md b/docs/versioned_docs/version-3.14/30-administration/05-installation/10-docker-compose.md similarity index 92% rename from docs/versioned_docs/version-3.11/30-administration/05-installation/10-docker-compose.md rename to docs/versioned_docs/version-3.14/30-administration/05-installation/10-docker-compose.md index 3ac2400e3..7f8fea552 100644 --- a/docs/versioned_docs/version-3.11/30-administration/05-installation/10-docker-compose.md +++ b/docs/versioned_docs/version-3.14/30-administration/05-installation/10-docker-compose.md @@ -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. diff --git a/docs/versioned_docs/version-3.11/30-administration/05-installation/20-helm-chart.md b/docs/versioned_docs/version-3.14/30-administration/05-installation/20-helm-chart.md similarity index 91% rename from docs/versioned_docs/version-3.11/30-administration/05-installation/20-helm-chart.md rename to docs/versioned_docs/version-3.14/30-administration/05-installation/20-helm-chart.md index d421f8959..54a14eddb 100644 --- a/docs/versioned_docs/version-3.11/30-administration/05-installation/20-helm-chart.md +++ b/docs/versioned_docs/version-3.14/30-administration/05-installation/20-helm-chart.md @@ -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 ``` ## Metrics diff --git a/docs/versioned_docs/version-3.11/30-administration/05-installation/30-packages.md b/docs/versioned_docs/version-3.14/30-administration/05-installation/30-packages.md similarity index 98% rename from docs/versioned_docs/version-3.11/30-administration/05-installation/30-packages.md rename to docs/versioned_docs/version-3.14/30-administration/05-installation/30-packages.md index ca4c55e2f..0b346a9bc 100644 --- a/docs/versioned_docs/version-3.11/30-administration/05-installation/30-packages.md +++ b/docs/versioned_docs/version-3.14/30-administration/05-installation/30-packages.md @@ -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 diff --git a/docs/versioned_docs/version-3.11/30-administration/05-installation/_category_.yaml b/docs/versioned_docs/version-3.14/30-administration/05-installation/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/05-installation/_category_.yaml rename to docs/versioned_docs/version-3.14/30-administration/05-installation/_category_.yaml diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/10-server.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/10-server.md similarity index 89% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/10-server.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/10-server.md index 182a9add9..6b6fcb695 100644 --- a/docs/versioned_docs/version-3.11/30-administration/10-configuration/10-server.md +++ b/docs/versioned_docs/version-3.14/30-administration/10-configuration/10-server.md @@ -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 @@ -1062,12 +971,82 @@ Supported variables: --- -### CONFIG_SERVICE_ENDPOINT +### CONFIG_EXTENSION_ENDPOINT -- Name: `WOODPECKER_CONFIG_SERVICE_ENDPOINT` +- Name: `WOODPECKER_CONFIG_EXTENSION_ENDPOINT` - Default: none -Specify a configuration service endpoint, see [Configuration Extension](#external-configuration-api) +Specify a configuration extension endpoint, see [Configuration Extension](../../20-usage/72-extensions/40-configuration-extension.md) + +--- + +### CONFIG_EXTENSION_EXCLUSIVE + +- Name: `CONFIG_EXTENSION_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. +::: + +--- + +### CONFIG_EXTENSION_NETRC + +- Name: `WOODPECKER_CONFIG_EXTENSION_NETRC` +- Default: false + +Send `netrc` to the config extension endpoint. + +:::warning +The `netrc` data is pretty powerful as it contains credentials to access the repository. You can use this to clone the repository or even use the forge API to get more information about the repository. +::: + +--- + +### SECRET_EXTENSION_ENDPOINT + +- Name: `WOODPECKER_SECRET_EXTENSION_ENDPOINT` +- Default: none + +Specify a secret extension endpoint, see [Secret Extension](../../20-usage/72-extensions/55-secret-extension.md) + +--- + +### SECRET_EXTENSION_NETRC + +- Name: `WOODPECKER_SECRET_EXTENSION_NETRC` +- Default: false + +Send `netrc` to the secret extension endpoint. + +:::warning +The `netrc` data is pretty powerful as it contains credentials to access the repository. You can use this to clone the repository or even use the forge API to get more information about the repository. +::: + +--- + +### REGISTRY_EXTENSION_ENDPOINT + +- Name: `WOODPECKER_REGISTRY_EXTENSION_ENDPOINT` +- Default: none + +Specify a registry extension endpoint, see [Registry Extension](../../20-usage/72-extensions/50-registry-extension.md) + +--- + +### REGISTRY_EXTENSION_NETRC + +- Name: `WOODPECKER_REGISTRY_EXTENSION_NETRC` +- Default: false + +Send `netrc` to the registry extension endpoint. + +:::warning +The `netrc` data is pretty powerful as it contains credentials to access the repository. You can use this to clone the repository or even use the forge API to get more information about the repository. +::: --- @@ -1121,7 +1100,11 @@ Disable version check in admin web UI. - Name: `WOODPECKER_LOG_STORE` - Default: `database` -Where to store logs. Possible values: `database` or `file`. +Where to store logs. Possible values: + +- `database`: stores the logs in the database +- `file`: stores logs in JSON files on the files system +- `addon`: uses an [addon](./100-addons.md#log) to store logs --- @@ -1130,7 +1113,10 @@ Where to store logs. Possible values: `database` or `file`. - Name: `WOODPECKER_LOG_STORE_FILE_PATH` - Default: none -Directory to store logs in if [`WOODPECKER_LOG_STORE`](#log_store) is `file`. +If [`WOODPECKER_LOG_STORE`](#log_store) is: + +- `file`: Directory to store logs in +- `addon`: The path to the addon executable --- @@ -1160,6 +1146,19 @@ Fully qualified public forge URL, used if forge url is not a public URL. Format: --- +### FORCE_IGNORE_SERVICE_FAILURE + +- Name: `WOODPECKER_FORCE_IGNORE_SERVICE_FAILURE` +- Default: true + +:::warning +Since v3.14.0, Woodpecker can report the status of services and detached steps. +Because these can now fail, until v4.0.0 is released, service failures are ignored by default to preserve backward compatibility. +We encourage you to disable this option and update your pipeline configuration. +::: + +--- + ### GITHUB\_\* See [GitHub configuration](./12-forges/20-github.md#configuration) diff --git a/docs/versioned_docs/version-3.14/30-administration/10-configuration/100-addons.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/100-addons.md new file mode 100644 index 000000000..12d297e7f --- /dev/null +++ b/docs/versioned_docs/version-3.14/30-administration/10-configuration/100-addons.md @@ -0,0 +1,42 @@ +# Addons + +Addons can be used to extend the Woodpecker server. Currently, they can be used for forges and the log service. + +:::warning +Addon forges are still experimental. Their implementation can change and break at any time. +::: + +:::danger +You must trust the author of the addon forge you are using. They may have access to authentication codes and other potentially sensitive information. +::: + +## Usage + +To use an addon forge, download the correct addon version. + +### Forge + +Use this in your `.env`: + +```ini +WOODPECKER_ADDON_FORGE=/path/to/your/addon/forge/file +``` + +In case you run Woodpecker as container, you probably want to mount the addon binary to `/opt/addons/`. + +#### List of addon forges + +- [Radicle](https://radicle.xyz/): Open source, peer-to-peer code collaboration stack built on Git. Radicle addon for Woodpecker CI can be found at [this repo](https://explorer.radicle.gr/nodes/seed.radicle.gr/rad:z39Cf1XzrvCLRZZJRUZnx9D1fj5ws). + +### Log + +Use this in your `.env`: + +```ini +WOODPECKER_LOG_STORE=addon +WOODPECKER_LOG_STORE_FILE_PATH=/path/to/your/addon/forge/file +``` + +## Developing addon forges + +See [Addons](../../92-development/100-addons.md). diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/10-docker.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/10-docker.md similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/10-docker.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/10-docker.md diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/20-kubernetes.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/20-kubernetes.md similarity index 72% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/20-kubernetes.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/20-kubernetes.md index 8ba332322..a6f43a683 100644 --- a/docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/20-kubernetes.md +++ b/docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/20-kubernetes.md @@ -145,6 +145,100 @@ steps: value: 'value1' effect: 'NoSchedule' tolerationSeconds: 3600 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: topology.kubernetes.io/zone + operator: In + values: + - eu-central-1a + - eu-central-1b +``` + +### Affinity + +Kubernetes [affinity and anti-affinity](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity) rules allow you to constrain which nodes your pods can be scheduled on based on node labels, or co-locate/spread pods relative to other pods. + +You can configure affinity at two levels: + +1. **Per-step via `backend_options.kubernetes.affinity`** (shown in example above) - requires agent configuration to allow it +2. **Agent-wide via `WOODPECKER_BACKEND_K8S_POD_AFFINITY`** - applies to all pods unless overridden + +#### Agent-wide affinity + +To apply affinity rules to all workflow pods, configure the agent with YAML-formatted affinity: + +```yaml +WOODPECKER_BACKEND_K8S_POD_AFFINITY: | + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/worker + operator: In + values: + - "true" +``` + +By default, per-step affinity settings are **not allowed** for security reasons. To enable them: + +```bash +WOODPECKER_BACKEND_K8S_POD_AFFINITY_ALLOW_FROM_STEP: true +``` + +:::warning +Enabling `WOODPECKER_BACKEND_K8S_POD_AFFINITY_ALLOW_FROM_STEP` in multi-tenant environments allows pipeline authors to control pod placement, which may have security or resource isolation implications. +::: + +When per-step affinity is allowed and specified, it **replaces** the agent-wide affinity entirely (not merged). + +#### Example: agent affinity for co-location + +This example configures all workflow pods within a workflow to be co-located on the same node, while requiring other workflows run on different nodes. + +It uses `matchLabelKeys` to dynamically match pods with the same `woodpecker-ci.org/task-uuid`, and `mismatchLabelKeys` to separating pods with different task UUIDs: + +```yaml +WOODPECKER_BACKEND_K8S_POD_AFFINITY: | + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: {} + matchLabelKeys: + - woodpecker-ci.org/task-uuid + topologyKey: "kubernetes.io/hostname" + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: {} + mismatchLabelKeys: + - woodpecker-ci.org/task-uuid + topologyKey: "kubernetes.io/hostname" +``` + +:::note +The `matchLabelKeys` and `mismatchLabelKeys` features require Kubernetes v1.29+ (alpha with feature gate `MatchLabelKeysInPodAffinity`) or v1.33+ (beta, enabled by default). These fields allow the Kubernetes API server to dynamically populate label selectors at pod creation time, eliminating the need to hardcode values like `$(WOODPECKER_TASK_UUID)`. +::: + +#### Example: Node affinity for GPU workloads + +Ensure a step runs only on GPU-enabled nodes: + +```yaml +steps: + - name: train-model + image: tensorflow/tensorflow:latest-gpu + backend_options: + kubernetes: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: accelerator + operator: In + values: + - nvidia-tesla-v100 ``` ### Volumes @@ -153,7 +247,7 @@ To mount volumes a PersistentVolume (PV) and PersistentVolumeClaim (PVC) are nee Persistent volumes must be created manually. Use the Kubernetes [Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) documentation as a reference. -_If your PVC is not highly available or NFS-based, you may also need to integrate affinity settings to ensure that your steps are executed on the correct node._ +_If your PVC is not highly available or NFS-based, use the `affinity` settings (documented above) to ensure that your steps are executed on the correct node._ NOTE: If you plan to use this volume in more than one workflow concurrently, make sure you have configured the PVC in `RWX` mode. Keep in mind that this feature must be supported by the used CSI driver: @@ -265,6 +359,26 @@ backend_options: The feature requires Kubernetes v1.30 or above. ::: +You can set `allowPrivilegeEscalation` to `false` to prevent a container from gaining more privileges than its parent process. + +```yaml +backend_options: + kubernetes: + securityContext: + allowPrivilegeEscalation: false +``` + +You can also drop [Linux capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html) from a container. Adding capabilities is not allowed. + +```yaml +backend_options: + kubernetes: + securityContext: + capabilities: + drop: + - ALL +``` + ### Annotations and labels You can specify arbitrary [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) and [labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) to be set on the Pod definition for a given workflow step using the following configuration: @@ -304,6 +418,36 @@ It configures the address of the Kubernetes API server to connect to. If running the agent within Kubernetes, this will already be set and you don't have to add it manually. +### Headless services + +For each workflow run a [headless services](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services) is created, +and all steps asigned the subdomain that matches the headless service, so any step can reach other steps via DNS by using the step name as hostname. + +Using the headless services, the step pod is connected to directly, so any port on the other step pods can be reached. + +This is useful for some use-cases, like test-containers in a docker-in-docker setup, where the step needs to connect to many ports on the docker host service. + +```yaml +steps: + - name: test + image: docker:cli # use 'docker:-cli' or similar in production + environment: + DOCKER_HOST: 'tcp://docker:2376' + DOCKER_CERT_PATH: '/woodpecker/dind-certs/client' + DOCKER_TLS_VERIFY: '1' + commands: + - docker run hello-world + + - name: docker + image: docker:dind # use 'docker:-dind' or similar in production + detached: true + privileged: true + environment: + DOCKER_TLS_CERTDIR: /woodpecker/dind-certs +``` + +If ports are defined on a service, then woodpecker will create a normal service for the pod, which use hosts override using the services cluster IP. + ## Environment variables These env vars can be set in the `env:` sections of the agent. diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/30-local.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/30-local.md similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/30-local.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/30-local.md diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/50-custom.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/50-custom.md similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/50-custom.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/50-custom.md diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/_category_.yaml b/docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/11-backends/_category_.yaml rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/11-backends/_category_.yaml diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/11-overview.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/11-overview.md similarity index 90% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/11-overview.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/11-overview.md index 5167f4f35..f384cefc4 100644 --- a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/11-overview.md +++ b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/11-overview.md @@ -11,6 +11,8 @@ | Event: Deploy¹ | :white_check_mark: | :x: | :x: | :x: | :x: | :x: | | [Event: Pull-Request-Metadata](../../../20-usage/50-environment.md#pull_request_metadata-specific-event-reason-values) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | | [Multiple workflows](../../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| [when.path filter](../../../20-usage/20-workflow-syntax.md#path) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | +| [when.path filter](../../../20-usage/20-workflow-syntax.md#path) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | ¹ The deployment event can be triggered for all forges from Woodpecker directly. However, only GitHub can trigger them using webhooks. + +In addition to this, Woodpecker supports [addon forges](../100-addons.md) if the forge you are using does not meet the [Woodpecker requirements](../../../92-development/02-core-ideas.md#forges) or your setup is too specific to be included in the Woodpecker core. diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/20-github.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/20-github.md similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/20-github.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/20-github.md diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/30-gitea.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/30-gitea.md similarity index 77% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/30-gitea.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/30-gitea.md index ad2f92e62..7631101fa 100644 --- a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/30-gitea.md +++ b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/30-gitea.md @@ -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./user/settings/`. It is very important that authorization callback URL matches your http(s) scheme and hostname exactly with `https:///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./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 diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/35-forgejo.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/35-forgejo.md similarity index 77% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/35-forgejo.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/35-forgejo.md index bd45c2aac..56d64f877 100644 --- a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/35-forgejo.md +++ b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/35-forgejo.md @@ -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./user/settings/`. It is very important that authorization callback URL matches your http(s) scheme and hostname exactly with `https:///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./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 diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/40-gitlab.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/40-gitlab.md similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/40-gitlab.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/40-gitlab.md diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/50-bitbucket.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/50-bitbucket.md similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/50-bitbucket.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/50-bitbucket.md diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/60-bitbucket_datacenter.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/60-bitbucket_datacenter.md similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/60-bitbucket_datacenter.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/60-bitbucket_datacenter.md diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/_category_.yaml b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/_category_.yaml rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/_category_.yaml diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/bitbucket_oauth.png b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/bitbucket_oauth.png similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/bitbucket_oauth.png rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/bitbucket_oauth.png diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/bitbucket_permissions.png b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/bitbucket_permissions.png similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/bitbucket_permissions.png rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/bitbucket_permissions.png diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/gitea_oauth.gif b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/gitea_oauth.gif similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/gitea_oauth.gif rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/gitea_oauth.gif diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/github_oauth.png b/docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/github_oauth.png similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/github_oauth.png rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/12-forges/github_oauth.png diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/30-agent.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/30-agent.md similarity index 89% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/30-agent.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/30-agent.md index 589c57b83..6c4d2bdb8 100644 --- a/docs/versioned_docs/version-3.11/30-administration/10-configuration/30-agent.md +++ b/docs/versioned_docs/version-3.14/30-administration/10-configuration/30-agent.md @@ -148,6 +148,19 @@ Configures the number of parallel workflows. --- +### AGENT_SINGLE_WORKFLOW + +- Name: `WOODPECKER_AGENT_SINGLE_WORKFLOW` +- Default: `false` + +Configures the agent to exit (shutdown) after executing one workflow. When configured, +`WOODPECKER_MAX_WORKFLOWS` is forced to 1. + +This one-shot mode is useful in ephemeral environments that are provisioned on demand +by external automation — for example, when an autoscaler spins up a dedicated machine. In these setups, the agent starts, executes exactly one workflow, and exits, allowing the environment to be cleanly torn down afterward. + +--- + ### AGENT_LABELS - Name: `WOODPECKER_AGENT_LABELS` @@ -215,6 +228,19 @@ Configures if the gRPC server certificate should be verified, only valid when `W --- +## RETRY_TIMEOUT + +- Name: `WOODPECKER_RETRY_TIMEOUT` +- Default: `2m` + +Set how long the agent keeps retrying to reconnect to the server after the gRPC connection is lost before giving up. + +:::warning +If set to 0 we retry forever. +::: + +--- + ### BACKEND - Name: `WOODPECKER_BACKEND` diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/40-autoscaler.md b/docs/versioned_docs/version-3.14/30-administration/10-configuration/40-autoscaler.md similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/40-autoscaler.md rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/40-autoscaler.md diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/_category_.yaml b/docs/versioned_docs/version-3.14/30-administration/10-configuration/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/_category_.yaml rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/_category_.yaml diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/new-agent-connected.png b/docs/versioned_docs/version-3.14/30-administration/10-configuration/new-agent-connected.png similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/new-agent-connected.png rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/new-agent-connected.png diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/new-agent-created.png b/docs/versioned_docs/version-3.14/30-administration/10-configuration/new-agent-created.png similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/new-agent-created.png rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/new-agent-created.png diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/new-agent-registration.png b/docs/versioned_docs/version-3.14/30-administration/10-configuration/new-agent-registration.png similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/new-agent-registration.png rename to docs/versioned_docs/version-3.14/30-administration/10-configuration/new-agent-registration.png diff --git a/docs/versioned_docs/version-3.11/30-administration/_category_.yaml b/docs/versioned_docs/version-3.14/30-administration/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-3.11/30-administration/_category_.yaml rename to docs/versioned_docs/version-3.14/30-administration/_category_.yaml diff --git a/docs/versioned_docs/version-3.11/40-cli.md b/docs/versioned_docs/version-3.14/40-cli.md similarity index 69% rename from docs/versioned_docs/version-3.11/40-cli.md rename to docs/versioned_docs/version-3.14/40-cli.md index 1e7a967a2..ff1c2c1f0 100644 --- a/docs/versioned_docs/version-3.11/40-cli.md +++ b/docs/versioned_docs/version-3.14/40-cli.md @@ -36,23 +36,23 @@ woodpecker-cli [GLOBAL OPTIONS] [command [COMMAND OPTIONS]] [ARGUMENTS...] **--config, -c**="": path to config file -**--disable-update-check**: disable update check +**--disable-update-check**: disable update check (default: false) **--log-file**="": Output destination for logs. 'stdout' and 'stderr' can be used as special keywords. (default: stderr) **--log-level**="": set logging level (default: info) -**--nocolor**: disable colored debug output, only has effect if pretty output is set too +**--nocolor**: disable colored debug output, only has effect if pretty output is set too (default: false) -**--pretty**: enable pretty-printed debug output +**--pretty**: enable pretty-printed debug output (default: true) **--server, -s**="": server address -**--skip-verify**: skip ssl verification +**--skip-verify**: skip ssl verification (default: false) **--socks-proxy**="": socks proxy address -**--socks-proxy-off**: socks proxy ignored +**--socks-proxy-off**: socks proxy ignored (default: false) **--token, -t**="": server auth token @@ -75,9 +75,7 @@ manage organizations list organizations -**--format**="": format output (deprecated) (default: {{ .Name }}  -Organization ID: {{ .ID }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nOrganization ID: {{ .ID }}\n) ### registry @@ -103,19 +101,13 @@ remove a registry list registries -**--format**="": format output (deprecated) (default: {{ .Address }}  -Username: {{ .Username }} -Email: {{ .Email }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Address }} \x1b[0m\nUsername: {{ .Username }}\nEmail: {{ .Email }}\n) #### show show registry information -**--format**="": format output (deprecated) (default: {{ .Address }}  -Username: {{ .Username }} -Email: {{ .Email }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Address }} \x1b[0m\nUsername: {{ .Username }}\nEmail: {{ .Email }}\n) **--hostname**="": registry hostname (default: docker.io) @@ -139,9 +131,9 @@ manage global secrets add a secret -**--event**="": secret limited to these events (default: []) +**--event**="": secret limited to these events -**--image**="": secret limited to these images (default: []) +**--image**="": secret limited to these images **--name**="": secret name @@ -157,27 +149,13 @@ remove a secret list secrets -**--format**="": format output (deprecated) (default: {{ .Name }}  -Events: {{ list .Events }} -{{- if .Images }} -Images: {{ list .Images }} -{{- else }} -Images: -{{- end }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nEvents: {{ list .Events }}\n{{- if .Images }}\nImages: {{ list .Images }}\n{{- else }}\nImages: \n{{- end }}\n) #### show show secret information -**--format**="": format output (deprecated) (default: {{ .Name }}  -Events: {{ list .Events }} -{{- if .Images }} -Images: {{ list .Images }} -{{- else }} -Images: -{{- end }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nEvents: {{ list .Events }}\n{{- if .Images }}\nImages: {{ list .Images }}\n{{- else }}\nImages: \n{{- end }}\n) **--name**="": secret name @@ -185,9 +163,9 @@ Images: update a secret -**--event**="": secret limited to these events (default: []) +**--event**="": secret limited to these events -**--image**="": secret limited to these images (default: []) +**--image**="": secret limited to these images **--name**="": secret name @@ -215,8 +193,33 @@ remove a user show user information -**--format**="": format output (default: User: {{ .Login }} -Email: {{ .Email }}) +**--format**="": format output (default: User: {{ .Login }}\nEmail: {{ .Email }}) + +## context, ctx + +manage contexts + +### list, ls + +list all contexts + +**--output**="": output format (default: table) + +**--output-no-headers**: do not print headers in output (default: false) + +**--output-no-headers**: don't print headers (default: false) + +### use + +set the current context + +### delete, rm + +delete a context + +### rename + +rename a context ## exec @@ -228,7 +231,7 @@ execute a local pipeline **--backend-docker-host**="": path to docker socket or url to the docker server -**--backend-docker-ipv6**: backend docker enable IPV6 +**--backend-docker-ipv6**: backend docker enable IPV6 (default: false) **--backend-docker-limit-cpu-quota**="": impose a cpu quota (default: 0) @@ -244,7 +247,9 @@ execute a local pipeline **--backend-docker-network**="": backend docker network -**--backend-docker-tls-verify**: enable or disable TLS verification for connecting to docker server +**--backend-docker-stop-timeout**="": seconds Woodpecker waits for a container to stop gracefully before forcefully killing it (default: 20) + +**--backend-docker-tls-verify**: enable or disable TLS verification for connecting to docker server (default: true) **--backend-docker-volumes**="": backend docker volumes (comma separated) @@ -254,38 +259,46 @@ execute a local pipeline **--backend-https-proxy**="": if set, pass the environment variable down as "HTTPS_PROXY" to steps -**--backend-k8s-allow-native-secrets**: whether to allow existing Kubernetes secrets to be referenced from steps +**--backend-k8s-allow-native-secrets**: whether to allow existing Kubernetes secrets to be referenced from steps (default: false) **--backend-k8s-namespace**="": backend k8s namespace, if used with WOODPECKER_BACKEND_K8S_NAMESPACE_PER_ORGANIZATION, this will be the prefix for the namespace appended with the organization name. (default: woodpecker) -**--backend-k8s-namespace-per-org**: Whether to enable namespace segregation per organization feature. When enabled, Woodpecker will create the Kubernetes resources to separated Kubernetes namespaces per Woodpecker organization. +**--backend-k8s-namespace-per-org**: Whether to enable namespace segregation per organization feature. When enabled, Woodpecker will create the Kubernetes resources to separated Kubernetes namespaces per Woodpecker organization. (default: false) + +**--backend-k8s-pod-affinity**="": backend k8s Agent-wide worker pod affinity, in YAML format + +**--backend-k8s-pod-affinity-allow-from-step**: whether to allow using affinity from step's backend options (default: false) **--backend-k8s-pod-annotations**="": backend k8s additional Agent-wide worker pod annotations -**--backend-k8s-pod-annotations-allow-from-step**: whether to allow using annotations from step's backend options +**--backend-k8s-pod-annotations-allow-from-step**: whether to allow using annotations from step's backend options (default: false) -**--backend-k8s-pod-image-pull-secret-names**="": backend k8s pull secret names for private registries (default: []) +**--backend-k8s-pod-image-pull-secret-names**="": backend k8s pull secret names for private registries **--backend-k8s-pod-labels**="": backend k8s additional Agent-wide worker pod labels -**--backend-k8s-pod-labels-allow-from-step**: whether to allow using labels from step's backend options +**--backend-k8s-pod-labels-allow-from-step**: whether to allow using labels from step's backend options (default: false) **--backend-k8s-pod-node-selector**="": backend k8s Agent-wide worker pod node selector **--backend-k8s-pod-tolerations**="": backend k8s Agent-wide worker pod tolerations -**--backend-k8s-pod-tolerations-allow-from-step**: whether to allow using tolerations from step's backend options +**--backend-k8s-pod-tolerations-allow-from-step**: whether to allow using tolerations from step's backend options (default: true) **--backend-k8s-priority-class**="": which kubernetes priority class to assign to created job pods -**--backend-k8s-secctx-nonroot**: `run as non root` Kubernetes security context option +**--backend-k8s-secctx-nonroot**: `run as non root` Kubernetes security context option (default: false) + +**--backend-k8s-stop-timeout**="": seconds Woodpecker waits for pods to stop gracefully before forcefully killing them (default: 20) **--backend-k8s-storage-class**="": backend k8s storage class -**--backend-k8s-storage-rwx**: backend k8s storage access mode, should ReadWriteMany (RWX) instead of ReadWriteOnce (RWO) be used? (default: true) +**--backend-k8s-storage-rwx**: backend k8s storage access mode, should ReadWriteMany (RWX) instead of ReadWriteOnce (RWO) be used? (default: true) (default: true) **--backend-k8s-volume-size**="": backend k8s volume size (default 10G) (default: 10G) +**--backend-local-isolated-home**: set HOME, USERPROFILE and other variables to an isolated directory, if false we ignore netrc (default: true) + **--backend-local-temp-dir**="": set a different temp dir to clone workflows into (default: system temporary directory) **--backend-no-proxy**="": if set, pass the environment variable down as "NO_PROXY" to steps @@ -300,7 +313,7 @@ execute a local pipeline **--commit-message**="": Set the metadata environment variable "CI_COMMIT_MESSAGE". -**--commit-pull-labels**="": Set the metadata environment variable "CI_COMMIT_PULL_REQUEST_LABELS". (default: []) +**--commit-pull-labels**="": Set the metadata environment variable "CI_COMMIT_PULL_REQUEST_LABELS". **--commit-pull-milestone**="": Set the metadata environment variable "CI_COMMIT_PULL_REQUEST_MILESTONE". @@ -308,17 +321,17 @@ execute a local pipeline **--commit-refspec**="": Set the metadata environment variable "CI_COMMIT_REFSPEC". -**--commit-release-is-pre**: Set the metadata environment variable "CI_COMMIT_PRERELEASE". +**--commit-release-is-pre**: Set the metadata environment variable "CI_COMMIT_PRERELEASE". (default: false) **--commit-sha**="": Set the metadata environment variable "CI_COMMIT_SHA". -**--env**="": Set the metadata environment variable "CI_ENV". (default: []) +**--env**="": Set the metadata environment variable "CI_ENV". **--forge-type**="": Set the metadata environment variable "CI_FORGE_TYPE". **--forge-url**="": Set the metadata environment variable "CI_FORGE_URL". -**--local**: run from local directory +**--local**: run from local directory (default: true) **--metadata-file**="": path to pipeline metadata file (normally downloaded from UI). Parameters can be adjusted by applying additional cli flags @@ -328,7 +341,7 @@ execute a local pipeline **--netrc-username**="": -**--network**="": external networks (default: []) +**--network**="": external networks **--pipeline-changed-files**="": Set the metadata environment variable "CI_PIPELINE_FILES", either json formatted list of strings, or comma separated string list. @@ -348,7 +361,7 @@ execute a local pipeline **--pipeline-url**="": Set the metadata environment variable "CI_PIPELINE_FORGE_URL". -**--plugins-privileged**="": Allow plugins to run in privileged mode, if environment variable is defined but empty there will be none (default: []) +**--plugins-privileged**="": Allow plugins to run in privileged mode, if environment variable is defined but empty there will be none **--prev-commit-author-avatar**="": Set the metadata environment variable "CI_PREV_COMMIT_AUTHOR_AVATAR". @@ -398,17 +411,17 @@ execute a local pipeline **--repo-remote-id**="": Set the metadata environment variable "CI_REPO_REMOTE_ID". -**--repo-trusted-network**: Set the metadata environment variable "CI_REPO_TRUSTED_NETWORK". +**--repo-trusted-network**: Set the metadata environment variable "CI_REPO_TRUSTED_NETWORK". (default: false) -**--repo-trusted-security**: Set the metadata environment variable "CI_REPO_TRUSTED_SECURITY". +**--repo-trusted-security**: Set the metadata environment variable "CI_REPO_TRUSTED_SECURITY". (default: false) -**--repo-trusted-volumes**: Set the metadata environment variable "CI_REPO_TRUSTED_VOLUMES". +**--repo-trusted-volumes**: Set the metadata environment variable "CI_REPO_TRUSTED_VOLUMES". (default: false) **--repo-url**="": Set the metadata environment variable "CI_REPO_URL". -**--secrets**="": map of secrets, ex. 'secret="val",secret2="value2"' (default: map[]) +**--secrets**="": map of secrets, ex. 'secret="val",secret2="value2"' -**--secrets**="": path to yaml file with secrets map +**--secrets-file**="": path to yaml file with secrets map **--system-host**="": Set the metadata environment variable "CI_SYSTEM_HOST". @@ -420,7 +433,7 @@ execute a local pipeline **--timeout**="": pipeline timeout (default: 1h0m0s) -**--volumes**="": pipeline volumes (default: []) +**--volumes**="": pipeline volumes **--workflow-name**="": Set the metadata environment variable "CI_WORKFLOW_NAME". @@ -434,18 +447,17 @@ execute a local pipeline show information about the current user -**--format**="": format output (deprecated) (default: User: {{ .Login }} -Email: {{ .Email }}) +**--format**="": format output (deprecated) (default: User: {{ .Login }}\nEmail: {{ .Email }}) ## lint lint a pipeline configuration file -**--plugins-privileged**="": allow plugins to run in privileged mode, if set empty, there is no (default: []) +**--plugins-privileged**="": allow plugins to run in privileged mode, if set empty, there is no -**--plugins-trusted-clone**="": plugins that are trusted to handle Git credentials in cloning steps (default: [docker.io/woodpeckerci/plugin-git:2.7.0 docker.io/woodpeckerci/plugin-git quay.io/woodpeckerci/plugin-git]) +**--plugins-trusted-clone**="": plugins that are trusted to handle Git credentials in cloning steps (default: "docker.io/woodpeckerci/plugin-git:2.9.0", "docker.io/woodpeckerci/plugin-git", "quay.io/woodpeckerci/plugin-git") -**--strict**: treat warnings as errors +**--strict**: treat warnings as errors (default: false) ## org @@ -479,10 +491,7 @@ remove a registry list registries -**--format**="": format output (deprecated) (default: {{ .Address }}  -Username: {{ .Username }} -Email: {{ .Email }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Address }} \x1b[0m\nUsername: {{ .Username }}\nEmail: {{ .Email }}\n) **--organization, --org**="": organization id or full name (e.g. 123 or octocat) @@ -490,10 +499,7 @@ Email: {{ .Email }} show registry information -**--format**="": format output (deprecated) (default: {{ .Address }}  -Username: {{ .Username }} -Email: {{ .Email }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Address }} \x1b[0m\nUsername: {{ .Username }}\nEmail: {{ .Email }}\n) **--hostname**="": registry hostname (default: docker.io) @@ -519,9 +525,9 @@ manage secrets add a secret -**--event**="": secret limited to these events (default: []) +**--event**="": secret limited to these events -**--image**="": secret limited to these images (default: []) +**--image**="": secret limited to these images **--name**="": secret name @@ -541,14 +547,7 @@ remove a secret list secrets -**--format**="": format output (deprecated) (default: {{ .Name }}  -Events: {{ list .Events }} -{{- if .Images }} -Images: {{ list .Images }} -{{- else }} -Images: -{{- end }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nEvents: {{ list .Events }}\n{{- if .Images }}\nImages: {{ list .Images }}\n{{- else }}\nImages: \n{{- end }}\n) **--organization, --org**="": organization id or full name (e.g. 123 or octocat) @@ -556,14 +555,7 @@ Images: show secret information -**--format**="": format output (deprecated) (default: {{ .Name }}  -Events: {{ list .Events }} -{{- if .Images }} -Images: {{ list .Images }} -{{- else }} -Images: -{{- end }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nEvents: {{ list .Events }}\n{{- if .Images }}\nImages: {{ list .Images }}\n{{- else }}\nImages: \n{{- end }}\n) **--name**="": secret name @@ -573,9 +565,9 @@ Images: update a secret -**--event**="": limit secret to these event (default: []) +**--event**="": limit secret to these event -**--image**="": limit secret to these image (default: []) +**--image**="": limit secret to these image **--name**="": secret name @@ -599,9 +591,9 @@ create new pipeline **--output**="": output format (default: table) -**--output-no-headers**: don't print headers +**--output-no-headers**: don't print headers (default: false) -**--var**="": key=value (default: []) +**--var**="": key=value ### decline @@ -615,17 +607,9 @@ trigger a pipeline with the 'deployment' event **--event**="": event filter (default: push) -**--format**="": format output (default: Number: {{ .Number }} -Status: {{ .Status }} -Commit: {{ .Commit }} -Branch: {{ .Branch }} -Ref: {{ .Ref }} -Message: {{ .Message }} -Author: {{ .Author }} -Target: {{ .Deploy }} -) +**--format**="": format output (default: Number: {{ .Number }}\nStatus: {{ .Status }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nMessage: {{ .Message }}\nAuthor: {{ .Author }}\nTarget: {{ .Deploy }}\n) -**--param, -p**="": custom parameters to inject into the step environment. Format: KEY=value (default: []) +**--param, -p**="": custom parameters to inject into the step environment. Format: KEY=value **--status**="": status filter (default: success) @@ -637,15 +621,15 @@ show latest pipeline information **--output**="": output format (default: table) -**--output-no-headers**: don't print headers +**--output-no-headers**: don't print headers (default: false) ### ls show pipeline history -**--after**="": only return pipelines after this date (RFC3339) (default: 0001-01-01 00:00:00 +0000 UTC) +**--after**="": only return pipelines after this date (RFC3339) -**--before**="": only return pipelines before this date (RFC3339) (default: 0001-01-01 00:00:00 +0000 UTC) +**--before**="": only return pipelines before this date (RFC3339) **--branch**="": branch filter @@ -655,7 +639,7 @@ show pipeline history **--output**="": output format (default: table) -**--output-no-headers**: don't print headers +**--output-no-headers**: don't print headers (default: false) **--status**="": status filter @@ -675,13 +659,7 @@ show pipeline logs show pipeline steps -**--format**="": format output (default: {{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}): -Step: {{ .step.Name }} -Started: {{ .step.Started }} -Stopped: {{ .step.Stopped }} -Type: {{ .step.Type }} -State: {{ .step.State }} -) +**--format**="": format output (default: \x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m\nStep: {{ .step.Name }}\nStarted: {{ .step.Started }}\nStopped: {{ .step.Stopped }}\nType: {{ .step.Type }}\nState: {{ .step.State }}\n) ### purge @@ -689,7 +667,7 @@ purge pipelines **--branch**="": remove pipelines of this branch only -**--dry-run**: disable non-read api calls +**--dry-run**: disable non-read api calls (default: false) **--keep-min**="": minimum number of pipelines to keep (default: 10) @@ -699,15 +677,7 @@ purge pipelines show pipeline queue -**--format**="": format output (default: {{ .FullName }} #{{ .Number }}  -Status: {{ .Status }} -Event: {{ .Event }} -Commit: {{ .Commit }} -Branch: {{ .Branch }} -Ref: {{ .Ref }} -Author: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }} -Message: {{ .Message }} -) +**--format**="": format output (default: \x1b[33m{{ .FullName }} #{{ .Number }} \x1b[0m\nStatus: {{ .Status }}\nEvent: {{ .Event }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nAuthor: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }}\nMessage: {{ .Message }}\n) ### show @@ -715,13 +685,13 @@ show pipeline information **--output**="": output format (default: table) -**--output-no-headers**: don't print headers +**--output-no-headers**: don't print headers (default: false) ### start start a pipeline -**--param, -p**="": custom parameters to inject into the step environment. Format: KEY=value (default: []) +**--param, -p**="": custom parameters to inject into the step environment. Format: KEY=value ### stop @@ -749,12 +719,9 @@ add a cron job **--branch**="": cron branch -**--format**="": format output (deprecated) (default: {{ .Name }}  -ID: {{ .ID }} -Branch: {{ .Branch }} -Schedule: {{ .Schedule }} -NextExec: {{ .NextExec }} -) +**--enabled**: whether cron is enabled (default: true) + +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nID: {{ .ID }}\nBranch: {{ .Branch }}\nSchedule: {{ .Schedule }}\nNextExec: {{ .NextExec }}\n) **--name**="": cron name @@ -774,12 +741,7 @@ remove a cron job list cron jobs -**--format**="": format output (deprecated) (default: {{ .Name }}  -ID: {{ .ID }} -Branch: {{ .Branch }} -Schedule: {{ .Schedule }} -NextExec: {{ .NextExec }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nID: {{ .ID }}\nBranch: {{ .Branch }}\nSchedule: {{ .Schedule }}\nNextExec: {{ .NextExec }}\n) **--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) @@ -787,12 +749,7 @@ NextExec: {{ .NextExec }} show cron job information -**--format**="": format output (deprecated) (default: {{ .Name }}  -ID: {{ .ID }} -Branch: {{ .Branch }} -Schedule: {{ .Schedule }} -NextExec: {{ .NextExec }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nID: {{ .ID }}\nBranch: {{ .Branch }}\nSchedule: {{ .Schedule }}\nNextExec: {{ .NextExec }}\n) **--id**="": cron id @@ -804,12 +761,9 @@ update a cron job **--branch**="": cron branch -**--format**="": format output (deprecated) (default: {{ .Name }}  -ID: {{ .ID }} -Branch: {{ .Branch }} -Schedule: {{ .Schedule }} -NextExec: {{ .NextExec }} -) +**--enabled**: whether cron is enabled (default: true) + +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nID: {{ .ID }}\nBranch: {{ .Branch }}\nSchedule: {{ .Schedule }}\nNextExec: {{ .NextExec }}\n) **--id**="": cron id @@ -823,7 +777,7 @@ NextExec: {{ .NextExec }} list all repos -**--all**: query all repos, including inactive ones +**--all**: query all repos, including inactive ones (default: false) **--format**="": format output (deprecated) @@ -831,7 +785,7 @@ list all repos **--output**="": output format (default: table) -**--output-no-headers**: don't print headers +**--output-no-headers**: don't print headers (default: false) ### registry @@ -861,10 +815,7 @@ remove a registry list registries -**--format**="": format output (deprecated) (default: {{ .Address }}  -Username: {{ .Username }} -Email: {{ .Email }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Address }} \x1b[0m\nUsername: {{ .Username }}\nEmail: {{ .Email }}\n) **--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) @@ -872,10 +823,7 @@ Email: {{ .Email }} show registry information -**--format**="": format output (deprecated) (default: {{ .Address }}  -Username: {{ .Username }} -Email: {{ .Email }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Address }} \x1b[0m\nUsername: {{ .Username }}\nEmail: {{ .Email }}\n) **--hostname**="": registry hostname (default: docker.io) @@ -909,9 +857,9 @@ manage secrets add a secret -**--event**="": limit secret to these events (default: []) +**--event**="": limit secret to these events -**--image**="": limit secret to these images (default: []) +**--image**="": limit secret to these images **--name**="": secret name @@ -931,14 +879,7 @@ remove a secret list secrets -**--format**="": format output (deprecated) (default: {{ .Name }}  -Events: {{ list .Events }} -{{- if .Images }} -Images: {{ list .Images }} -{{- else }} -Images: -{{- end }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nEvents: {{ list .Events }}\n{{- if .Images }}\nImages: {{ list .Images }}\n{{- else }}\nImages: \n{{- end }}\n) **--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) @@ -946,14 +887,7 @@ Images: show secret information -**--format**="": format output (deprecated) (default: {{ .Name }}  -Events: {{ list .Events }} -{{- if .Images }} -Images: {{ list .Images }} -{{- else }} -Images: -{{- end }} -) +**--format**="": format output (deprecated) (default: \x1b[33m{{ .Name }} \x1b[0m\nEvents: {{ list .Events }}\n{{- if .Images }}\nImages: {{ list .Images }}\n{{- else }}\nImages: \n{{- end }}\n) **--name**="": secret name @@ -963,9 +897,9 @@ Images: update a secret -**--event**="": limit secret to these events (default: []) +**--event**="": limit secret to these events -**--image**="": limit secret to these images (default: []) +**--image**="": limit secret to these images **--name**="": secret name @@ -979,13 +913,13 @@ show repository information **--output**="": output format (default: table) -**--output-no-headers**: don't print headers +**--output-no-headers**: don't print headers (default: false) ### sync synchronize the repository list -**--format**="": format output (default: {{ .FullName }} (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }}, isActive: {{ .IsActive }})) +**--format**="": format output (default: \x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }}, isActive: {{ .IsActive }})) ### update @@ -999,9 +933,13 @@ update a repository **--timeout**="": repository timeout (default: 0s) -**--trusted**: repository is trusted +**--trusted-network**: repository is network trusted (default: false) -**--unsafe**: allow unsafe operations +**--trusted-security**: repository is security trusted (default: false) + +**--trusted-volumes**: repository is volumes trusted (default: false) + +**--unsafe**: allow unsafe operations (default: false) **--visibility**="": repository visibility @@ -1009,6 +947,8 @@ update a repository setup the woodpecker-cli for the first time +**--context, --ctx**="": name for the context (defaults to 'default') + **--server**="": URL of the woodpecker server **--token**="": token to authenticate with the woodpecker server @@ -1017,4 +957,4 @@ setup the woodpecker-cli for the first time update the woodpecker-cli to the latest version -**--force**: force update even if the latest version is already installed +**--force**: force update even if the latest version is already installed (default: false) diff --git a/docs/versioned_docs/version-3.11/92-development/01-getting-started.md b/docs/versioned_docs/version-3.14/92-development/01-getting-started.md similarity index 96% rename from docs/versioned_docs/version-3.11/92-development/01-getting-started.md rename to docs/versioned_docs/version-3.14/92-development/01-getting-started.md index 89e5d960c..ed8020202 100644 --- a/docs/versioned_docs/version-3.11/92-development/01-getting-started.md +++ b/docs/versioned_docs/version-3.14/92-development/01-getting-started.md @@ -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`. diff --git a/docs/versioned_docs/version-3.11/92-development/02-core-ideas.md b/docs/versioned_docs/version-3.14/92-development/02-core-ideas.md similarity index 85% rename from docs/versioned_docs/version-3.11/92-development/02-core-ideas.md rename to docs/versioned_docs/version-3.14/92-development/02-core-ideas.md index fce17ab24..49a276d25 100644 --- a/docs/versioned_docs/version-3.11/92-development/02-core-ideas.md +++ b/docs/versioned_docs/version-3.14/92-development/02-core-ideas.md @@ -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 forge](../30-administration/10-configuration/12-forges/100-addon.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? diff --git a/docs/versioned_docs/version-3.11/92-development/03-ui.md b/docs/versioned_docs/version-3.14/92-development/03-ui.md similarity index 100% rename from docs/versioned_docs/version-3.11/92-development/03-ui.md rename to docs/versioned_docs/version-3.14/92-development/03-ui.md diff --git a/docs/versioned_docs/version-3.11/92-development/04-docs.md b/docs/versioned_docs/version-3.14/92-development/04-docs.md similarity index 100% rename from docs/versioned_docs/version-3.11/92-development/04-docs.md rename to docs/versioned_docs/version-3.14/92-development/04-docs.md diff --git a/docs/versioned_docs/version-3.14/92-development/05-architecture.md b/docs/versioned_docs/version-3.14/92-development/05-architecture.md new file mode 100644 index 000000000..51e218749 --- /dev/null +++ b/docs/versioned_docs/version-3.14/92-development/05-architecture.md @@ -0,0 +1,98 @@ +# Architecture + +## Module Interactions + +![Woodpecker architecture](./woodpecker-architecture.svg) + + + +## System architecture + +### main package hierarchy + +| 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`, `rpc`, `shared` | +| `cli/**` | code only cli tool does need | `pipeline`, `shared`, `woodpecker-go` | +| `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`, `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 + +| 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 + +| 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//`. + +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/` + +### Engine + +The engine is the shared kernel that validates, parses frontend facing config files, enrich it by the provided forge metadata and produce config for the backends to execute on based on that. It also contains the default backend implementations. + +#### Runtime + +The runtime is the package controlling how a workflow is executed, and can be found at `pipeline/runtime`. + +Pipeline/runtime flow diagram diff --git a/docs/versioned_docs/version-3.11/92-development/06-conventions.md b/docs/versioned_docs/version-3.14/92-development/06-conventions.md similarity index 56% rename from docs/versioned_docs/version-3.11/92-development/06-conventions.md rename to docs/versioned_docs/version-3.14/92-development/06-conventions.md index e94a90c43..d79e07811 100644 --- a/docs/versioned_docs/version-3.11/92-development/06-conventions.md +++ b/docs/versioned_docs/version-3.14/92-development/06-conventions.md @@ -4,4 +4,4 @@ Database tables are named plural, columns don't have any prefix. -Example: Table name `agent`, columns `id`, `name`. +Example: Model name `Agent` with table name `agents` and columns `id`, `name`. diff --git a/docs/versioned_docs/version-3.11/92-development/07-guides.md b/docs/versioned_docs/version-3.14/92-development/07-guides.md similarity index 100% rename from docs/versioned_docs/version-3.11/92-development/07-guides.md rename to docs/versioned_docs/version-3.14/92-development/07-guides.md diff --git a/docs/versioned_docs/version-3.11/92-development/08-translations.md b/docs/versioned_docs/version-3.14/92-development/08-translations.md similarity index 100% rename from docs/versioned_docs/version-3.11/92-development/08-translations.md rename to docs/versioned_docs/version-3.14/92-development/08-translations.md diff --git a/docs/versioned_docs/version-3.11/92-development/09-openapi.md b/docs/versioned_docs/version-3.14/92-development/09-openapi.md similarity index 100% rename from docs/versioned_docs/version-3.11/92-development/09-openapi.md rename to docs/versioned_docs/version-3.14/92-development/09-openapi.md diff --git a/docs/versioned_docs/version-3.11/92-development/09-testing.md b/docs/versioned_docs/version-3.14/92-development/09-testing.md similarity index 98% rename from docs/versioned_docs/version-3.11/92-development/09-testing.md rename to docs/versioned_docs/version-3.14/92-development/09-testing.md index f27f71130..dd2604cdf 100644 --- a/docs/versioned_docs/version-3.11/92-development/09-testing.md +++ b/docs/versioned_docs/version-3.14/92-development/09-testing.md @@ -5,7 +5,7 @@ ### Unit Tests [We use default golang unit tests](https://go.dev/doc/tutorial/add-a-test) -with [`"github.com/stretchr/testify/assert"`](https://pkg.go.dev/github.com/stretchr/testify@v1.9.0/assert) to simplify testing. +with [`"github.com/stretchr/testify/assert"`](https://pkg.go.dev/github.com/stretchr/testify/assert) to simplify testing. ### Integration Tests diff --git a/docs/versioned_docs/version-3.14/92-development/10-packaging.md b/docs/versioned_docs/version-3.14/92-development/10-packaging.md new file mode 100644 index 000000000..415b426a4 --- /dev/null +++ b/docs/versioned_docs/version-3.14/92-development/10-packaging.md @@ -0,0 +1,18 @@ +# Packaging + +If you repackage it, we encourage to build from source, which requires internet connection. + +For offline builds, we also offer a tarball with all vendored dependencies and a pre-built web UI +on the [release page](https://github.com/woodpecker-ci/woodpecker/releases). + +## Distribute web UI in own directory + +If you do not want to embed the web UI in the binary, you can compile a custom root path for the web UI into the binary. + +Add `external_web` to the tags and use the build flag `-X go.woodpecker-ci.org/woodpecker/v3/web.webUIRoot=/some/path` to set a custom path. + +Example: + +```sh +go build -tags 'external_web' -ldflags '-s -w -extldflags "-static" -X go.woodpecker-ci.org/woodpecker/v3/version.Version=3.12.0 -X go.woodpecker-ci.org/woodpecker/v3/web.webUIRoot=/nix/store/maaajlp8h5gy9zyjgfhaipzj07qnnmrl-woodpecker-WebUI-3.12.0' -o dist/woodpecker-server go.woodpecker-ci.org/woodpecker/v3/cmd/server +``` diff --git a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/100-addon.md b/docs/versioned_docs/version-3.14/92-development/100-addons.md similarity index 54% rename from docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/100-addon.md rename to docs/versioned_docs/version-3.14/92-development/100-addons.md index 2434fee06..fe5b62a95 100644 --- a/docs/versioned_docs/version-3.11/30-administration/10-configuration/12-forges/100-addon.md +++ b/docs/versioned_docs/version-3.14/92-development/100-addons.md @@ -1,34 +1,16 @@ -# Custom +# Addons -If the forge you are using does not meet the [Woodpecker requirements](../../../92-development/02-core-ideas.md#forges) or your setup is too specific to be included in the Woodpecker core, you can write an addon forge. +The Woodpecker server supports addons for forges and the log store. :::warning -Addon forges are still experimental. Their implementation can change and break at any time. +Addons are still experimental. Their implementation can change and break at any time. ::: -:::danger -You must trust the author of the addon forge you are using. They may have access to authentication codes and other potentially sensitive information. -::: - -## Usage - -To use an addon forge, download the correct addon version. Then, you can add the following to your configuration: - -```ini -WOODPECKER_ADDON_FORGE=/path/to/your/addon/forge/file -``` - -In case you run Woodpecker as container, you probably want to mount the addon binary to `/opt/addons/`. - -### Bug reports +## Bug reports If you experience bugs, please check which component has the issue. If it's the addon, **do not raise an issue in the main repository**, but rather use the separate addon repositories. To check which component is responsible for the bug, look at the logs. Logs from addons are marked with a special field `addon` containing their addon file name. -## List of addon forges - -- [Radicle](https://radicle.xyz/): Open source, peer-to-peer code collaboration stack built on Git. Radicle addon for Woodpecker CI can be found at [this repo](https://explorer.radicle.gr/nodes/seed.radicle.gr/rad:z39Cf1XzrvCLRZZJRUZnx9D1fj5ws). - -## Creating addon forges +## Creating addons Addons use RPC to communicate to the server and are implemented using the [`go-plugin` library](https://github.com/hashicorp/go-plugin). @@ -38,7 +20,7 @@ This example will use the Go language. Directly import Woodpecker's Go packages (`go.woodpecker-ci.org/woodpecker/v3`) and use the interfaces and types defined there. -In the `main` function, just call `"go.woodpecker-ci.org/woodpecker/v3/server/forge/addon".Serve` with a `"go.woodpecker-ci.org/woodpecker/v3/server/forge".Forge` as argument. +In the `main` function, just call the `Serve` method in the corresponding [addon package](#addon-types) with the service as argument. This will take care of connecting the addon forge to the server. :::note @@ -47,6 +29,8 @@ It is not possible to access global variables from Woodpecker, for example the s ### Example structure +This is an example for a forge addon. + ```go package main @@ -68,3 +52,10 @@ type config struct { // `config` must implement `"go.woodpecker-ci.org/woodpecker/v3/server/forge".Forge`. You must directly use Woodpecker's packages - see imports above. ``` + +### Addon types + +| Type | Addon package | Service interface | +| --------- | ------------------------------------------------------------- | ----------------------------------------------------------------- | +| Forge | `go.woodpecker-ci.org/woodpecker/v3/server/forge/addon` | `"go.woodpecker-ci.org/woodpecker/v3/server/forge".Forge` | +| Log store | `go.woodpecker-ci.org/woodpecker/v3/server/service/log/addon` | `"go.woodpecker-ci.org/woodpecker/v3/server/service/log".Service` | diff --git a/docs/versioned_docs/version-3.14/92-development/40-deprecations.md b/docs/versioned_docs/version-3.14/92-development/40-deprecations.md new file mode 100644 index 000000000..f19f17c8e --- /dev/null +++ b/docs/versioned_docs/version-3.14/92-development/40-deprecations.md @@ -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 diff --git a/docs/versioned_docs/version-3.11/92-development/_category_.yaml b/docs/versioned_docs/version-3.14/92-development/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-3.11/92-development/_category_.yaml rename to docs/versioned_docs/version-3.14/92-development/_category_.yaml diff --git a/docs/versioned_docs/version-3.11/92-development/ui-proxy.svg b/docs/versioned_docs/version-3.14/92-development/ui-proxy.svg similarity index 100% rename from docs/versioned_docs/version-3.11/92-development/ui-proxy.svg rename to docs/versioned_docs/version-3.14/92-development/ui-proxy.svg diff --git a/docs/versioned_docs/version-3.11/92-development/vscode-debug.png b/docs/versioned_docs/version-3.14/92-development/vscode-debug.png similarity index 100% rename from docs/versioned_docs/version-3.11/92-development/vscode-debug.png rename to docs/versioned_docs/version-3.14/92-development/vscode-debug.png diff --git a/docs/versioned_docs/version-3.11/92-development/vscode-run-test.png b/docs/versioned_docs/version-3.14/92-development/vscode-run-test.png similarity index 100% rename from docs/versioned_docs/version-3.11/92-development/vscode-run-test.png rename to docs/versioned_docs/version-3.14/92-development/vscode-run-test.png diff --git a/docs/versioned_docs/version-3.14/92-development/woodpecker-architecture.dot b/docs/versioned_docs/version-3.14/92-development/woodpecker-architecture.dot new file mode 100644 index 000000000..e72bede01 --- /dev/null +++ b/docs/versioned_docs/version-3.14/92-development/woodpecker-architecture.dot @@ -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 +} diff --git a/docs/versioned_docs/version-3.14/92-development/woodpecker-architecture.svg b/docs/versioned_docs/version-3.14/92-development/woodpecker-architecture.svg new file mode 100644 index 000000000..5bb308a59 --- /dev/null +++ b/docs/versioned_docs/version-3.14/92-development/woodpecker-architecture.svg @@ -0,0 +1,352 @@ + + + + + + +WoodpeckerArchitecture + + +cluster_ui + +UI + + +cluster_sdk + +SDK (woodpecker-go) + + +cluster_cli + +woodpecker-cli + + +cluster_agent + +woodpecker-agent + + +cluster_pipelines + +Pipelines + + +cluster_server + +woodpecker-server + + +cluster_shared + +Shared Libs + + +cluster_external + +External Systems + + + +ui_web + +web/ + + + +srv_router + +server/router/ + + + +ui_web->srv_router + + +HTTP + + + +srv_api + +server/api/ + + + +ui_web->srv_api + + +REST API + + + +sdk + +woodpecker-go + + + +sdk->srv_api + + +REST API + + + +cli_cmd + +cmd/cli/ + + + +cli_core + +cli/ + + + +cli_cmd->cli_core + + + + + +cli_core->sdk + + + + + +agent_cmd + +cmd/agent/ + + + +agent_core + +agent/ + + + +agent_cmd->agent_core + + + + + +pipe_backend + +pipeline/backend/ +(exec engines) + + + +agent_core->pipe_backend + + +execute steps + + + +srv_grpc + +server/rpc/ + + + +agent_core->srv_grpc + + +gRPC connect + + + +srv_queue + +server/queue/ + + + +agent_core->srv_queue + + +poll work + + + +pipe_core + +pipeline/ + + + +pipe_core->pipe_backend + + + + + +shared_util + +shared/util/ + + + +pipe_core->shared_util + + + + + +pipe_frontend + +pipeline/frontend/ +(yaml) + + + +pipe_frontend->pipe_core + + + + + +srv_cmd + +cmd/server/ + + + +srv_cmd->srv_router + + + + + +srv_router->srv_api + + + + + +shared_token + +shared/token/ + + + +srv_router->shared_token + + + + + +srv_api->srv_queue + + + + + +srv_pubsub + +server/pubsub/ + + + +srv_api->srv_pubsub + + + + + +srv_store + +server/store/ + + + +srv_api->srv_store + + + + + +shared_http + +shared/httputil/ + + + +srv_api->shared_http + + + + + +srv_grpc->srv_queue + + + + + +shared_log + +shared/logger/ + + + +srv_grpc->shared_log + + + + + +srv_model + +server/model/ + + + +srv_store->srv_model + + + + + +ext_db + + +Database + + + +srv_store->ext_db + + +SQL + + + +srv_forge + +server/forge/ + + + +ext_scm + +SCM Providers + + + +srv_forge->ext_scm + + +SCM API + + + diff --git a/docs/versioned_sidebars/version-3.11-sidebars.json b/docs/versioned_sidebars/version-3.14-sidebars.json similarity index 100% rename from docs/versioned_sidebars/version-3.11-sidebars.json rename to docs/versioned_sidebars/version-3.14-sidebars.json diff --git a/docs/versions.json b/docs/versions.json index 71a63593a..55bfb5728 100644 --- a/docs/versions.json +++ b/docs/versions.json @@ -1 +1 @@ -["3.13", "3.12", "3.11", "2.8"] +["3.14", "3.13", "3.12", "2.8"]