Merge branch 'main' into pr/anbraten/3849

This commit is contained in:
pat-s 2025-01-11 00:09:49 +01:00
commit b804585b88
No known key found for this signature in database
GPG key ID: 3C6318841EF78925
1043 changed files with 37686 additions and 25984 deletions

View file

@ -6,39 +6,51 @@
"en_us",
// code
"go",
"node",
// package names
"npm"
"node"
],
"words": [
"abool",
"addgroup",
"adduser",
"anbraten",
"antfu",
"apimachinery",
"appleboy",
"Archlinux",
"autoincr",
"automerge",
"autoscaler",
"backporting",
"backports",
"binutils",
"bitbucketdatacenter",
"Boguslawski",
"bradrydzewski",
"BUILDPLATFORM",
"buildx",
"caddyfile",
"ccmenu",
"certmagic",
"charmbracelet",
"cicd",
"ciphertext",
"Cloudron",
"Codeberg",
"compatiblelicenses",
"corepack",
"cpuset",
"creativecommons",
"Curr",
"CERTDIR",
"datacenter",
"DATASOURCE",
"Debugf",
"desaturate",
"devx",
"dind",
"Dockle",
"doublestar",
"emojify",
"envsubst",
"errgroup",
"estree",
@ -46,64 +58,90 @@
"excalidraw",
"favicons",
"Fediverse",
"Feishu",
"Fogas",
"forbidigo",
"Forgejo",
"fsnotify",
"Geeklab",
"Georgiana",
"gitea",
"gitmodules",
"GOARCH",
"GOBIN",
"gocritic",
"GODEBUG",
"godoc",
"Gogs",
"golangci",
"gomod",
"gonic",
"GOPATH",
"Gource",
"handlebargh",
"HEALTHCHECK",
"healthz",
"Hetzner",
"HETZNERCLOUD",
"homelab",
"HTMLURL",
"HTTPFS",
"httpsig",
"httpsign",
"HTTPURL",
"httputil",
"ianvs",
"iconify",
"inetutils",
"Infima",
"Infof",
"Informatyka",
"intlify",
"Ionescu",
"Jetpack",
"Kaniko",
"Keyfunc",
"kyvg",
"LASTEXITCODE",
"Laszlo",
"laszlocph",
"letsencrypt",
"loadbalancer",
"logfile",
"loglevel",
"LONGBLOB",
"LONGTEXT",
"lonix1",
"mapstructure",
"markdownlint",
"mdbook",
"memswap",
"Metas",
"mhmxs",
"moby",
"Msgf",
"mstruebing",
"multiarch",
"multierr",
"netdns",
"Netrc",
"Nextcloud",
"nfpm",
"nixos",
"nixpkgs",
"nocolor",
"nolint",
"norunningpipelines",
"nosniff",
"ntfy",
"octocat",
"openapi",
"opensource",
"Pacman",
"picus",
"Pinia",
"pkce",
"pnpx",
"Polyform",
"posix",
"ppid",
"Println",
@ -123,19 +161,26 @@
"repology",
"reslimit",
"Reviewdog",
"Rieter",
"riscv",
"rundll32",
"Rydzewski",
"seccomp",
"secprofile",
"securecookie",
"selfhosted",
"sess",
"shellescape",
"sigstore",
"Sonatype",
"SSHURL",
"sslmode",
"stepbuilder",
"stretchr",
"structs",
"sublicensable",
"swaggo",
"syscalls",
"TARGETARCH",
"TARGETOS",
"techknowlogick",
@ -143,38 +188,49 @@
"threadcreate",
"tink",
"tinycolor",
"tmole",
"tmpfs",
"tmpl",
"tolerations",
"Traefik",
"tseslint",
"ttlcache",
"TUNEIT",
"Tunnelmole",
"typecheck",
"Typeflag",
"unplugin",
"Upsert",
"urfave",
"usecase",
"varchar",
"varz",
"Vieter",
"virtualisation",
"visualisation",
"vite",
"vueuse",
"waivable",
"Warnf",
"webhookd",
"Weblate",
"windi",
"windicss",
"woodpeckerci",
"WORKDIR",
"Wrapf",
"x-enum-varnames",
"xlink",
"xlog",
"xorm",
"xormigrate",
"xyaml",
"yamls",
"Yuno",
"zerolog",
"zerologger"
],
"ignorePaths": [
"**/node_modules/**/*",
"*.excalidraw",
"*.svg",
"*_test.go",
@ -185,19 +241,26 @@
".vscode/extensions.json",
"CHANGELOG.md",
"Makefile",
"flake.lock",
"flake.nix",
"go.mod",
"go.sum",
"pipeline/rpc/proto/woodpecker.pb.go",
"pnpm-lock.yaml",
"**/*.pb.go",
"server/store/datastore/migration/**/*",
"web/components.d.ts",
"web/src/assets/locales/**/*",
"**/fixtures/**",
"**/testdata/**",
"docs/versioned_docs/",
"package.json",
// generated
"go.sum",
"flake.lock",
"pnpm-lock.yaml",
"**/node_modules/**/*",
"cmd/server/openapi/docs.go",
"renovate.json",
// TODO: remove the following
"docs/"
"docs/**/*.js",
"docs/**/*.ts"
],
// Exclude imports, because they are also strings.
"ignoreRegExpList": [
@ -210,7 +273,9 @@
// ignore nolint directive
"//\\s*nolint:.*",
// ignore docker image names
"\\s*docker\\.io/.*"
"\\s*docker\\.io/.*",
// ignore inline svg in css
"\\s*url\\(\"data:image/svg\\+xml.*"
],
"enableFiletypes": ["dockercompose"]
}

5
.ecrc
View file

@ -1,14 +1,15 @@
{
"Exclude": [
".git",
"go.mod", "go.sum",
"go.mod",
"go.sum",
"vendor",
"fixtures",
"LICENSE",
"node_modules",
"server/store/datastore/migration/test-files/sqlite.db",
"server/store/datastore/feed.go",
"cmd/server/docs/docs.go",
"cmd/server/openapi/docs.go",
"_test.go",
"Makefile"
]

11
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,11 @@
<!--
Please check the following tips:
1. Avoid using force-push and commands that require it (such as `commit --amend` and `rebase origin/main`). This makes it more difficult for the maintainers to review your work. Add new commits on top of the current branch, and merge the new state of `main` into your branch with plain `merge`.
2. Provide a meaningful title for this pull request. It will be used as the commit message when this pull request is merged. Add as many commits as you like with any messages you like, they will be squashed into one commit.
3. If this pull request fixes an issue, refer to the issue with messages like `Closes #1234`, or `Fixes #1234` in the pull description.
4. Please check that you are targeting the `main` branch. Pull requests on release branches are only allowed for backports.
5. Make sure you have read contribution guidelines: https://woodpecker-ci.org/docs/development/getting-started
6. It is recommended to enable "Allow edits by maintainers", so maintainers can help you more easily.
-->

12
.github/renovate.json vendored
View file

@ -1,12 +1,13 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>woodpecker-ci/renovate-config"],
"automergeType": "pr",
"customManagers": [
{
"customType": "regex",
"fileMatch": ["shared/constant/constant.go"],
"matchStrings": [
"//\\s*renovate:\\s*datasource=(?<datasource>.*?) depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?\\s+DefaultCloneImage = \"docker.io/woodpeckerci/plugin-git:(?<currentValue>.*)\""
"//\\s*renovate:\\s*datasource=(?<datasource>.*?) depName=(?<depName>.*?)( versioning=(?<versioning>.*?))?\\s+DefaultClonePlugin = \"docker.io/woodpeckerci/plugin-git:(?<currentValue>.*)\""
],
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}"
}
@ -29,8 +30,8 @@
},
{
"groupName": "golang-lang",
"matchPackagePatterns": ["^golang$", "xgo"],
"matchUpdateTypes": ["minor", "patch"]
"matchUpdateTypes": ["minor", "patch"],
"matchPackageNames": ["/^golang$/", "/xgo/"]
},
{
"groupName": "golang-packages",
@ -60,9 +61,10 @@
"matchFileNames": ["docs/**/package.json"]
},
{
"description": "Extract version from xgo container tags",
"matchDatasources": ["docker"],
"matchPackagePatterns": ["xgo"],
"versioning": "regex:^go-(?<major>\\d+)?(\\.(?<minor>\\d+))?(\\.(?<patch>\\d+))$"
"versioning": "regex:^go-(?<major>\\d+)\\.(?<minor>\\d+)\\.x$",
"matchPackageNames": ["/techknowlogick/xgo/"]
}
]
}

3
.gitignore vendored
View file

@ -51,4 +51,7 @@ docs/venv
### Generated by CI ###
docs/docs/40-cli.md
docs/openapi.json
# Removed once v3.0.x is minimum version to be touched
docs/swagger.json

View file

@ -112,9 +112,10 @@ vscode:
- 'EditorConfig.EditorConfig'
- 'dbaeumer.vscode-eslint'
- 'esbenp.prettier-vscode'
- 'voorjaar.windicss-intellisense'
- 'bradlc.vscode-tailwindcss'
- 'Vue.volar'
- 'redhat.vscode-yaml'
- 'davidanson.vscode-markdownlint'
- 'streetsidesoftware.code-spell-checker'
- 'stivo.tailwind-fold'
# cSpell:enable

1
.lycheeignore Normal file
View file

@ -0,0 +1 @@
https://stackoverflow.com/*

View file

@ -5,16 +5,16 @@ repos:
- id: check-hooks-apply
- id: check-useless-excludes
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/golangci/golangci-lint
rev: v1.59.1
rev: v1.63.4
hooks:
- id: golangci-lint
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.41.0
rev: v0.43.0
hooks:
- id: markdownlint
exclude: '^(docs/versioned_docs/.*|CHANGELOG.md)$'
@ -24,11 +24,11 @@ repos:
- id: checkmake
exclude: '^docker/Dockerfile.make$' # actually a Dockerfile and not a makefile
- repo: https://github.com/hadolint/hadolint
rev: v2.13.0-beta
rev: v2.13.1-beta
hooks:
- id: hadolint
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
- repo: https://github.com/rbubley/mirrors-prettier
rev: v3.4.2
hooks:
- id: prettier
- repo: https://github.com/adrienverge/yamllint.git

View file

@ -1,10 +1,8 @@
build/
docs/versioned_docs/
docs/.docusaurus/
docs/pnpm-lock.yaml
dist/
CHANGELOG.md
# web/ must be directly formatted from there
# web/ and docs/ must be directly formatted from there
# to prevent conflicts with different prettier version
web/
docs/

View file

@ -5,10 +5,11 @@
"EditorConfig.EditorConfig",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"voorjaar.windicss-intellisense",
"bradlc.vscode-tailwindcss",
"Vue.volar",
"redhat.vscode-yaml",
"davidanson.vscode-markdownlint"
"davidanson.vscode-markdownlint",
"stivo.tailwind-fold"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": []

View file

@ -1,12 +1,17 @@
when:
event: tag
- event: tag
- event: pull_request
branch: ${CI_REPO_DEFAULT_BRANCH}
path:
- Makefile
- .woodpecker/binaries.yaml
variables:
- &golang_image 'docker.io/golang:1.22'
- &node_image 'docker.io/node:22-alpine'
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x'
- &golang_image 'docker.io/golang:1.23'
- &node_image 'docker.io/node:23-alpine'
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.23.x'
# cspell:words bindata netgo TARGZ
# cspell:words bindata netgo
steps:
build-web:
@ -35,7 +40,7 @@ steps:
environment:
PLATFORMS: linux|arm64/v8;linux|amd64;windows|amd64
TAGS: bindata sqlite sqlite_unlock_notify netgo
TARGZ: '1'
ARCHIVE_IT: '1'
build-tarball:
depends_on:
@ -50,6 +55,8 @@ steps:
- vendor
image: *golang_image
commands:
- apt update
- apt install -y zip
- make release-agent
build-cli:
@ -57,6 +64,8 @@ steps:
- vendor
image: *golang_image
commands:
- apt update
- apt install -y zip
- make release-cli
build-deb-rpm:
@ -90,13 +99,16 @@ steps:
release:
depends_on:
- checksums
image: woodpeckerci/plugin-release:0.1.0
image: woodpeckerci/plugin-release:0.2.2
settings:
api_key:
from_secret: github_token
files:
- dist/*.tar.gz
- dist/*.zip
- dist/*.deb
- dist/*.rpm
- dist/checksums.txt
title: ${CI_COMMIT_TAG##v}
when:
event: tag

View file

@ -1,15 +1,15 @@
variables:
- &golang_image 'docker.io/golang:1.22'
- &node_image 'docker.io/node:22-alpine'
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x'
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:4.0.0'
- &golang_image 'docker.io/golang:1.23'
- &node_image 'docker.io/node:23-alpine'
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.23.x'
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:5.1.0'
- &platforms_release 'linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/386,linux/amd64,linux/ppc64le,linux/riscv64,linux/s390x,freebsd/arm64,freebsd/amd64,openbsd/arm64,openbsd/amd64'
- &platforms_server 'linux/arm/v7,linux/arm64/v8,linux/amd64,linux/ppc64le,linux/riscv64'
- &platforms_preview 'linux/amd64'
- &platforms_alpine 'linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/amd64,linux/ppc64le'
- &build_args 'CI_COMMIT_SHA=${CI_COMMIT_SHA},CI_COMMIT_BRANCH=${CI_COMMIT_BRANCH},CI_COMMIT_TAG=${CI_COMMIT_TAG}'
# cspell:words woodpeckerbot netgo TARGZ
# cspell:words woodpeckerbot netgo
# vars used on push / tag events only
- publish_logins: &publish_logins # Default DockerHub login
@ -41,9 +41,6 @@ variables:
when:
- event: [pull_request, tag]
- event: push
branch:
- renovate/*
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
path: *when_path
@ -61,7 +58,6 @@ steps:
path: *when_path
- branch:
- ${CI_REPO_DEFAULT_BRANCH}
- renovate/*
event: [push, tag]
path: *when_path
@ -82,7 +78,6 @@ steps:
path: *when_path
- branch:
- ${CI_REPO_DEFAULT_BRANCH}
- renovate/*
event: [push, tag]
path: *when_path
@ -104,9 +99,6 @@ steps:
evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images"'
- event: pull_request
path: *when_path
- event: push
path: *when_path
branch: renovate/*
cross-compile-server:
depends_on:
@ -127,31 +119,19 @@ steps:
event: [push, tag]
path: *when_path
publish-server-preview:
depends_on:
- cross-compile-server-preview
image: *buildx_plugin
settings:
repo: woodpeckerci/woodpecker-server
dockerfile: docker/Dockerfile.server.multiarch
platforms: *platforms_preview
tag: pull_${CI_COMMIT_PULL_REQUEST}
logins: *publish_logins
when: &when-preview
evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images"'
event: pull_request
publish-server-alpine-preview:
depends_on:
- cross-compile-server-preview
image: *buildx_plugin
settings:
repo: woodpeckerci/woodpecker-server
dockerfile: docker/Dockerfile.server.alpine.multiarch
dockerfile: docker/Dockerfile.server.alpine.multiarch.rootless
platforms: *platforms_preview
tag: pull_${CI_COMMIT_PULL_REQUEST}-alpine
logins: *publish_logins
when: *when-preview
when: &when-preview
evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images"'
event: pull_request
build-server-dryrun:
depends_on:
@ -162,16 +142,13 @@ steps:
settings:
dry_run: true
repo: woodpeckerci/woodpecker-server
dockerfile: docker/Dockerfile.server.multiarch
dockerfile: docker/Dockerfile.server.multiarch.rootless
platforms: *platforms_preview
tag: pull_${CI_COMMIT_PULL_REQUEST}
when: &when-dryrun
- evaluate: 'not (CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images")'
event: pull_request
path: *when_path
- event: push
path: *when_path
branch: renovate/*
publish-next-server:
depends_on:
@ -179,7 +156,7 @@ steps:
image: *buildx_plugin
settings:
repo: *publish_repos_server
dockerfile: docker/Dockerfile.server.multiarch
dockerfile: docker/Dockerfile.server.multiarch.rootless
platforms: *platforms_server
tag: [next, 'next-${CI_COMMIT_SHA:0:10}']
logins: *publish_logins
@ -194,7 +171,7 @@ steps:
image: *buildx_plugin
settings:
repo: *publish_repos_server
dockerfile: docker/Dockerfile.server.alpine.multiarch
dockerfile: docker/Dockerfile.server.alpine.multiarch.rootless
platforms: *platforms_alpine
tag: [next-alpine, 'next-${CI_COMMIT_SHA:0:10}-alpine']
logins: *publish_logins
@ -206,10 +183,9 @@ steps:
image: *buildx_plugin
settings:
repo: *publish_repos_server
dockerfile: docker/Dockerfile.server.multiarch
dockerfile: docker/Dockerfile.server.multiarch.rootless
platforms: *platforms_server
# remove 'latest' on older version branches to avoid accidental downgrade
tag: [latest, '${CI_COMMIT_TAG}']
tag: ['${CI_COMMIT_TAG%%.*}', '${CI_COMMIT_TAG%.*}-alpine', '${CI_COMMIT_TAG}']
logins: *publish_logins
when: &when-release
event: tag
@ -220,10 +196,9 @@ steps:
image: *buildx_plugin
settings:
repo: *publish_repos_server
dockerfile: docker/Dockerfile.server.alpine.multiarch
dockerfile: docker/Dockerfile.server.alpine.multiarch.rootless
platforms: *platforms_alpine
# remove 'latest-alpine' on older version branches to avoid accidental downgrade
tag: [latest-alpine, '${CI_COMMIT_TAG}-alpine']
tag: ['${CI_COMMIT_TAG%%.*}-alpine', '${CI_COMMIT_TAG%.*}-alpine', '${CI_COMMIT_TAG}-alpine']
logins: *publish_logins
when: *when-release
@ -231,15 +206,15 @@ steps:
# A g e n t #
#############
publish-agent-preview:
publish-agent-preview-alpine:
depends_on:
- vendor
image: *buildx_plugin
settings:
repo: woodpeckerci/woodpecker-agent
dockerfile: docker/Dockerfile.agent.multiarch
dockerfile: docker/Dockerfile.agent.alpine.multiarch
platforms: *platforms_preview
tag: pull_${CI_COMMIT_PULL_REQUEST}
tag: pull_${CI_COMMIT_PULL_REQUEST}-alpine
build_args: *build_args
logins: *publish_logins
when: *when-preview
@ -303,8 +278,7 @@ steps:
repo: *publish_repos_agent
dockerfile: docker/Dockerfile.agent.multiarch
platforms: *platforms_release
# remove 'latest' on older version branches to avoid accidental downgrade
tag: [latest, '${CI_COMMIT_TAG}']
tag: ['${CI_COMMIT_TAG%%.*}', '${CI_COMMIT_TAG%.*}', '${CI_COMMIT_TAG}']
logins: *publish_logins
build_args: *build_args
when: *when-release
@ -320,8 +294,7 @@ steps:
repo: *publish_repos_agent
dockerfile: docker/Dockerfile.agent.alpine.multiarch
platforms: *platforms_alpine
# remove 'latest-alpine' on older version branches to avoid accidental downgrade
tag: [latest-alpine, '${CI_COMMIT_TAG}-alpine']
tag: ['${CI_COMMIT_TAG%%.*}-alpine', '${CI_COMMIT_TAG%.*}-alpine', '${CI_COMMIT_TAG}-alpine']
logins: *publish_logins
build_args: *build_args
when: *when-release
@ -330,19 +303,6 @@ steps:
# C L I #
#########
publish-cli-preview:
depends_on:
- vendor
image: *buildx_plugin
settings:
repo: woodpeckerci/woodpecker-cli
dockerfile: docker/Dockerfile.cli.multiarch
platforms: *platforms_preview
tag: pull_${CI_COMMIT_PULL_REQUEST}
build_args: *build_args
logins: *publish_logins
when: *when-preview
build-cli-dryrun:
depends_on:
- vendor
@ -350,7 +310,7 @@ steps:
settings:
dry_run: true
repo: woodpeckerci/woodpecker-cli
dockerfile: docker/Dockerfile.cli.multiarch
dockerfile: docker/Dockerfile.cli.multiarch.rootless
platforms: *platforms_preview
tag: pull_${CI_COMMIT_PULL_REQUEST}
build_args: *build_args
@ -365,7 +325,7 @@ steps:
image: *buildx_plugin
settings:
repo: *publish_repos_cli
dockerfile: docker/Dockerfile.cli.multiarch
dockerfile: docker/Dockerfile.cli.multiarch.rootless
platforms: *platforms_release
tag: [next, 'next-${CI_COMMIT_SHA:0:10}']
logins: *publish_logins
@ -381,7 +341,7 @@ steps:
image: *buildx_plugin
settings:
repo: *publish_repos_cli
dockerfile: docker/Dockerfile.cli.alpine.multiarch
dockerfile: docker/Dockerfile.cli.alpine.multiarch.rootless
platforms: *platforms_alpine
tag: [next-alpine, 'next-${CI_COMMIT_SHA:0:10}-alpine']
logins: *publish_logins
@ -397,10 +357,9 @@ steps:
image: *buildx_plugin
settings:
repo: *publish_repos_cli
dockerfile: docker/Dockerfile.cli.multiarch
dockerfile: docker/Dockerfile.cli.multiarch.rootless
platforms: *platforms_release
# remove 'latest' on older version branches to avoid accidental downgrade
tag: [latest, '${CI_COMMIT_TAG}']
tag: ['${CI_COMMIT_TAG%%.*}', '${CI_COMMIT_TAG%.*}', '${CI_COMMIT_TAG}']
logins: *publish_logins
build_args: *build_args
when: *when-release
@ -414,10 +373,9 @@ steps:
image: *buildx_plugin
settings:
repo: *publish_repos_cli
dockerfile: docker/Dockerfile.cli.alpine.multiarch
dockerfile: docker/Dockerfile.cli.alpine.multiarch.rootless
platforms: *platforms_alpine
# remove 'latest-alpine' on older version branches to avoid accidental downgrade
tag: [latest-alpine, '${CI_COMMIT_TAG}-alpine']
tag: ['${CI_COMMIT_TAG%%.*}-alpine', '${CI_COMMIT_TAG%.*}-alpine', '${CI_COMMIT_TAG}-alpine']
logins: *publish_logins
build_args: *build_args
when: *when-release

View file

@ -1,7 +1,7 @@
variables:
- &golang_image 'docker.io/golang:1.22'
- &node_image 'docker.io/node:21-alpine'
- &alpine_image 'docker.io/alpine:3.19'
- &golang_image 'docker.io/golang:1.23'
- &node_image 'docker.io/node:23-alpine'
- &alpine_image 'docker.io/alpine:3.21'
- path: &when_path
- 'docs/**'
- '.woodpecker/docs.yaml'
@ -31,13 +31,22 @@ when:
- <<: *docker_path
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- renovate/*
- event: pull_request_closed
path: *when_path
- event: manual
evaluate: 'TASK == "docs"'
steps:
prettier:
image: docker.io/woodpeckerci/plugin-prettier:next
settings:
version: 3.3.3
plugins:
- 'prettier-plugin-tailwindcss'
- '@ianvs/prettier-plugin-sort-imports'
when:
- event: pull_request
build-cli:
image: *golang_image
commands:
@ -60,7 +69,7 @@ steps:
- event: manual
deploy-preview:
image: docker.io/woodpeckerci/plugin-surge-preview:1.3.0
image: docker.io/woodpeckerci/plugin-surge-preview:1.3.3
settings:
path: 'docs/build/'
surge_token:
@ -69,13 +78,14 @@ steps:
from_secret: GITHUB_TOKEN_SURGE
failure: ignore
when:
event: [pull_request, pull_request_closed]
path: *when_path
- event: [pull_request, pull_request_closed]
path: *when_path
deploy-prepare:
image: *alpine_image
secrets:
- BOT_PRIVATE_KEY
environment:
BOT_PRIVATE_KEY:
from_secret: BOT_PRIVATE_KEY
commands:
- apk add openssh-client git
- mkdir -p $HOME/.ssh
@ -127,8 +137,9 @@ steps:
deploy:
image: *alpine_image
secrets:
- BOT_PRIVATE_KEY
environment:
BOT_PRIVATE_KEY:
from_secret: BOT_PRIVATE_KEY
commands:
- apk add openssh-client rsync git
- mkdir -p $HOME/.ssh

31
.woodpecker/links.yaml Normal file
View file

@ -0,0 +1,31 @@
when:
- event: cron
cron: links
steps:
- name: links
image: docker.io/lycheeverse/lychee:0.15.1
failure: ignore
depends_on: []
commands:
- lychee pipeline/frontend/yaml/linter/schema/schema.json > links.md
- lychee --exclude localhost docs/docs/ >> links.md
- lychee --exclude localhost docs/src/pages/ >> links.md
- echo -e "\nLast checked:$(date)" >> links.md
- name: Update issue
image: docker.io/alpine:3.21
depends_on: links
environment:
GITHUB_TOKEN:
from_secret: github_token
commands:
- apk add -q --no-cache jq curl
- export ISSUE_NUMBER=4514
- export DESCRIPTION=$(cat links.md)
- |
curl -X PATCH \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${CI_REPO}/issues/$ISSUE_NUMBER \
-d "$(jq -n --arg body "$DESCRIPTION" '{body: $body}')"

View file

@ -1,16 +1,15 @@
when:
- event: push
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- release/*
steps:
release-helper:
image: woodpeckerci/plugin-ready-release-go:1.1.2
pull: true
- name: release-helper
image: docker.io/woodpeckerci/plugin-ready-release-go:3.1.1
settings:
release_branch: ${CI_REPO_DEFAULT_BRANCH}
release_branch: ${CI_COMMIT_BRANCH}
forge_type: github
git_email: woodpecker-bot@obermui.de
github_token:
from_secret: GITHUB_TOKEN
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
- event: manual
evaluate: 'TASK == "release-helper"'

View file

@ -1,24 +1,25 @@
when:
- event: [pull_request, cron]
- event: [pull_request]
- event: push
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- renovate/*
variables:
- &trivy_plugin docker.io/woodpeckerci/plugin-trivy:1.1.0
- &trivy_plugin docker.io/woodpeckerci/plugin-trivy:1.3.0
steps:
backend:
depends_on: []
image: *trivy_plugin
settings:
server: server
skip-dirs: web/,docs/
docs:
depends_on: []
image: *trivy_plugin
settings:
server: server
skip-dirs: node_modules/,plugins/woodpecker-plugins/node_modules/
dir: docs/
@ -26,5 +27,15 @@ steps:
depends_on: []
image: *trivy_plugin
settings:
server: server
skip-dirs: node_modules/
dir: web/
services:
server:
image: *trivy_plugin
settings:
service: true
db-repository: docker.io/aquasec/trivy-db:2
ports:
- 10000

View file

@ -1,19 +1,15 @@
when:
- event: pull_request
- event: push
branch: renovate/*
steps:
- name: lint-editorconfig
image: docker.io/mstruebing/editorconfig-checker:v3.0.3
image: docker.io/woodpeckerci/plugin-editorconfig-checker:0.2.0
depends_on: []
when:
- event: pull_request
- event: push
branch: renovate/*
- name: spellcheck
image: docker.io/node:22-alpine
image: docker.io/node:23-alpine
depends_on: []
commands:
- corepack enable
@ -23,15 +19,11 @@ steps:
- tree --gitignore -I 012_columns_rename_procs_to_steps.go -I versioned_docs -I '*opensource.svg'| pnpx cspell lint --no-progress stdin
- name: prettier
image: docker.io/woodpeckerci/plugin-prettier:0.1.0
image: docker.io/woodpeckerci/plugin-prettier:next
pull: true
depends_on: []
settings:
version: 3.2.5
- name: links
image: docker.io/lycheeverse/lychee:0.15.1
depends_on: []
commands:
- lychee pipeline/frontend/yaml/linter/schema/schema.json
- lychee --exclude localhost docs/docs/
- lychee --exclude localhost docs/src/pages/
version: 3.3.3
plugins:
- 'prettier-plugin-tailwindcss'
- '@ianvs/prettier-plugin-sort-imports'

View file

@ -1,5 +1,5 @@
variables:
- &golang_image 'docker.io/golang:1.22'
- &golang_image 'docker.io/golang:1.23'
- &when
- path: &when_path # related config files
- '.woodpecker/test.yaml'
@ -10,14 +10,9 @@ variables:
# schema changes
- 'pipeline/schema/**'
event: pull_request
- event: push
branch: renovate/*
path: *when_path
when:
- event: pull_request
- event: push
branch: renovate/*
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
path: *when_path
@ -37,9 +32,11 @@ steps:
- vendor
image: *golang_image
commands:
- go run go.woodpecker-ci.org/woodpecker/v2/cmd/cli lint
- go run go.woodpecker-ci.org/woodpecker/v3/cmd/cli lint
environment:
WOODPECKER_DISABLE_UPDATE_CHECK: true
WOODPECKER_LINT_STRICT: true
WOODPECKER_PLUGINS_PRIVILEGED: 'docker.io/woodpeckerci/plugin-docker-buildx'
when:
- event: pull_request
path:
@ -61,14 +58,14 @@ steps:
- make lint
when: *when
check-swagger:
check-openapi:
depends_on:
- vendor
image: *golang_image
commands:
- 'make generate-swagger'
- 'make generate-openapi'
- 'DIFF=$(git diff | head)'
- '[ -n "$DIFF" ] && { echo "swagger not up to date, exec `make generate-swagger` and commit"; exit 1; } || true'
- '[ -n "$DIFF" ] && { echo "openapi not up to date, exec `make generate-openapi` and commit"; exit 1; } || true'
when: *when
lint-license-header:
@ -128,7 +125,7 @@ steps:
- test
- sqlite
pull: true
image: docker.io/woodpeckerci/plugin-codecov:2.1.2
image: docker.io/woodpeckerci/plugin-codecov:2.1.6
settings:
files:
- agent-coverage.out
@ -144,7 +141,7 @@ steps:
services:
postgres:
image: docker.io/postgres:16
image: docker.io/postgres:17
ports: ['5432']
environment:
POSTGRES_USER: postgres
@ -152,7 +149,7 @@ services:
when: *when
mysql:
image: docker.io/mysql:8.2.0
image: docker.io/mysql:9.1.0
ports: ['3306']
environment:
MYSQL_DATABASE: test

View file

@ -3,10 +3,9 @@ when:
- event: push
branch:
- release/*
- renovate/*
variables:
- &node_image 'docker.io/node:22-alpine'
- &node_image 'docker.io/node:23-alpine'
- &when
path:
# related config files
@ -25,6 +24,18 @@ steps:
- pnpm install --frozen-lockfile
when: *when
prettier:
depends_on:
- install-dependencies
image: docker.io/woodpeckerci/plugin-prettier:next
pull: true
settings:
version: 3.3.3
plugins:
- 'prettier-plugin-tailwindcss'
- '@ianvs/prettier-plugin-sort-imports'
when: *when
lint:
depends_on:
- install-dependencies
@ -58,6 +69,7 @@ steps:
test:
depends_on:
- install-dependencies
- format-check # wait for it else test artifacts are falsely detected as wrong
image: *node_image
directory: web/
commands:

View file

@ -6,6 +6,7 @@ ignore-from-file:
- .gitignore
- server/store/datastore/migration/test-files/.gitignore
- web/.gitignore
- web/.yamlignore
rules:
line-length: disable

View file

@ -1,5 +1,490 @@
# Changelog
## [3.0.0](https://github.com/woodpecker-ci/woodpecker/releases/tag/v3.0.0) - 2024-12-13
### ❤️ Thanks to all contributors! ❤️
@6543, @Fishbowler, @M0Rf30, @anbraten, @cduchenoy, @fernandrone, @gnowland, @greenaar, @hg, @j04n-f, @jenrik, @johanneskastl, @jolheiser, @lafriks, @lukashass, @meln5674, @not-my-profile, @pat-s, @qwerty287, @smainz, @tori-27, @tsufeki, @xoxys, @xtexChooser, @zc-devs
### 💥 Breaking changes
- Drop native Let's Encrypt support [[#4541](https://github.com/woodpecker-ci/woodpecker/pull/4541)]
- Set new default approval mode based on repo visibility [[#4456](https://github.com/woodpecker-ci/woodpecker/pull/4456)]
- Do not set empty environment variables [[#4193](https://github.com/woodpecker-ci/woodpecker/pull/4193)]
- Unify cli commands and flags [[#4481](https://github.com/woodpecker-ci/woodpecker/pull/4481)]
- Move pipeline logs command [[#4480](https://github.com/woodpecker-ci/woodpecker/pull/4480)]
- Fix woodpecker-go repo model to match server [[#4479](https://github.com/woodpecker-ci/woodpecker/pull/4479)]
- Restructure cli commands [[#4467](https://github.com/woodpecker-ci/woodpecker/pull/4467)]
- Add pagination options to all supported endpoints in sdk [[#4463](https://github.com/woodpecker-ci/woodpecker/pull/4463)]
- Allow to set custom trusted clone plugins [[#4352](https://github.com/woodpecker-ci/woodpecker/pull/4352)]
- Add PipelineListsOptions to woodpecker-go [[#3652](https://github.com/woodpecker-ci/woodpecker/pull/3652)]
- Remove `secrets` in favor of `from_secret` [[#4363](https://github.com/woodpecker-ci/woodpecker/pull/4363)]
- Kubernetes | Docker: Add support for rootless images [[#4151](https://github.com/woodpecker-ci/woodpecker/pull/4151)]
- Split repo trusted setting [[#4025](https://github.com/woodpecker-ci/woodpecker/pull/4025)]
- Move docker resource limit settings from server to agent [[#3174](https://github.com/woodpecker-ci/woodpecker/pull/3174)]
- Set `/woodpecker` as default workdir for the **woodpecker-cli** container [[#4130](https://github.com/woodpecker-ci/woodpecker/pull/4130)]
- Require upgrade from 2.x [[#4112](https://github.com/woodpecker-ci/woodpecker/pull/4112)]
- Don't expose task data via api [[#4108](https://github.com/woodpecker-ci/woodpecker/pull/4108)]
- Remove some ci environment variables [[#3846](https://github.com/woodpecker-ci/woodpecker/pull/3846)]
- Remove all default privileged plugins [[#4053](https://github.com/woodpecker-ci/woodpecker/pull/4053)]
- Add option to filter secrets by plugins with specific tags [[#4069](https://github.com/woodpecker-ci/woodpecker/pull/4069)]
- Remove old pipeline options [[#4016](https://github.com/woodpecker-ci/woodpecker/pull/4016)]
- Remove various deprecations [[#4017](https://github.com/woodpecker-ci/woodpecker/pull/4017)]
- Drop repo name fallback for hooks [[#4013](https://github.com/woodpecker-ci/woodpecker/pull/4013)]
- Improve local backend detection [[#4006](https://github.com/woodpecker-ci/woodpecker/pull/4006)]
- Refactor JSON and SDK fields [[#3968](https://github.com/woodpecker-ci/woodpecker/pull/3968)]
- Migrate to maintained cron lib and remove seconds [[#3785](https://github.com/woodpecker-ci/woodpecker/pull/3785)]
- Switch to profile-based AppArmor configuration [[#4008](https://github.com/woodpecker-ci/woodpecker/pull/4008)]
- Remove Kubernetes default image pull secret name `regcred` [[#4005](https://github.com/woodpecker-ci/woodpecker/pull/4005)]
- Drop "WOODPECKER_WEBHOOK_HOST" env var and adjust docs [[#3969](https://github.com/woodpecker-ci/woodpecker/pull/3969)]
- Drop version in schema [[#3970](https://github.com/woodpecker-ci/woodpecker/pull/3970)]
- Update docker to v27 [[#3972](https://github.com/woodpecker-ci/woodpecker/pull/3972)]
- Require gitlab 12.4 [[#3966](https://github.com/woodpecker-ci/woodpecker/pull/3966)]
- Migrate to maintained httpsign library [[#3839](https://github.com/woodpecker-ci/woodpecker/pull/3839)]
- Remove `WOODPECKER_DEV_OAUTH_HOST` and `WOODPECKER_DEV_GITEA_OAUTH_URL` [[#3961](https://github.com/woodpecker-ci/woodpecker/pull/3961)]
- Remove deprecated pipeline keywords: `pipeline:`, `platform:`, `branches:` [[#3916](https://github.com/woodpecker-ci/woodpecker/pull/3916)]
- server: remove old unused routes [[#3845](https://github.com/woodpecker-ci/woodpecker/pull/3845)]
- CLI: remove step-id and add step-number as option to logs [[#3927](https://github.com/woodpecker-ci/woodpecker/pull/3927)]
### 🔒 Security
- Add server config to disable user registered agents [[#4206](https://github.com/woodpecker-ci/woodpecker/pull/4206)]
- chore: fix `http-proxy-middleware` CVE [[#4257](https://github.com/woodpecker-ci/woodpecker/pull/4257)]
- Allow altering trusted clone plugins and filter them via tag [[#4074](https://github.com/woodpecker-ci/woodpecker/pull/4074)]
- Update gitea sdk [[#4012](https://github.com/woodpecker-ci/woodpecker/pull/4012)]
- Update Forgejo SDK [[#3948](https://github.com/woodpecker-ci/woodpecker/pull/3948)]
### ✨ Features
- Add user as docker backend_option [[#4526](https://github.com/woodpecker-ci/woodpecker/pull/4526)]
- Implement org/user agents [[#3539](https://github.com/woodpecker-ci/woodpecker/pull/3539)]
- Replay pipeline using `cli exec` by downloading metadata [[#4103](https://github.com/woodpecker-ci/woodpecker/pull/4103)]
- Update clone plugin to support sha256 [[#4136](https://github.com/woodpecker-ci/woodpecker/pull/4136)]
### 🐛 Bug Fixes
- Fix BB ambiguous commit status key [[#4544](https://github.com/woodpecker-ci/woodpecker/pull/4544)]
- fix: addon JSON pointers [[#4508](https://github.com/woodpecker-ci/woodpecker/pull/4508)]
- Fix apparmorProfile being ignored when it's the only field [[#4507](https://github.com/woodpecker-ci/woodpecker/pull/4507)]
- Sanitize strings in table output [[#4466](https://github.com/woodpecker-ci/woodpecker/pull/4466)]
- Cleanup openapi generation [[#4331](https://github.com/woodpecker-ci/woodpecker/pull/4331)]
- Support github refresh tokens [[#3811](https://github.com/woodpecker-ci/woodpecker/pull/3811)]
- Fix not working overflow on repo list message [[#4420](https://github.com/woodpecker-ci/woodpecker/pull/4420)]
- Fix avatar column type [[#4340](https://github.com/woodpecker-ci/woodpecker/pull/4340)]
- fix `error="io: read/write on closed pipe"` on k8s backend [[#4281](https://github.com/woodpecker-ci/woodpecker/pull/4281)]
- Move update notifier dot into settings button [[#4334](https://github.com/woodpecker-ci/woodpecker/pull/4334)]
- gitea: add check if pull_request webhook is missing pull info [[#4305](https://github.com/woodpecker-ci/woodpecker/pull/4305)]
- Refresh token before loading branches [[#4284](https://github.com/woodpecker-ci/woodpecker/pull/4284)]
- Delete GitLab webhooks with partial URL match [[#4259](https://github.com/woodpecker-ci/woodpecker/pull/4259)]
- Increase `WOODPECKER_FORGE_TIMEOUT` to fix config fetching for GitLab [[#4262](https://github.com/woodpecker-ci/woodpecker/pull/4262)]
- Ensure cli exec has by default not the same prefix [[#4132](https://github.com/woodpecker-ci/woodpecker/pull/4132)]
- Fix repo add loading spinner [[#4135](https://github.com/woodpecker-ci/woodpecker/pull/4135)]
- Fix migration registries table [[#4111](https://github.com/woodpecker-ci/woodpecker/pull/4111)]
- Wait for tracer to be done before finishing workflow [[#4068](https://github.com/woodpecker-ci/woodpecker/pull/4068)]
- Fix schema with detached steps [[#4066](https://github.com/woodpecker-ci/woodpecker/pull/4066)]
- Fix schema with commands and entrypoint [[#4065](https://github.com/woodpecker-ci/woodpecker/pull/4065)]
- Read long log lines from file storage correctly [[#4048](https://github.com/woodpecker-ci/woodpecker/pull/4048)]
- Set refspec for gitlab MR [[#4021](https://github.com/woodpecker-ci/woodpecker/pull/4021)]
- Set `CI_PREV_COMMIT_{SOURCE,TARGET}_BRANCH` as mentioned in the documentation [[#4001](https://github.com/woodpecker-ci/woodpecker/pull/4001)]
- [Bitbucket Datacenter] Return empty list instead of null [[#4010](https://github.com/woodpecker-ci/woodpecker/pull/4010)]
- Fix BB PR pipeline ref [[#3985](https://github.com/woodpecker-ci/woodpecker/pull/3985)]
- Change Bitbucket PR hook to point the source branch, commit & ref [[#3965](https://github.com/woodpecker-ci/woodpecker/pull/3965)]
- Add updated, merged and declined events to bb webhook activation [[#3963](https://github.com/woodpecker-ci/woodpecker/pull/3963)]
- Fix login via navbar [[#3962](https://github.com/woodpecker-ci/woodpecker/pull/3962)]
- Truncate creation in list [[#3952](https://github.com/woodpecker-ci/woodpecker/pull/3952)]
- Fix panic if forge is unreachable [[#3944](https://github.com/woodpecker-ci/woodpecker/pull/3944)]
### 📚 Documentation
- Show client flags [[#4542](https://github.com/woodpecker-ci/woodpecker/pull/4542)]
- chore(deps): update react monorepo to v19 (major) [[#4523](https://github.com/woodpecker-ci/woodpecker/pull/4523)]
- chore(deps): update docs npm deps non-major [[#4519](https://github.com/woodpecker-ci/woodpecker/pull/4519)]
- chore(deps): lock file maintenance [[#4502](https://github.com/woodpecker-ci/woodpecker/pull/4502)]
- chore(deps): lock file maintenance [[#4501](https://github.com/woodpecker-ci/woodpecker/pull/4501)]
- chore(deps): update dependency isomorphic-dompurify to v2.18.0 [[#4493](https://github.com/woodpecker-ci/woodpecker/pull/4493)]
- fix(deps): update docs npm deps non-major [[#4484](https://github.com/woodpecker-ci/woodpecker/pull/4484)]
- Add migration notes for restructured cli commands [[#4476](https://github.com/woodpecker-ci/woodpecker/pull/4476)]
- Various fixes for `awesome.md` [[#4448](https://github.com/woodpecker-ci/woodpecker/pull/4448)]
- chore(deps): lock file maintenance [[#4453](https://github.com/woodpecker-ci/woodpecker/pull/4453)]
- chore(deps): update dependency isomorphic-dompurify to v2.17.0 [[#4449](https://github.com/woodpecker-ci/woodpecker/pull/4449)]
- fix(deps): update docs npm deps non-major [[#4441](https://github.com/woodpecker-ci/woodpecker/pull/4441)]
- chore(deps): update dependency @docusaurus/tsconfig to v3.6.2 [[#4433](https://github.com/woodpecker-ci/woodpecker/pull/4433)]
- chore(deps): lock file maintenance [[#4435](https://github.com/woodpecker-ci/woodpecker/pull/4435)]
- Bump minimum nodejs to v20 [[#4417](https://github.com/woodpecker-ci/woodpecker/pull/4417)]
- chore(deps): lock file maintenance [[#4402](https://github.com/woodpecker-ci/woodpecker/pull/4402)]
- Add microsoft teams plugin [[#4400](https://github.com/woodpecker-ci/woodpecker/pull/4400)]
- fix(deps): update docs npm deps non-major [[#4394](https://github.com/woodpecker-ci/woodpecker/pull/4394)]
- chore(deps): update dependency @types/node to v22 [[#4395](https://github.com/woodpecker-ci/woodpecker/pull/4395)]
- chore(deps): update dependency marked to v15 [[#4396](https://github.com/woodpecker-ci/woodpecker/pull/4396)]
- Podman is not (official) supported [[#4367](https://github.com/woodpecker-ci/woodpecker/pull/4367)]
- Add EditorConfig-Checker Plugin to docs [[#4371](https://github.com/woodpecker-ci/woodpecker/pull/4371)]
- Update netrc option description [[#4342](https://github.com/woodpecker-ci/woodpecker/pull/4342)]
- Fix deployment event note [[#4283](https://github.com/woodpecker-ci/woodpecker/pull/4283)]
- Improve migration notes [[#4291](https://github.com/woodpecker-ci/woodpecker/pull/4291)]
- Add instructions how to build images locally [[#4277](https://github.com/woodpecker-ci/woodpecker/pull/4277)]
- chore(deps): update docs npm deps non-major [[#4238](https://github.com/woodpecker-ci/woodpecker/pull/4238)]
- Correct spelling [[#4279](https://github.com/woodpecker-ci/woodpecker/pull/4279)]
- Add Telegram plugin [[#4229](https://github.com/woodpecker-ci/woodpecker/pull/4229)]
- Remove archived plugin [[#4227](https://github.com/woodpecker-ci/woodpecker/pull/4227)]
- Use "Woodpecker Authors" as copyright on website [[#4225](https://github.com/woodpecker-ci/woodpecker/pull/4225)]
- chore(deps): update dependency cookie to v1 [[#4224](https://github.com/woodpecker-ci/woodpecker/pull/4224)]
- fix(deps): update docs npm deps non-major [[#4221](https://github.com/woodpecker-ci/woodpecker/pull/4221)]
- Fix errant apostrophe in docker-compose documentation [[#4203](https://github.com/woodpecker-ci/woodpecker/pull/4203)]
- chore(deps): lock file maintenance [[#4186](https://github.com/woodpecker-ci/woodpecker/pull/4186)]
- chore(deps): update dependency concurrently to v9 [[#4176](https://github.com/woodpecker-ci/woodpecker/pull/4176)]
- chore(deps): update docs npm deps non-major [[#4164](https://github.com/woodpecker-ci/woodpecker/pull/4164)]
- Update image filter error message [[#4143](https://github.com/woodpecker-ci/woodpecker/pull/4143)]
- Docs: reference to built-in docker compose and remove deprecated version from compose examples [[#4123](https://github.com/woodpecker-ci/woodpecker/pull/4123)]
- directory key is allowed for services [[#4127](https://github.com/woodpecker-ci/woodpecker/pull/4127)]
- [docs] Removes dot prefix from pipeline configuration filenames [[#4105](https://github.com/woodpecker-ci/woodpecker/pull/4105)]
- Use kaniko plugin in docs as example [[#4072](https://github.com/woodpecker-ci/woodpecker/pull/4072)]
- Add some posts and videos [[#4070](https://github.com/woodpecker-ci/woodpecker/pull/4070)]
- Move event type descriptions from Terminology to Workflow Syntax [[#4062](https://github.com/woodpecker-ci/woodpecker/pull/4062)]
- Add community posts from discussions [[#4058](https://github.com/woodpecker-ci/woodpecker/pull/4058)]
- Add a pull request template with some basic guidelines [[#4055](https://github.com/woodpecker-ci/woodpecker/pull/4055)]
- Add examples of CI environment variable values [[#4009](https://github.com/woodpecker-ci/woodpecker/pull/4009)]
- Fix inline author warning [[#4040](https://github.com/woodpecker-ci/woodpecker/pull/4040)]
- Updated Secrets image filter docs [[#4028](https://github.com/woodpecker-ci/woodpecker/pull/4028)]
- Update dependency marked to v14 [[#4036](https://github.com/woodpecker-ci/woodpecker/pull/4036)]
- Update docs npm deps non-major [[#4033](https://github.com/woodpecker-ci/woodpecker/pull/4033)]
- Overhaul README [[#3995](https://github.com/woodpecker-ci/woodpecker/pull/3995)]
- fix(deps): update docs npm deps non-major [[#3990](https://github.com/woodpecker-ci/woodpecker/pull/3990)]
- Add spellchecking for docs [[#3787](https://github.com/woodpecker-ci/woodpecker/pull/3787)]
### 📈 Enhancement
- Use docusaurus faster [[#4528](https://github.com/woodpecker-ci/woodpecker/pull/4528)]
- Use pagination helper to list pipelines in cli [[#4478](https://github.com/woodpecker-ci/woodpecker/pull/4478)]
- Some UI improvements [[#4497](https://github.com/woodpecker-ci/woodpecker/pull/4497)]
- Add status filter to list pipeline API [[#4494](https://github.com/woodpecker-ci/woodpecker/pull/4494)]
- Use JS-native date/time formatting [[#4488](https://github.com/woodpecker-ci/woodpecker/pull/4488)]
- Add pipeline purge command to cli [[#4470](https://github.com/woodpecker-ci/woodpecker/pull/4470)]
- Add option to limit the resultset returned by paginate helper [[#4475](https://github.com/woodpecker-ci/woodpecker/pull/4475)]
- Add filter to list repository pipelines API [[#4416](https://github.com/woodpecker-ci/woodpecker/pull/4416)]
- Increase log level when failing to fetch YAML [[#4107](https://github.com/woodpecker-ci/woodpecker/pull/4107)]
- Trim space to all config flags that allow to read value from file [[#4468](https://github.com/woodpecker-ci/woodpecker/pull/4468)]
- Change default icon size to 20 [[#4458](https://github.com/woodpecker-ci/woodpecker/pull/4458)]
- Add visibility icon to repo list [[#4460](https://github.com/woodpecker-ci/woodpecker/pull/4460)]
- Unify pipeline status icons [[#4414](https://github.com/woodpecker-ci/woodpecker/pull/4414)]
- Improve project settings descriptions [[#4410](https://github.com/woodpecker-ci/woodpecker/pull/4410)]
- Add count badge to visualize counters in tab title [[#4419](https://github.com/woodpecker-ci/woodpecker/pull/4419)]
- Redesign repo list and include last pipeline [[#4386](https://github.com/woodpecker-ci/woodpecker/pull/4386)]
- Use KeyValueEditor for DeployPipelinePopup too [[#4412](https://github.com/woodpecker-ci/woodpecker/pull/4412)]
- Use separate routes instead of anchors [[#4285](https://github.com/woodpecker-ci/woodpecker/pull/4285)]
- Untangle settings / header slots [[#4403](https://github.com/woodpecker-ci/woodpecker/pull/4403)]
- Fix responsiveness of the settings template [[#4383](https://github.com/woodpecker-ci/woodpecker/pull/4383)]
- Use squared spinner for active pipelines [[#4379](https://github.com/woodpecker-ci/woodpecker/pull/4379)]
- Add server configuration option to add default set of labels for workflows that has no labels specified [[#4326](https://github.com/woodpecker-ci/woodpecker/pull/4326)]
- Add `cli lint` option to treat warnings as errors [[#4373](https://github.com/woodpecker-ci/woodpecker/pull/4373)]
- Improve error message for wrong secrets / environment config [[#4359](https://github.com/woodpecker-ci/woodpecker/pull/4359)]
- Improve linter messages in UI [[#4351](https://github.com/woodpecker-ci/woodpecker/pull/4351)]
- Pass settings to services [[#4338](https://github.com/woodpecker-ci/woodpecker/pull/4338)]
- Inline model types for migrations [[#4293](https://github.com/woodpecker-ci/woodpecker/pull/4293)]
- Add options to control the database connections (open,idle,timeout) [[#4212](https://github.com/woodpecker-ci/woodpecker/pull/4212)]
- Move Queue creation behind new func that evaluates queue type [[#4252](https://github.com/woodpecker-ci/woodpecker/pull/4252)]
- Add additional error message on swagger v2 to v3 convert [[#4254](https://github.com/woodpecker-ci/woodpecker/pull/4254)]
- Deprecate `secrets` [[#4235](https://github.com/woodpecker-ci/woodpecker/pull/4235)]
- Agent edit/detail view: change the help url based on the backend [[#4219](https://github.com/woodpecker-ci/woodpecker/pull/4219)]
- Use middleware to load org [[#4208](https://github.com/woodpecker-ci/woodpecker/pull/4208)]
- Assign workflows to agents with the best label matches [[#4201](https://github.com/woodpecker-ci/woodpecker/pull/4201)]
- Report custom labels set by agent admins back [[#4141](https://github.com/woodpecker-ci/woodpecker/pull/4141)]
- Highlight invalid entries in manual pipeline trigger [[#4153](https://github.com/woodpecker-ci/woodpecker/pull/4153)]
- Print agent labels in debug mode [[#4155](https://github.com/woodpecker-ci/woodpecker/pull/4155)]
- Implement registries for Kubernetes backend [[#4092](https://github.com/woodpecker-ci/woodpecker/pull/4092)]
- Correct cli exec flags and remove ineffective ones [[#4129](https://github.com/woodpecker-ci/woodpecker/pull/4129)]
- Set repo user to repairing user when old user is missing [[#4128](https://github.com/woodpecker-ci/woodpecker/pull/4128)]
- Restart tasks on dead agents sooner [[#4114](https://github.com/woodpecker-ci/woodpecker/pull/4114)]
- Adjust cli exec metadata structure to equal server metadata [[#4119](https://github.com/woodpecker-ci/woodpecker/pull/4119)]
- Allow to restart declined pipelines [[#4109](https://github.com/woodpecker-ci/woodpecker/pull/4109)]
- Add indices to repo table [[#4087](https://github.com/woodpecker-ci/woodpecker/pull/4087)]
- Add systemd unit files to the RPM/DEB packages [[#3986](https://github.com/woodpecker-ci/woodpecker/pull/3986)]
- Duplicate key `workflow_id` in the agent logs [[#4046](https://github.com/woodpecker-ci/woodpecker/pull/4046)]
- Improve error on config loading [[#4024](https://github.com/woodpecker-ci/woodpecker/pull/4024)]
- Show error if secret name is missing [[#4014](https://github.com/woodpecker-ci/woodpecker/pull/4014)]
- Show error returned from API [[#3980](https://github.com/woodpecker-ci/woodpecker/pull/3980)]
- Move manual popup to own page [[#3981](https://github.com/woodpecker-ci/woodpecker/pull/3981)]
- Fail on InvalidImageName [[#4007](https://github.com/woodpecker-ci/woodpecker/pull/4007)]
- Use Bitbucket PR title for pipeline message [[#3984](https://github.com/woodpecker-ci/woodpecker/pull/3984)]
- Show logs if step has error [[#3979](https://github.com/woodpecker-ci/woodpecker/pull/3979)]
- Refactor docker backend and add more test coverage [[#2700](https://github.com/woodpecker-ci/woodpecker/pull/2700)]
- Make cli plugin log purge recognize steps by name [[#3953](https://github.com/woodpecker-ci/woodpecker/pull/3953)]
- Pin page size [[#3946](https://github.com/woodpecker-ci/woodpecker/pull/3946)]
- Improve cron list [[#3947](https://github.com/woodpecker-ci/woodpecker/pull/3947)]
- Add PULLREQUEST_DRONE_PULL_REQUEST drone env [[#3939](https://github.com/woodpecker-ci/woodpecker/pull/3939)]
- Make agent gRPC errors distinguishable [[#3936](https://github.com/woodpecker-ci/woodpecker/pull/3936)]
### 📦️ Dependency
- fix(deps): update module google.golang.org/grpc to v1.69.0 [[#4563](https://github.com/woodpecker-ci/woodpecker/pull/4563)]
- fix(deps): update golang-packages [[#4553](https://github.com/woodpecker-ci/woodpecker/pull/4553)]
- Update kin-openapi [[#4560](https://github.com/woodpecker-ci/woodpecker/pull/4560)]
- fix(deps): update module golang.org/x/crypto to v0.31.0 [security] [[#4557](https://github.com/woodpecker-ci/woodpecker/pull/4557)]
- fix(deps): update golang-packages [[#4546](https://github.com/woodpecker-ci/woodpecker/pull/4546)]
- chore(deps): update docker.io/woodpeckerci/plugin-ready-release-go docker tag to v3.1.0 [[#4536](https://github.com/woodpecker-ci/woodpecker/pull/4536)]
- chore(deps): update docker.io/curlimages/curl docker tag to v8.11.0 [[#4530](https://github.com/woodpecker-ci/woodpecker/pull/4530)]
- fix(deps): update golang-packages [[#4496](https://github.com/woodpecker-ci/woodpecker/pull/4496)]
- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v5.1.0 [[#4524](https://github.com/woodpecker-ci/woodpecker/pull/4524)]
- chore(deps): update docker.io/woodpeckerci/plugin-prettier docker tag to v1 [[#4522](https://github.com/woodpecker-ci/woodpecker/pull/4522)]
- chore(deps): update docker.io/alpine docker tag to v3.21 [[#4520](https://github.com/woodpecker-ci/woodpecker/pull/4520)]
- chore(deps): update dependency vite to v6 [[#4485](https://github.com/woodpecker-ci/woodpecker/pull/4485)]
- chore(deps): update docker.io/woodpeckerci/plugin-ready-release-go docker tag to v3 [[#4506](https://github.com/woodpecker-ci/woodpecker/pull/4506)]
- chore(deps): update docker.io/woodpeckerci/plugin-surge-preview docker tag to v1.3.3 [[#4495](https://github.com/woodpecker-ci/woodpecker/pull/4495)]
- fix(deps): update golang-packages [[#4477](https://github.com/woodpecker-ci/woodpecker/pull/4477)]
- fix(deps): update dependency @vueuse/core to v12 [[#4486](https://github.com/woodpecker-ci/woodpecker/pull/4486)]
- fix(deps): update module github.com/google/go-github/v66 to v67 [[#4487](https://github.com/woodpecker-ci/woodpecker/pull/4487)]
- chore(deps): update woodpeckerci/plugin-release docker tag to v0.2.2 [[#4483](https://github.com/woodpecker-ci/woodpecker/pull/4483)]
- chore(deps): update pre-commit hook golangci/golangci-lint to v1.62.2 [[#4482](https://github.com/woodpecker-ci/woodpecker/pull/4482)]
- fix(deps): update golang-packages [[#4452](https://github.com/woodpecker-ci/woodpecker/pull/4452)]
- fix(deps): update golang-packages [[#4411](https://github.com/woodpecker-ci/woodpecker/pull/4411)]
- chore(deps): update pre-commit hook igorshubovych/markdownlint-cli to v0.43.0 [[#4443](https://github.com/woodpecker-ci/woodpecker/pull/4443)]
- chore(deps): update postgres docker tag to v17.2 [[#4442](https://github.com/woodpecker-ci/woodpecker/pull/4442)]
- chore(deps): update docker.io/woodpeckerci/plugin-trivy docker tag to v1.3.0 [[#4434](https://github.com/woodpecker-ci/woodpecker/pull/4434)]
- chore(deps): update web npm deps non-major [[#4432](https://github.com/woodpecker-ci/woodpecker/pull/4432)]
- fix(deps): update golang-packages [[#4401](https://github.com/woodpecker-ci/woodpecker/pull/4401)]
- chore(deps): update web npm deps non-major [[#4391](https://github.com/woodpecker-ci/woodpecker/pull/4391)]
- fix(deps): update dependency @intlify/unplugin-vue-i18n to v6 [[#4397](https://github.com/woodpecker-ci/woodpecker/pull/4397)]
- chore(deps): update pre-commit hook golangci/golangci-lint to v1.62.0 [[#4390](https://github.com/woodpecker-ci/woodpecker/pull/4390)]
- chore(deps): update postgres docker tag to v17.1 [[#4389](https://github.com/woodpecker-ci/woodpecker/pull/4389)]
- chore(deps): update docker.io/techknowlogick/xgo docker tag to go-1.23.x [[#4388](https://github.com/woodpecker-ci/woodpecker/pull/4388)]
- chore(config): migrate renovate config [[#4296](https://github.com/woodpecker-ci/woodpecker/pull/4296)]
- chore(deps): update docker.io/woodpeckerci/plugin-trivy docker tag to v1.2.0 [[#4289](https://github.com/woodpecker-ci/woodpecker/pull/4289)]
- chore(deps): update docker.io/techknowlogick/xgo docker tag to go-1.23.x [[#4282](https://github.com/woodpecker-ci/woodpecker/pull/4282)]
- fix(deps): update golang-packages [[#4251](https://github.com/woodpecker-ci/woodpecker/pull/4251)]
- fix(deps): update web npm deps non-major [[#4258](https://github.com/woodpecker-ci/woodpecker/pull/4258)]
- chore(deps): update web npm deps non-major [[#4250](https://github.com/woodpecker-ci/woodpecker/pull/4250)]
- chore(deps): update node.js to v23 [[#4239](https://github.com/woodpecker-ci/woodpecker/pull/4239)]
- chore(deps): update web npm deps non-major [[#4237](https://github.com/woodpecker-ci/woodpecker/pull/4237)]
- chore(deps): update docker.io/mysql docker tag to v9.1.0 [[#4236](https://github.com/woodpecker-ci/woodpecker/pull/4236)]
- fix(deps): update dependency simple-icons to v13.14.0 [[#4226](https://github.com/woodpecker-ci/woodpecker/pull/4226)]
- fix(deps): update web npm deps non-major [[#4223](https://github.com/woodpecker-ci/woodpecker/pull/4223)]
- fix(deps): update golang-packages [[#4215](https://github.com/woodpecker-ci/woodpecker/pull/4215)]
- fix(deps): update golang-packages [[#4210](https://github.com/woodpecker-ci/woodpecker/pull/4210)]
- fix(deps): update module github.com/google/go-github/v65 to v66 [[#4205](https://github.com/woodpecker-ci/woodpecker/pull/4205)]
- fix(deps): update dependency vue-i18n to v10.0.4 [[#4200](https://github.com/woodpecker-ci/woodpecker/pull/4200)]
- chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v5 [[#4192](https://github.com/woodpecker-ci/woodpecker/pull/4192)]
- fix(deps): update dependency simple-icons to v13.13.0 [[#4196](https://github.com/woodpecker-ci/woodpecker/pull/4196)]
- chore(deps): update web npm deps non-major [[#4174](https://github.com/woodpecker-ci/woodpecker/pull/4174)]
- chore(deps): update docker.io/postgres docker tag to v17 [[#4179](https://github.com/woodpecker-ci/woodpecker/pull/4179)]
- fix(deps): update dependency @intlify/unplugin-vue-i18n to v5 [[#4183](https://github.com/woodpecker-ci/woodpecker/pull/4183)]
- fix(deps): update dependency @vueuse/core to v11 [[#4184](https://github.com/woodpecker-ci/woodpecker/pull/4184)]
- chore(deps): update docker.io/woodpeckerci/plugin-codecov docker tag to v2.1.5 [[#4167](https://github.com/woodpecker-ci/woodpecker/pull/4167)]
- fix(deps): update module github.com/google/go-github/v64 to v65 [[#4185](https://github.com/woodpecker-ci/woodpecker/pull/4185)]
- chore(deps): update docker.io/mysql docker tag to v9 [[#4178](https://github.com/woodpecker-ci/woodpecker/pull/4178)]
- chore(deps): update docker.io/alpine docker tag to v3.20 [[#4169](https://github.com/woodpecker-ci/woodpecker/pull/4169)]
- fix(deps): update github.com/urfave/cli/v3 digest to 20ef97b [[#4166](https://github.com/woodpecker-ci/woodpecker/pull/4166)]
- chore(deps): update docker.io/woodpeckerci/plugin-surge-preview docker tag to v1.3.2 [[#4168](https://github.com/woodpecker-ci/woodpecker/pull/4168)]
- chore(deps): update woodpeckerci/plugin-release docker tag to v0.2.1 [[#4175](https://github.com/woodpecker-ci/woodpecker/pull/4175)]
- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v2 [[#4182](https://github.com/woodpecker-ci/woodpecker/pull/4182)]
- fix(deps): update github.com/muesli/termenv digest to 82936c5 [[#4165](https://github.com/woodpecker-ci/woodpecker/pull/4165)]
- chore(deps): update postgres docker tag to v17 [[#4181](https://github.com/woodpecker-ci/woodpecker/pull/4181)]
- chore(deps): update pre-commit non-major [[#4173](https://github.com/woodpecker-ci/woodpecker/pull/4173)]
- chore(deps): update docker.io/golang docker tag to v1.23 [[#4170](https://github.com/woodpecker-ci/woodpecker/pull/4170)]
- chore(deps): update node.js to v22 [[#4180](https://github.com/woodpecker-ci/woodpecker/pull/4180)]
- fix(deps): update golang-packages [[#4161](https://github.com/woodpecker-ci/woodpecker/pull/4161)]
- chore(deps): update dependency @antfu/eslint-config to v3 [[#4095](https://github.com/woodpecker-ci/woodpecker/pull/4095)]
- chore(deps): update dependency jsdom to v25 [[#4094](https://github.com/woodpecker-ci/woodpecker/pull/4094)]
- chore(deps): update docker.io/golang docker tag to v1.23 [[#4081](https://github.com/woodpecker-ci/woodpecker/pull/4081)]
- chore(deps): update docker.io/woodpeckerci/plugin-prettier docker tag to v0.2.0 [[#4082](https://github.com/woodpecker-ci/woodpecker/pull/4082)]
- fix(deps): update module github.com/google/go-github/v63 to v64 [[#4073](https://github.com/woodpecker-ci/woodpecker/pull/4073)]
- fix(deps): update golang-packages [[#4059](https://github.com/woodpecker-ci/woodpecker/pull/4059)]
- Update github.com/urfave/cli/v3 digest to fc07a8c [[#4043](https://github.com/woodpecker-ci/woodpecker/pull/4043)]
- Update woodpeckerci/plugin-git Docker tag to v2.5.2 [[#4041](https://github.com/woodpecker-ci/woodpecker/pull/4041)]
- Update web npm deps non-major [[#4034](https://github.com/woodpecker-ci/woodpecker/pull/4034)]
- Update dependency simple-icons to v13 [[#4037](https://github.com/woodpecker-ci/woodpecker/pull/4037)]
- chore(deps): lock file maintenance [[#3991](https://github.com/woodpecker-ci/woodpecker/pull/3991)]
- fix(deps): update golang-packages [[#3958](https://github.com/woodpecker-ci/woodpecker/pull/3958)]
### Misc
- Move link checks into cron-curated issue dashboard [[#4515](https://github.com/woodpecker-ci/woodpecker/pull/4515)]
- Add settings title action [[#4499](https://github.com/woodpecker-ci/woodpecker/pull/4499)]
- Use same default sort for repo and org repo list [[#4461](https://github.com/woodpecker-ci/woodpecker/pull/4461)]
- Add dns config option to official feature set [[#4418](https://github.com/woodpecker-ci/woodpecker/pull/4418)]
- Remove `renovate` branch triggers [[#4437](https://github.com/woodpecker-ci/woodpecker/pull/4437)]
- Improve tab layout and add hover effect [[#4431](https://github.com/woodpecker-ci/woodpecker/pull/4431)]
- Dont run pipeline on push events to renovate branches [[#4406](https://github.com/woodpecker-ci/woodpecker/pull/4406)]
- Harden and correct fifo task queue tests [[#4377](https://github.com/woodpecker-ci/woodpecker/pull/4377)]
- Kubernetes documentation enhancements [[#4374](https://github.com/woodpecker-ci/woodpecker/pull/4374)]
- Use release-helper for release/* branches [[#4301](https://github.com/woodpecker-ci/woodpecker/pull/4301)]
- Fix wording for privileged plugins linter error [[#4280](https://github.com/woodpecker-ci/woodpecker/pull/4280)]
- Fix renovate support for `xgo` [[#4276](https://github.com/woodpecker-ci/woodpecker/pull/4276)]
- Improve nix development environment [[#4256](https://github.com/woodpecker-ci/woodpecker/pull/4256)]
- [pre-commit.ci] pre-commit autoupdate [[#4209](https://github.com/woodpecker-ci/woodpecker/pull/4209)]
- Add `.lycheeignore` [[#4154](https://github.com/woodpecker-ci/woodpecker/pull/4154)]
- Add eslint-plugin-promise back [[#4022](https://github.com/woodpecker-ci/woodpecker/pull/4022)]
- Improve wording [[#3951](https://github.com/woodpecker-ci/woodpecker/pull/3951)]
- Fix typos and optimize wording [[#3940](https://github.com/woodpecker-ci/woodpecker/pull/3940)]
## [2.7.2](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.7.2) - 2024-11-03
### Important
To secure your instance, set `WOODPECKER_PLUGINS_PRIVILEGED` to only allow specific versions of the `woodpeckerci/plugin-docker-buildx` plugin, use version 5.0.0 or above. This prevents older, potentially unstable versions from being privileged.
For example, to allow only version 5.0.0, use:
```bash
WOODPECKER_PLUGINS_PRIVILEGED=woodpeckerci/plugin-docker-buildx:5.0.0
```
To allow multiple versions, you can separate them with commas:
```bash
WOODPECKER_PLUGINS_PRIVILEGED=woodpeckerci/plugin-docker-buildx:5.0.0,woodpeckerci/plugin-docker-buildx:5.1.0
```
This setup ensures only specified, stable plugin versions are given privileged access.
Read more about it in [#4213](https://github.com/woodpecker-ci/woodpecker/pull/4213)
### ❤️ Thanks to all contributors! ❤️
@6543, @anbraten, @j04n-f, @pat-s, @qwerty287
### 🔒 Security
- Chore(deps): update dependency vite to v5.4.6 [security] ([#4163](https://github.com/woodpecker-ci/woodpecker/pull/4163)) [[#4187](https://github.com/woodpecker-ci/woodpecker/pull/4187)]
### 🐛 Bug Fixes
- Don't parse forge config files multiple times if no error occured ([#4272](https://github.com/woodpecker-ci/woodpecker/pull/4272)) [[#4273](https://github.com/woodpecker-ci/woodpecker/pull/4273)]
- Fix repo/owner parsing for gitlab ([#4255](https://github.com/woodpecker-ci/woodpecker/pull/4255)) [[#4261](https://github.com/woodpecker-ci/woodpecker/pull/4261)]
- Run queue.process() in background [[#4115](https://github.com/woodpecker-ci/woodpecker/pull/4115)]
- Only update agent.LastWork if not done recently ([#4031](https://github.com/woodpecker-ci/woodpecker/pull/4031)) [[#4100](https://github.com/woodpecker-ci/woodpecker/pull/4100)]
### Misc
- Backport JS dependency updates [[#4189](https://github.com/woodpecker-ci/woodpecker/pull/4189)]
## [2.7.1](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.7.1) - 2024-09-07
### ❤️ Thanks to all contributors! ❤️
@6543, @anbraten, @j04n-f, @qwerty287
### 🔒 Security
- Lint privileged plugin match and allow to be set empty [[#4084](https://github.com/woodpecker-ci/woodpecker/pull/4084)]
- Allow admins to specify privileged plugins by name **and tag** [[#4076](https://github.com/woodpecker-ci/woodpecker/pull/4076)]
- Warn if using secrets/env with plugin [[#4039](https://github.com/woodpecker-ci/woodpecker/pull/4039)]
### 🐛 Bug Fixes
- Set refspec for gitlab MR [[#4021](https://github.com/woodpecker-ci/woodpecker/pull/4021)]
- Change Bitbucket PR hook to point the source branch, commit & ref [[#3965](https://github.com/woodpecker-ci/woodpecker/pull/3965)]
- Add updated, merged and declined events to bb webhook activation [[#3963](https://github.com/woodpecker-ci/woodpecker/pull/3963)]
- Fix login via navbar [[#3962](https://github.com/woodpecker-ci/woodpecker/pull/3962)]
- Fix panic if forge is unreachable [[#3944](https://github.com/woodpecker-ci/woodpecker/pull/3944)]
- Fix org settings page [[#4093](https://github.com/woodpecker-ci/woodpecker/pull/4093)]
### Misc
- Bump github.com/docker/docker from v24.0.9 to v24.0.9+30 [[#4077](https://github.com/woodpecker-ci/woodpecker/pull/4077)]
## [2.7.0](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.7.0) - 2024-07-18
### ❤️ Thanks to all contributors! ❤️
@6543, @anbraten, @dvjn, @hhamalai, @lafriks, @pat-s, @qwerty287, @smainz, @tongjicoder, @zc-devs
### 🔒 Security
- Add blocklist of environment variables who could alter execution of plugins [[#3934](https://github.com/woodpecker-ci/woodpecker/pull/3934)]
- Make sure plugins only mount the workspace base in a predefinde location [[#3933](https://github.com/woodpecker-ci/woodpecker/pull/3933)]
- Disallow to set arbitrary environments for plugins [[#3909](https://github.com/woodpecker-ci/woodpecker/pull/3909)]
- Use proper oauth state [[#3847](https://github.com/woodpecker-ci/woodpecker/pull/3847)]
- Enhance token checking [[#3842](https://github.com/woodpecker-ci/woodpecker/pull/3842)]
- Bump github.com/hashicorp/go-retryablehttp v0.7.5 -> v0.7.7 [[#3834](https://github.com/woodpecker-ci/woodpecker/pull/3834)]
### ✨ Features
- Gracefully shutdown server [[#3896](https://github.com/woodpecker-ci/woodpecker/pull/3896)]
- Gracefully shutdown agent [[#3895](https://github.com/woodpecker-ci/woodpecker/pull/3895)]
- Convert urls in logs to links [[#3904](https://github.com/woodpecker-ci/woodpecker/pull/3904)]
- Allow login using multiple forges [[#3822](https://github.com/woodpecker-ci/woodpecker/pull/3822)]
- Global and organization registries [[#1672](https://github.com/woodpecker-ci/woodpecker/pull/1672)]
- Cli get repo from git remote [[#3830](https://github.com/woodpecker-ci/woodpecker/pull/3830)]
- Add api for forges [[#3733](https://github.com/woodpecker-ci/woodpecker/pull/3733)]
### 📈 Enhancement
- Cli fix pipeline logs [[#3913](https://github.com/woodpecker-ci/woodpecker/pull/3913)]
- Migrate to github.com/urfave/cli/v3 [[#2951](https://github.com/woodpecker-ci/woodpecker/pull/2951)]
- Allow to change the working directory also for plugins and services [[#3914](https://github.com/woodpecker-ci/woodpecker/pull/3914)]
- Remove `unplugin-icons` [[#3809](https://github.com/woodpecker-ci/woodpecker/pull/3809)]
- Release windows binaries as zip file [[#3906](https://github.com/woodpecker-ci/woodpecker/pull/3906)]
- Convert to openapi 3.0 [[#3897](https://github.com/woodpecker-ci/woodpecker/pull/3897)]
- Enhance pipeline list [[#3898](https://github.com/woodpecker-ci/woodpecker/pull/3898)]
- Add user registries UI [[#3888](https://github.com/woodpecker-ci/woodpecker/pull/3888)]
- Sort users by login [[#3891](https://github.com/woodpecker-ci/woodpecker/pull/3891)]
- Exclude dummy backend in production [[#3877](https://github.com/woodpecker-ci/woodpecker/pull/3877)]
- Fix deploy task env [[#3878](https://github.com/woodpecker-ci/woodpecker/pull/3878)]
- Get default branch and show message in pipeline list [[#3867](https://github.com/woodpecker-ci/woodpecker/pull/3867)]
- Add timestamp for last work done by agent [[#3844](https://github.com/woodpecker-ci/woodpecker/pull/3844)]
- Adjust logger types [[#3859](https://github.com/woodpecker-ci/woodpecker/pull/3859)]
- Cleanup state reporting [[#3850](https://github.com/woodpecker-ci/woodpecker/pull/3850)]
- Unify DB tables/columns [[#3806](https://github.com/woodpecker-ci/woodpecker/pull/3806)]
- Let webhook pass on pipeline parsing error [[#3829](https://github.com/woodpecker-ci/woodpecker/pull/3829)]
- Exclude mocks from release build [[#3831](https://github.com/woodpecker-ci/woodpecker/pull/3831)]
- K8s secrets reference from step [[#3655](https://github.com/woodpecker-ci/woodpecker/pull/3655)]
### 🐛 Bug Fixes
- Handle empty repositories in gitea when listing PRs [[#3925](https://github.com/woodpecker-ci/woodpecker/pull/3925)]
- Update alpine package dep for docker images [[#3917](https://github.com/woodpecker-ci/woodpecker/pull/3917)]
- Don't report error if agent was terminated gracefully [[#3894](https://github.com/woodpecker-ci/woodpecker/pull/3894)]
- Let agents continuously report their health [[#3893](https://github.com/woodpecker-ci/woodpecker/pull/3893)]
- Ignore warnings for cli exec [[#3868](https://github.com/woodpecker-ci/woodpecker/pull/3868)]
- Correct favicon states [[#3832](https://github.com/woodpecker-ci/woodpecker/pull/3832)]
- Cleanup of the login flow and tests [[#3810](https://github.com/woodpecker-ci/woodpecker/pull/3810)]
- Fix newlines in logs [[#3808](https://github.com/woodpecker-ci/woodpecker/pull/3808)]
- Fix authentication error handling [[#3807](https://github.com/woodpecker-ci/woodpecker/pull/3807)]
### 📚 Documentation
- Streamline docs for new users [[#3803](https://github.com/woodpecker-ci/woodpecker/pull/3803)]
- Add mastodon verification [[#3843](https://github.com/woodpecker-ci/woodpecker/pull/3843)]
- chore(deps): update docs npm deps non-major [[#3837](https://github.com/woodpecker-ci/woodpecker/pull/3837)]
- fix(deps): update docs npm deps non-major [[#3824](https://github.com/woodpecker-ci/woodpecker/pull/3824)]
- Add openSUSE package [[#3800](https://github.com/woodpecker-ci/woodpecker/pull/3800)]
- chore(deps): update docs npm deps non-major [[#3798](https://github.com/woodpecker-ci/woodpecker/pull/3798)]
- Add "Docker Tags" Plugin [[#3796](https://github.com/woodpecker-ci/woodpecker/pull/3796)]
- chore(deps): update dependency marked to v13 [[#3792](https://github.com/woodpecker-ci/woodpecker/pull/3792)]
- chore: fix some comments [[#3788](https://github.com/woodpecker-ci/woodpecker/pull/3788)]
### Misc
- chore(deps): update web npm deps non-major [[#3930](https://github.com/woodpecker-ci/woodpecker/pull/3930)]
- chore(deps): update dependency vitest to v2 [[#3905](https://github.com/woodpecker-ci/woodpecker/pull/3905)]
- fix(deps): update module github.com/google/go-github/v62 to v63 [[#3910](https://github.com/woodpecker-ci/woodpecker/pull/3910)]
- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v4.1.0 [[#3908](https://github.com/woodpecker-ci/woodpecker/pull/3908)]
- Update plugin-git and add renovate trigger [[#3901](https://github.com/woodpecker-ci/woodpecker/pull/3901)]
- chore(deps): update docker.io/mstruebing/editorconfig-checker docker tag to v3.0.3 [[#3903](https://github.com/woodpecker-ci/woodpecker/pull/3903)]
- fix(deps): update golang-packages [[#3875](https://github.com/woodpecker-ci/woodpecker/pull/3875)]
- chore(deps): lock file maintenance [[#3876](https://github.com/woodpecker-ci/woodpecker/pull/3876)]
- [pre-commit.ci] pre-commit autoupdate [[#3862](https://github.com/woodpecker-ci/woodpecker/pull/3862)]
- Add dummy backend [[#3820](https://github.com/woodpecker-ci/woodpecker/pull/3820)]
- chore(deps): update dependency replace-in-file to v8 [[#3852](https://github.com/woodpecker-ci/woodpecker/pull/3852)]
- Update forgejo sdk [[#3840](https://github.com/woodpecker-ci/woodpecker/pull/3840)]
- chore(deps): lock file maintenance [[#3838](https://github.com/woodpecker-ci/woodpecker/pull/3838)]
- Allow to set dist dir using env var [[#3814](https://github.com/woodpecker-ci/woodpecker/pull/3814)]
- chore(deps): lock file maintenance [[#3805](https://github.com/woodpecker-ci/woodpecker/pull/3805)]
- chore(deps): update docker.io/lycheeverse/lychee docker tag to v0.15.1 [[#3797](https://github.com/woodpecker-ci/woodpecker/pull/3797)]
## [2.6.1](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.6.1) - 2024-07-19
### 🔒 Security
- Add blocklist of environment variables who could alter execution of plugins [[#3934](https://github.com/woodpecker-ci/woodpecker/pull/3934)]
- Make sure plugins only mount the workspace base in a predefinde location [[#3933](https://github.com/woodpecker-ci/woodpecker/pull/3933)]
- Disalow to set arbitrary environments for plugins [[#3909](https://github.com/woodpecker-ci/woodpecker/pull/3909)]
- Bump trivy plugin version and remove unused variable [[#3833](https://github.com/woodpecker-ci/woodpecker/pull/3833)]
### 🐛 Bug Fixes
- Let webhook pass on pipeline parsion error [[#3829](https://github.com/woodpecker-ci/woodpecker/pull/3829)]
- Fix newlines in logs [[#3808](https://github.com/woodpecker-ci/woodpecker/pull/3808)]
## [2.6.0](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.6.0) - 2024-06-13
### ❤️ Thanks to all contributors! ❤️

101
Makefile
View file

@ -30,7 +30,7 @@ else
endif
TAGS ?=
LDFLAGS := -X go.woodpecker-ci.org/woodpecker/v2/version.Version=${VERSION}
LDFLAGS := -X go.woodpecker-ci.org/woodpecker/v3/version.Version=${VERSION}
STATIC_BUILD ?= true
ifeq ($(STATIC_BUILD),true)
LDFLAGS := -s -w -extldflags "-static" $(LDFLAGS)
@ -39,7 +39,8 @@ CGO_ENABLED ?= 1 # only used to compile server
HAS_GO = $(shell hash go > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
ifeq ($(HAS_GO),GO)
XGO_VERSION ?= go-1.20.x
# renovate: datasource=docker depName=docker.io/techknowlogick/xgo
XGO_VERSION ?= go-1.23.x
CGO_CFLAGS ?= $(shell go env CGO_CFLAGS)
endif
CGO_CFLAGS ?=
@ -108,15 +109,15 @@ clean: ## Clean build artifacts
clean-all: clean ## Clean all artifacts
rm -rf ${DIST_DIR} web/dist docs/build docs/node_modules web/node_modules
# delete generated
rm -rf docs/docs/40-cli.md docs/swagger.json
rm -rf docs/docs/40-cli.md docs/openapi.json
.PHONY: generate
generate: install-tools generate-swagger ## Run all code generations
generate: install-tools generate-openapi ## Run all code generations
CGO_ENABLED=0 go generate ./...
generate-swagger: install-tools ## Run swagger code generation
swag init -g server/api/ -g cmd/server/swagger.go --outputTypes go -output cmd/server/docs
CGO_ENABLED=0 go generate cmd/server/swagger.go
generate-openapi: install-tools ## Run openapi code generation and format it
go run github.com/swaggo/swag/cmd/swag fmt
CGO_ENABLED=0 go generate cmd/server/openapi.go
generate-license-header: install-tools
addlicense -c "Woodpecker Authors" -ignore "vendor/**" **/*.go
@ -133,9 +134,6 @@ install-tools: ## Install development tools
hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install mvdan.cc/gofumpt@latest; \
fi ; \
hash swag > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install github.com/swaggo/swag/cmd/swag@latest; \
fi ; \
hash addlicense > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install github.com/google/addlicense@latest; \
fi ; \
@ -163,20 +161,20 @@ lint-ui: ui-dependencies ## Lint UI code
(cd web/; pnpm lint --quiet)
test-agent: ## Test agent code
go test -race -cover -coverprofile agent-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/cmd/agent go.woodpecker-ci.org/woodpecker/v2/agent/...
go test -race -cover -coverprofile agent-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v3/cmd/agent go.woodpecker-ci.org/woodpecker/v3/agent/...
test-server: ## Test server code
go test -race -cover -coverprofile server-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/cmd/server $(shell go list go.woodpecker-ci.org/woodpecker/v2/server/... | grep -v '/store')
go test -race -cover -coverprofile server-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v3/cmd/server $(shell go list go.woodpecker-ci.org/woodpecker/v3/server/... | grep -v '/store')
test-cli: ## Test cli code
go test -race -cover -coverprofile cli-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/cmd/cli go.woodpecker-ci.org/woodpecker/v2/cli/...
go test -race -cover -coverprofile cli-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v3/cmd/cli go.woodpecker-ci.org/woodpecker/v3/cli/...
test-server-datastore: ## Test server datastore
go test -timeout 300s -tags 'test $(TAGS)' -run TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/...
go test -race -timeout 100s -tags 'test $(TAGS)' -skip TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/...
go test -timeout 300s -tags 'test $(TAGS)' -run TestMigrate go.woodpecker-ci.org/woodpecker/v3/server/store/...
go test -race -timeout 100s -tags 'test $(TAGS)' -skip TestMigrate go.woodpecker-ci.org/woodpecker/v3/server/store/...
test-server-datastore-coverage: ## Test server datastore with coverage report
go test -race -cover -coverprofile datastore-coverage.out -timeout 300s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/server/store/...
go test -race -cover -coverprofile datastore-coverage.out -timeout 300s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v3/server/store/...
test-ui: ui-dependencies ## Test UI code
(cd web/; pnpm run lint)
@ -195,14 +193,14 @@ test: test-agent test-server test-server-datastore test-cli test-lib ## Run all
build-ui: ## Build UI
(cd web/; pnpm install --frozen-lockfile; pnpm build)
build-server: build-ui generate-swagger ## Build server
CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-server${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/server
build-server: build-ui generate-openapi ## Build server
CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-server${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v3/cmd/server
build-agent: ## Build agent
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-agent${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/agent
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-agent${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v3/cmd/agent
build-cli: ## Build cli
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-cli${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/cli
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-cli${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v3/cmd/cli
build-tarball: ## Build tar archive
mkdir -p ${DIST_DIR} && tar chzvf ${DIST_DIR}/woodpecker-src.tar.gz \
@ -235,54 +233,75 @@ release-server-xgo: check-xgo ## Create server binaries for release using xgo
@echo "arch orgi:$(TARGETARCH)"
@echo "arch (xgo):$(TARGETARCH_XGO)"
@echo "arch (buildx):$(TARGETARCH_BUILDX)"
# build via xgo
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX) -tags 'netgo osusergo grpcnotrace $(TAGS)' -ldflags '-linkmode external $(LDFLAGS)' -targets '$(TARGETOS)/$(TARGETARCH_XGO)' -out woodpecker-server -pkg cmd/server .
@if [ "$${XGO_IN_XGO:-0}" -eq "1" ]; then echo "inside xgo image"; \
# move binary into subfolder depending on target os and arch
@if [ "$${XGO_IN_XGO:-0}" -eq "1" ]; then \
echo "inside xgo image"; \
mkdir -p ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX); \
mv -vf /build/woodpecker-server* ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \
else echo "outside xgo image"; \
else \
echo "outside xgo image"; \
[ -f "${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX)" ] && rm -v ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \
mv -v ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_XGO)/woodpecker-server* ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \
fi
@[ "$${TARGZ:-0}" -eq "1" ] && tar -cvzf ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz -C ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX) woodpecker-server$(BIN_SUFFIX) || echo "skip creating '${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz'"
# if enabled package it in an archive
@if [ "$${ARCHIVE_IT:-0}" -eq "1" ]; then \
if [ "$(BIN_SUFFIX)" = ".exe" ]; then \
rm -f ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).zip; \
zip -j ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).zip ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server.exe; \
else \
tar -cvzf ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz -C ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX) woodpecker-server$(BIN_SUFFIX); \
fi; \
else \
echo "skip creating '${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz'"; \
fi
release-server: ## Create server binaries for release
# compile
GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=${CGO_ENABLED} go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH)/woodpecker-server$(BIN_SUFFIX) go.woodpecker-ci.org/woodpecker/v2/cmd/server
GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=${CGO_ENABLED} go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH)/woodpecker-server$(BIN_SUFFIX) go.woodpecker-ci.org/woodpecker/v3/cmd/server
# tar binary files
tar -cvzf ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH).tar.gz -C ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH) woodpecker-server$(BIN_SUFFIX)
if [ "$(BIN_SUFFIX)" == ".exe" ]; then \
zip -j ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH).zip ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH)/woodpecker-server.exe; \
else \
tar -cvzf ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH).tar.gz -C ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH) woodpecker-server$(BIN_SUFFIX); \
fi
release-agent: ## Create agent binaries for release
# compile
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/windows_amd64/woodpecker-agent.exe go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent
GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/windows_amd64/woodpecker-agent.exe go.woodpecker-ci.org/woodpecker/v3/cmd/agent
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent
# tar binary files
tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_amd64.tar.gz -C ${DIST_DIR}/agent/linux_amd64 woodpecker-agent
tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_arm64.tar.gz -C ${DIST_DIR}/agent/linux_arm64 woodpecker-agent
tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_arm.tar.gz -C ${DIST_DIR}/agent/linux_arm woodpecker-agent
tar -cvzf ${DIST_DIR}/woodpecker-agent_windows_amd64.tar.gz -C ${DIST_DIR}/agent/windows_amd64 woodpecker-agent.exe
tar -cvzf ${DIST_DIR}/woodpecker-agent_darwin_amd64.tar.gz -C ${DIST_DIR}/agent/darwin_amd64 woodpecker-agent
tar -cvzf ${DIST_DIR}/woodpecker-agent_darwin_arm64.tar.gz -C ${DIST_DIR}/agent/darwin_arm64 woodpecker-agent
# zip binary files
rm -f ${DIST_DIR}/woodpecker-agent_windows_amd64.zip
zip -j ${DIST_DIR}/woodpecker-agent_windows_amd64.zip ${DIST_DIR}/agent/windows_amd64/woodpecker-agent.exe
release-cli: ## Create cli binaries for release
# compile
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/windows_amd64/woodpecker-cli.exe go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli
GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/windows_amd64/woodpecker-cli.exe go.woodpecker-ci.org/woodpecker/v3/cmd/cli
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli
# tar binary files
tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_amd64.tar.gz -C ${DIST_DIR}/cli/linux_amd64 woodpecker-cli
tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_arm64.tar.gz -C ${DIST_DIR}/cli/linux_arm64 woodpecker-cli
tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_arm.tar.gz -C ${DIST_DIR}/cli/linux_arm woodpecker-cli
tar -cvzf ${DIST_DIR}/woodpecker-cli_windows_amd64.tar.gz -C ${DIST_DIR}/cli/windows_amd64 woodpecker-cli.exe
tar -cvzf ${DIST_DIR}/woodpecker-cli_darwin_amd64.tar.gz -C ${DIST_DIR}/cli/darwin_amd64 woodpecker-cli
tar -cvzf ${DIST_DIR}/woodpecker-cli_darwin_arm64.tar.gz -C ${DIST_DIR}/cli/darwin_arm64 woodpecker-cli
# zip binary files
rm -f ${DIST_DIR}/woodpecker-cli_windows_amd64.zip
zip -j ${DIST_DIR}/woodpecker-cli_windows_amd64.zip ${DIST_DIR}/cli/windows_amd64/woodpecker-cli.exe
release-checksums: ## Create checksums for all release files
# generate shas for tar files
@ -321,6 +340,6 @@ spellcheck:
.PHONY: docs
docs: ## Generate docs (currently only for the cli)
CGO_ENABLED=0 go generate cmd/cli/app.go
CGO_ENABLED=0 go generate cmd/server/swagger.go
CGO_ENABLED=0 go generate cmd/server/openapi.go
endif

View file

@ -19,11 +19,11 @@
<a href="https://matrix.to/#/#woodpecker:matrix.org" title="Join the Matrix space at https://matrix.to/#/#woodpecker:matrix.org">
<img src="https://img.shields.io/matrix/woodpecker:matrix.org?label=matrix" alt="Matrix space">
</a>
<a href="https://goreportcard.com/report/go.woodpecker-ci.org/woodpecker/v2" title="Go Report Card">
<img src="https://goreportcard.com/badge/go.woodpecker-ci.org/woodpecker/v2" alt="Go Report Card">
<a href="https://goreportcard.com/report/go.woodpecker-ci.org/woodpecker/v3" title="Go Report Card">
<img src="https://goreportcard.com/badge/go.woodpecker-ci.org/woodpecker/v3" alt="Go Report Card">
</a>
<a href="https://pkg.go.dev/go.woodpecker-ci.org/woodpecker/v2" title="go reference">
<img src="https://pkg.go.dev/badge/go.woodpecker-ci.org/woodpecker/v2" alt="go reference">
<a href="https://pkg.go.dev/go.woodpecker-ci.org/woodpecker/v3" title="go reference">
<img src="https://pkg.go.dev/badge/go.woodpecker-ci.org/woodpecker/v3" alt="go reference">
</a>
<a href="https://github.com/woodpecker-ci/woodpecker/releases/latest" title="GitHub release">
<img src="https://img.shields.io/github/v/release/woodpecker-ci/woodpecker?sort=semver" alt="GitHub release">
@ -43,55 +43,48 @@
</p>
<br/>
Woodpecker is a simple yet powerful CI/CD engine with great extensibility.
Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.
![woodpecker](docs/docs/woodpecker.png)
![woodpecker](docs/woodpecker.png)
## 🫶 Support
## Installation & Resources
Please consider donating and become a backer. 🙏 [[Become a backer](https://opencollective.com/woodpecker-ci#category-CONTRIBUTE)]
Woodpecker can be installed in various ways (see the [Installation Instructions](https://woodpecker-ci.org/docs/administration/getting-started)) and runs with SQLite as database by default.
It requires around 100 MB of RAM (Server) and 30 MB (Agent) at runtime in idle mode.
## Support
You can support the project by becoming a backer on [Open Collective](https://opencollective.com/woodpecker-ci#category-CONTRIBUTE) or via [GitHub Sponsors](https://github.com/sponsors/woodpecker-ci).
<a href="https://opencollective.com/woodpecker-ci" target="_blank"><img src="https://opencollective.com/woodpecker-ci/backers.svg?width=890" alt="Open Collective backers"></a>
## 📖 Documentation
## Documentation
<https://woodpecker-ci.org/>
Our documentation can be found at <https://woodpecker-ci.org/docs/intro>.
## ✨ Contribute
## Translation
See [Contributing Guide](https://github.com/woodpecker-ci/.github/blob/main/CONTRIBUTING.md)
We have a self-hosted [Weblate](https://weblate.org/en/) instance at [translate.woodpecker-ci.org](https://translate.woodpecker-ci.org).
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://woodpecker-ci.org/docs/next/development/getting-started#gitpod)
An overview of the current translation state is available at <https://translate.woodpecker-ci.org/projects/woodpecker-ci/#languages>.
## 📣 Translate
## Public Woodpecker Instances
We use an own [Weblate](https://weblate.org/en/) instance at [translate.woodpecker-ci.org](https://translate.woodpecker-ci.org).
Woodpecker is used as the main CI/CD engine at [Codeberg](https://codeberg.org), an alternative Git hosting platform with a focus on privacy and free software development.
<a href="https://translate.woodpecker-ci.org/engage/woodpecker-ci/">
<img src="https://translate.woodpecker-ci.org/widgets/woodpecker-ci/-/ui/multi-blue.svg" alt="Translation status" />
</a>
## Plugins
## 👋 Who uses Woodpecker?
Woodpecker can be extended via plugins.
The [plugin overview website](https://woodpecker-ci.org/plugins) helps browsing available plugins.
It combines both plugins by the Woodpecker core team and community-maintained ones.
Woodpecker is used by [itself](https://ci.woodpecker-ci.org/woodpecker/woodpecker-ci/), multiple well-known companies, organizations like [Codeberg](https://codeberg.org), hobbyists and many others.
## Star History
Leave a [comment](https://github.com/woodpecker-ci/woodpecker/discussions/2149) if you're using it as well.
Also consider using the topic `WoodpeckerCI` in your repository, so others can learn from your config and use the hashtag `#WoodpeckerCI` when talking about the project on social media!
Here are some places where people mention Woodpecker:
- [GitHub](https://github.com/topics/WoodpeckerCI)
- [Codeberg](https://codeberg.org/explore/repos?q=woodpeckerci&topic=1)
- [Twitter](https://twitter.com/search?q=%23WoodpeckerCI&src=typed_query)
- [Fediverse](https://mastodon.social/tags/WoodpeckerCI)
## ✨ Stars over time
[![Stargazers over time](https://starchart.cc/woodpecker-ci/woodpecker.svg)](https://starchart.cc/woodpecker-ci/woodpecker)
[![Star History Chart](https://api.star-history.com/svg?repos=woodpecker-ci/woodpecker&type=Date)](https://star-history.com/#woodpecker-ci/woodpecker&Date)
## License
Woodpecker is Apache 2.0 licensed with the source files in this repository having a header indicating which license they are under and what copyrights apply.
Woodpecker is Apache 2.0 licensed.
The source files have a header indicating which license they are under and what copyrights apply.
Files under the `docs/` folder are licensed under Creative Commons Attribution-ShareAlike 4.0 International Public License.
Everything in `docs/` is licensed under the Creative Commons Attribution-ShareAlike 4.0 International Public License.

View file

@ -20,16 +20,10 @@ import (
"github.com/rs/zerolog"
"go.woodpecker-ci.org/woodpecker/v2/pipeline"
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/log"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
)
const (
// Store not more than 1mb in a log-line as 4mb is the limit of a grpc message
// and log-lines needs to be parsed by the browsers later on.
maxLogLineLength = 1024 * 1024 // 1mb
"go.woodpecker-ci.org/woodpecker/v3/pipeline"
backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/log"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
)
func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, workflow *rpc.Workflow) pipeline.Logger {
@ -38,7 +32,6 @@ func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, w
logger := _logger.With().
Str("image", step.Image).
Str("workflow_id", workflow.ID).
Logger()
uploads.Add(1)
@ -51,7 +44,7 @@ func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, w
logger.Debug().Msg("log stream opened")
logStream := log.NewLineWriter(r.client, step.UUID, secrets...)
if err := log.CopyLineByLine(logStream, rc, maxLogLineLength); err != nil {
if err := log.CopyLineByLine(logStream, rc, pipeline.MaxLogLineLength); err != nil {
logger.Error().Err(err).Msg("copy limited logStream part")
}

View file

@ -20,9 +20,11 @@ import (
"google.golang.org/grpc"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto"
)
const authClientTimeout = time.Second * 5
type AuthClient struct {
client proto.WoodpeckerAuthClient
conn *grpc.ClientConn
@ -39,8 +41,8 @@ func NewAuthGrpcClient(conn *grpc.ClientConn, agentToken string, agentID int64)
return client
}
func (c *AuthClient) Auth() (string, int64, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) //nolint:mnd
func (c *AuthClient) Auth(ctx context.Context) (string, int64, error) {
ctx, cancel := context.WithTimeout(ctx, authClientTimeout)
defer cancel()
req := &proto.AuthRequest{

View file

@ -30,15 +30,12 @@ type AuthInterceptor struct {
}
// NewAuthInterceptor returns a new auth interceptor.
func NewAuthInterceptor(
authClient *AuthClient,
refreshDuration time.Duration,
) (*AuthInterceptor, error) {
func NewAuthInterceptor(ctx context.Context, authClient *AuthClient, refreshDuration time.Duration) (*AuthInterceptor, error) {
interceptor := &AuthInterceptor{
authClient: authClient,
}
err := interceptor.scheduleRefreshToken(refreshDuration)
err := interceptor.scheduleRefreshToken(ctx, refreshDuration)
if err != nil {
return nil, err
}
@ -78,21 +75,26 @@ func (interceptor *AuthInterceptor) attachToken(ctx context.Context) context.Con
return metadata.AppendToOutgoingContext(ctx, "token", interceptor.accessToken)
}
func (interceptor *AuthInterceptor) scheduleRefreshToken(refreshDuration time.Duration) error {
err := interceptor.refreshToken()
func (interceptor *AuthInterceptor) scheduleRefreshToken(ctx context.Context, refreshInterval time.Duration) error {
err := interceptor.refreshToken(ctx)
if err != nil {
return err
}
go func() {
wait := refreshDuration
wait := refreshInterval
for {
time.Sleep(wait)
err := interceptor.refreshToken()
if err != nil {
wait = time.Second
} else {
wait = refreshDuration
select {
case <-ctx.Done():
return
case <-time.After(wait):
err := interceptor.refreshToken(ctx)
if err != nil {
wait = time.Second
} else {
wait = refreshInterval
}
}
}
}()
@ -100,8 +102,8 @@ func (interceptor *AuthInterceptor) scheduleRefreshToken(refreshDuration time.Du
return nil
}
func (interceptor *AuthInterceptor) refreshToken() error {
accessToken, _, err := interceptor.authClient.Auth()
func (interceptor *AuthInterceptor) refreshToken(ctx context.Context) error {
accessToken, _, err := interceptor.authClient.Auth(ctx)
if err != nil {
return err
}

View file

@ -17,44 +17,57 @@ package rpc
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/cenkalti/backoff/v5"
"github.com/rs/zerolog/log"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
grpcproto "google.golang.org/protobuf/proto"
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto"
backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto"
)
// Set grpc version on compile time to compare against server version response.
const ClientGrpcVersion int32 = proto.Version
const (
// Set grpc version on compile time to compare against server version response.
ClientGrpcVersion int32 = proto.Version
// Maximum size of an outgoing log message.
// Picked to prevent it from going over GRPC size limit (4 MiB) with a large safety margin.
maxLogBatchSize int = 1 * 1024 * 1024
// Maximum amount of time between sending consecutive batched log messages.
// Controls the delay between the CI job generating a log record, and web users receiving it.
maxLogFlushPeriod time.Duration = time.Second
)
type client struct {
client proto.WoodpeckerClient
conn *grpc.ClientConn
logs chan *proto.LogEntry
}
// NewGrpcClient returns a new grpc Client.
func NewGrpcClient(conn *grpc.ClientConn) rpc.Peer {
func NewGrpcClient(ctx context.Context, conn *grpc.ClientConn) rpc.Peer {
client := new(client)
client.client = proto.NewWoodpeckerClient(conn)
client.conn = conn
client.logs = make(chan *proto.LogEntry, 10) // max memory use: 10 lines * 1 MiB
go client.processLogs(ctx)
return client
}
func (c *client) Close() error {
close(c.logs)
return c.conn.Close()
}
func (c *client) newBackOff() backoff.BackOff {
b := backoff.NewExponentialBackOff()
b.MaxElapsedTime = 0
b.MaxInterval = 10 * time.Second //nolint:mnd
b.InitialInterval = 10 * time.Millisecond //nolint:mnd
return b
@ -73,13 +86,13 @@ func (c *client) Version(ctx context.Context) (*rpc.Version, error) {
}
// Next returns the next workflow in the queue.
func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error) {
func (c *client) Next(ctx context.Context, filter rpc.Filter) (*rpc.Workflow, error) {
var res *proto.NextResponse
var err error
retry := c.newBackOff()
req := new(proto.NextRequest)
req.Filter = new(proto.Filter)
req.Filter.Labels = f.Labels
req.Filter.Labels = filter.Labels
for {
res, err = c.client.Next(ctx, req)
if err == nil {
@ -90,8 +103,10 @@ func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error)
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: next(): context canceled")
return nil, nil
}
log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err))
return nil, err
case
codes.Aborted,
@ -105,10 +120,11 @@ func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error)
// https://github.com/woodpecker-ci/woodpecker/issues/717#issuecomment-1049365104
log.Trace().Err(err).Msg("grpc: to many keepalive pings without sending data")
} else {
log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err))
log.Warn().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err))
}
default:
return nil, fmt.Errorf("grpc error: next(): code: %v: %w", status.Code(err), err)
log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err))
return nil, err
}
select {
@ -143,9 +159,15 @@ func (c *client) Wait(ctx context.Context, workflowID string) (err error) {
break
}
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: wait(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -153,7 +175,9 @@ func (c *client) Wait(ctx context.Context, workflowID string) (err error) {
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
return err
}
@ -184,6 +208,14 @@ func (c *client) Init(ctx context.Context, workflowID string, state rpc.Workflow
log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: init(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -191,7 +223,9 @@ func (c *client) Init(ctx context.Context, workflowID string, state rpc.Workflow
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
return err
}
@ -222,6 +256,14 @@ func (c *client) Done(ctx context.Context, workflowID string, state rpc.Workflow
log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: done(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -229,7 +271,9 @@ func (c *client) Done(ctx context.Context, workflowID string, state rpc.Workflow
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
return err
}
@ -256,6 +300,14 @@ func (c *client) Extend(ctx context.Context, workflowID string) (err error) {
log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: extend(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -263,7 +315,9 @@ func (c *client) Extend(ctx context.Context, workflowID string) (err error) {
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
return err
}
@ -297,6 +351,14 @@ func (c *client) Update(ctx context.Context, workflowID string, state rpc.StepSt
log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: update(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -304,7 +366,9 @@ func (c *client) Update(ctx context.Context, workflowID string, state rpc.StepSt
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
return err
}
@ -317,25 +381,82 @@ func (c *client) Update(ctx context.Context, workflowID string, state rpc.StepSt
return nil
}
// Log writes the step log entry.
func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) {
retry := c.newBackOff()
req := new(proto.LogRequest)
req.LogEntry = new(proto.LogEntry)
req.LogEntry.StepUuid = logEntry.StepUUID
req.LogEntry.Data = logEntry.Data
req.LogEntry.Line = int32(logEntry.Line)
req.LogEntry.Time = logEntry.Time
req.LogEntry.Type = int32(logEntry.Type)
// EnqueueLog queues the log entry to be written in a batch later.
func (c *client) EnqueueLog(logEntry *rpc.LogEntry) {
c.logs <- &proto.LogEntry{
StepUuid: logEntry.StepUUID,
Data: logEntry.Data,
Line: int32(logEntry.Line),
Time: logEntry.Time,
Type: int32(logEntry.Type),
}
}
func (c *client) processLogs(ctx context.Context) {
var entries []*proto.LogEntry
var bytes int
send := func() {
if len(entries) == 0 {
return
}
log.Debug().
Int("entries", len(entries)).
Int("bytes", bytes).
Msg("log drain: sending queued logs")
if err := c.sendLogs(ctx, entries); err != nil {
log.Error().Err(err).Msg("log drain: could not send logs to server")
}
// even if send failed, we don't have infinite memory; retry has already been used
entries = entries[:0]
bytes = 0
}
// ctx.Done() is covered by the log channel being closed
for {
_, err = c.client.Log(ctx, req)
select {
case entry, ok := <-c.logs:
if !ok {
log.Info().Msg("log drain: channel closed")
send()
return
}
entries = append(entries, entry)
bytes += grpcproto.Size(entry) // cspell:words grpcproto
if bytes >= maxLogBatchSize {
send()
}
case <-time.After(maxLogFlushPeriod):
send()
}
}
}
func (c *client) sendLogs(ctx context.Context, entries []*proto.LogEntry) error {
req := &proto.LogRequest{LogEntries: entries}
retry := c.newBackOff()
for {
_, err := c.client.Log(ctx, req)
if err == nil {
break
}
log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: log(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -343,7 +464,9 @@ func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) {
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
return err
}
@ -356,12 +479,15 @@ func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) {
return nil
}
func (c *client) RegisterAgent(ctx context.Context, platform, backend, version string, capacity int) (int64, error) {
func (c *client) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) {
req := new(proto.RegisterAgentRequest)
req.Platform = platform
req.Backend = backend
req.Version = version
req.Capacity = int32(capacity)
req.Info = &proto.AgentInfo{
Platform: info.Platform,
Backend: info.Backend,
Version: info.Version,
Capacity: int32(info.Capacity),
CustomLabels: info.CustomLabels,
}
res, err := c.client.RegisterAgent(ctx, req)
return res.GetAgentId(), err
@ -383,6 +509,14 @@ func (c *client) ReportHealth(ctx context.Context) (err error) {
return nil
}
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: report_health(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -390,7 +524,9 @@ func (c *client) ReportHealth(ctx context.Context) (err error) {
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err))
return err
}

View file

@ -25,10 +25,11 @@ import (
"github.com/rs/zerolog/log"
"google.golang.org/grpc/metadata"
"go.woodpecker-ci.org/woodpecker/v2/pipeline"
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/v2/shared/utils"
"go.woodpecker-ci.org/woodpecker/v3/pipeline"
backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/v3/shared/constant"
"go.woodpecker-ci.org/woodpecker/v3/shared/utils"
)
type Runner struct {
@ -49,7 +50,7 @@ func NewRunner(workEngine rpc.Peer, f rpc.Filter, h string, state *State, backen
}
}
func (r *Runner) Run(runnerCtx context.Context) error { //nolint:contextcheck
func (r *Runner) Run(runnerCtx, shutdownCtx context.Context) error { //nolint:contextcheck
log.Debug().Msg("request next execution")
meta, _ := metadata.FromOutgoingContext(runnerCtx)
@ -118,7 +119,7 @@ func (r *Runner) Run(runnerCtx context.Context) error { //nolint:contextcheck
logger.Debug().Msg("pipeline done")
return
case <-time.After(time.Minute):
case <-time.After(constant.TaskTimeout / 3):
logger.Debug().Msg("pipeline lease renewed")
if err := r.client.Extend(workflowCtx, workflow.ID); err != nil {
log.Error().Err(err).Msg("extending pipeline deadline failed")
@ -176,7 +177,11 @@ func (r *Runner) Run(runnerCtx context.Context) error { //nolint:contextcheck
Str("error", state.Error).
Msg("updating workflow status")
if err := r.client.Done(runnerCtx, workflow.ID, state); err != nil {
doneCtx := runnerCtx
if doneCtx.Err() != nil {
doneCtx = shutdownCtx
}
if err := r.client.Done(doneCtx, workflow.ID, state); err != nil {
logger.Error().Err(err).Msg("updating workflow status failed")
} else {
logger.Debug().Msg("updating workflow status complete")

View file

@ -23,8 +23,8 @@ import (
"github.com/rs/zerolog"
"go.woodpecker-ci.org/woodpecker/v2/pipeline"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/v3/pipeline"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc"
)
func (r *Runner) createTracer(ctxMeta context.Context, uploads *sync.WaitGroup, logger zerolog.Logger, workflow *rpc.Workflow) pipeline.TraceFunc {
@ -74,21 +74,12 @@ func (r *Runner) createTracer(ctxMeta context.Context, uploads *sync.WaitGroup,
// TODO: find better way to update this state and move it to pipeline to have the same env in cli-exec
state.Pipeline.Step.Environment["CI_MACHINE"] = r.hostname
state.Pipeline.Step.Environment["CI_PIPELINE_STATUS"] = "success"
state.Pipeline.Step.Environment["CI_PIPELINE_STARTED"] = strconv.FormatInt(state.Pipeline.Started, 10)
state.Pipeline.Step.Environment["CI_PIPELINE_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
state.Pipeline.Step.Environment["CI_STEP_STATUS"] = "success"
state.Pipeline.Step.Environment["CI_STEP_STARTED"] = strconv.FormatInt(state.Pipeline.Started, 10)
state.Pipeline.Step.Environment["CI_STEP_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
state.Pipeline.Step.Environment["CI_SYSTEM_PLATFORM"] = runtime.GOOS + "/" + runtime.GOARCH
if state.Pipeline.Error != nil {
state.Pipeline.Step.Environment["CI_PIPELINE_STATUS"] = "failure"
state.Pipeline.Step.Environment["CI_STEP_STATUS"] = "failure"
}
return nil
}
}

View file

@ -15,16 +15,22 @@
package admin
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/admin/registry"
"go.woodpecker-ci.org/woodpecker/v3/cli/admin/loglevel"
"go.woodpecker-ci.org/woodpecker/v3/cli/admin/registry"
"go.woodpecker-ci.org/woodpecker/v3/cli/admin/secret"
"go.woodpecker-ci.org/woodpecker/v3/cli/admin/user"
)
// Command exports the admin command set.
var Command = &cli.Command{
Name: "admin",
Usage: "administer server settings",
Subcommands: []*cli.Command{
Usage: "manage server settings",
Commands: []*cli.Command{
loglevel.Command,
registry.Command,
secret.Command,
user.Command,
},
}

View file

@ -15,24 +15,26 @@
package loglevel
import (
"context"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
// Command exports the log-level command used to change the servers log-level.
var Command = &cli.Command{
Name: "log-level",
ArgsUsage: "[level]",
Usage: "get the logging level of the server, or set it with [level]",
Usage: "retrieve log level from server, or set it with [level]",
Action: logLevel,
}
func logLevel(c *cli.Context) error {
client, err := internal.NewClient(c)
func logLevel(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -57,6 +59,6 @@ func logLevel(c *cli.Context) error {
}
}
log.Info().Msgf("logging level: %s", ll.Level)
log.Info().Msgf("log level: %s", ll.Level)
return nil
}

View file

@ -15,18 +15,18 @@
package registry
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Command exports the registry command set.
var Command = &cli.Command{
Name: "registry",
Usage: "manage global registries",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
registryShowCmd,
registryUpdateCmd,
},
}

View file

@ -15,18 +15,19 @@
package registry
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
Usage: "add a registry",
Action: registryCreate,
Flags: []cli.Flag{
&cli.StringFlag{
@ -45,14 +46,14 @@ var registryCreateCmd = &cli.Command{
},
}
func registryCreate(c *cli.Context) error {
func registryCreate(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
username = c.String("username")
password = c.String("password")
)
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,13 +15,15 @@
package registry
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var registryListCmd = &cli.Command{
@ -33,15 +35,17 @@ var registryListCmd = &cli.Command{
},
}
func registryList(c *cli.Context) error {
func registryList(ctx context.Context, c *cli.Command) error {
format := c.String("format") + "\n"
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
list, err := client.GlobalRegistryList()
opt := woodpecker.RegistryListOptions{}
list, err := client.GlobalRegistryList(opt)
if err != nil {
return err
}

View file

@ -15,9 +15,11 @@
package registry
import (
"github.com/urfave/cli/v2"
"context"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var registryDeleteCmd = &cli.Command{
@ -33,10 +35,10 @@ var registryDeleteCmd = &cli.Command{
},
}
func registryDelete(c *cli.Context) error {
func registryDelete(ctx context.Context, c *cli.Command) error {
hostname := c.String("hostname")
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,14 +15,15 @@
package registry
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var registryUpdateCmd = &cli.Command{
@ -47,14 +48,14 @@ var registryUpdateCmd = &cli.Command{
},
}
func registryUpdate(c *cli.Context) error {
func registryUpdate(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
username = c.String("username")
password = c.String("password")
)
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,19 +15,20 @@
package registry
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
Action: registryInfo,
var registryShowCmd = &cli.Command{
Name: "show",
Usage: "show registry information",
Action: registryShow,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "hostname",
@ -38,13 +39,13 @@ var registryInfoCmd = &cli.Command{
},
}
func registryInfo(c *cli.Context) error {
func registryShow(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
format = c.String("format") + "\n"
)
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -1,10 +1,10 @@
// Copyright 2021 Woodpecker Authors
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@ -12,16 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package migration
package secret
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
"github.com/urfave/cli/v3"
)
var alterTableReposDropCounter = xormigrate.Migration{
ID: "alter-table-drop-counter",
MigrateSession: func(sess *xorm.Session) error {
return dropTableColumns(sess, "repos", "repo_counter")
// Command exports the secret command.
var Command = &cli.Command{
Name: "secret",
Usage: "manage global secrets",
Commands: []*cli.Command{
secretCreateCmd,
secretDeleteCmd,
secretListCmd,
secretShowCmd,
secretUpdateCmd,
},
}

View file

@ -0,0 +1,82 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var secretCreateCmd = &cli.Command{
Name: "add",
Usage: "add a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretCreate,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "value",
Usage: "secret value",
},
&cli.StringSliceFlag{
Name: "event",
Usage: "secret limited to these events",
},
&cli.StringSliceFlag{
Name: "image",
Usage: "secret limited to these images",
},
},
}
func secretCreate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
secret := &woodpecker.Secret{
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("image"),
Events: c.StringSlice("event"),
}
if len(secret.Events) == 0 {
secret.Events = defaultSecretEvents
}
if strings.HasPrefix(secret.Value, "@") {
path := strings.TrimPrefix(secret.Value, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
secret.Value = string(out)
}
_, err = client.GlobalSecretCreate(secret)
return err
}
var defaultSecretEvents = []string{
woodpecker.EventPush,
woodpecker.EventTag,
woodpecker.EventRelease,
woodpecker.EventDeploy,
}

View file

@ -15,15 +15,16 @@
package secret
import (
"context"
"html/template"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var secretListCmd = &cli.Command{
@ -32,48 +33,25 @@ var secretListCmd = &cli.Command{
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretList,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
common.FormatFlag(tmplSecretList, true),
},
}
func secretList(c *cli.Context) error {
func secretList(ctx context.Context, c *cli.Command) error {
format := c.String("format") + "\n"
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
global, orgID, repoID, err := parseTargetArgs(client, c)
opt := woodpecker.SecretListOptions{}
list, err := client.GlobalSecretList(opt)
if err != nil {
return err
}
var list []*woodpecker.Secret
switch {
case global:
list, err = client.GlobalSecretList()
if err != nil {
return err
}
case orgID != -1:
list, err = client.OrgSecretList(orgID)
if err != nil {
return err
}
default:
list, err = client.SecretList(repoID)
if err != nil {
return err
}
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err

View file

@ -0,0 +1,47 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var secretDeleteCmd = &cli.Command{
Name: "rm",
Usage: "remove a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretDelete,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
},
}
func secretDelete(ctx context.Context, c *cli.Command) error {
secretName := c.String("name")
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
return client.GlobalSecretDelete(secretName)
}

View file

@ -0,0 +1,76 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var secretUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretUpdate,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
&cli.StringFlag{
Name: "value",
Usage: "secret value",
},
&cli.StringSliceFlag{
Name: "event",
Usage: "secret limited to these events",
},
&cli.StringSliceFlag{
Name: "image",
Usage: "secret limited to these images",
},
},
}
func secretUpdate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
secret := &woodpecker.Secret{
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("image"),
Events: c.StringSlice("event"),
}
if strings.HasPrefix(secret.Value, "@") {
path := strings.TrimPrefix(secret.Value, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
secret.Value = string(out)
}
_, err = client.GlobalSecretUpdate(secret)
return err
}

View file

@ -0,0 +1,68 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"fmt"
"html/template"
"os"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var secretShowCmd = &cli.Command{
Name: "show",
Usage: "show secret information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretShow,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
common.FormatFlag(tmplSecretList, true),
},
}
func secretShow(ctx context.Context, c *cli.Command) error {
var (
secretName = c.String("name")
format = c.String("format") + "\n"
)
if secretName == "" {
return fmt.Errorf("secret name is missing")
}
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
secret, err := client.GlobalSecret(secretName)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, secret)
}

View file

@ -15,17 +15,17 @@
package user
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Command exports the user command set.
var Command = &cli.Command{
Name: "user",
Usage: "manage users",
Subcommands: []*cli.Command{
userListCmd,
userInfoCmd,
Commands: []*cli.Command{
userAddCmd,
userListCmd,
userRemoveCmd,
userShowCmd,
},
}

View file

@ -15,25 +15,26 @@
package user
import (
"context"
"fmt"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var userAddCmd = &cli.Command{
Name: "add",
Usage: "adds a user",
Usage: "add a user",
ArgsUsage: "<username>",
Action: userAdd,
}
func userAdd(c *cli.Context) error {
func userAdd(ctx context.Context, c *cli.Command) error {
login := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,13 +15,15 @@
package user
import (
"context"
"os"
"text/template"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var userListCmd = &cli.Command{
@ -32,13 +34,15 @@ var userListCmd = &cli.Command{
Flags: []cli.Flag{common.FormatFlag(tmplUserList)},
}
func userList(c *cli.Context) error {
client, err := internal.NewClient(c)
func userList(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
users, err := client.UserList()
opt := woodpecker.UserListOptions{}
users, err := client.UserList(opt)
if err != nil || len(users) == 0 {
return err
}

View file

@ -15,11 +15,12 @@
package user
import (
"context"
"fmt"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var userRemoveCmd = &cli.Command{
@ -29,10 +30,10 @@ var userRemoveCmd = &cli.Command{
Action: userRemove,
}
func userRemove(c *cli.Context) error {
func userRemove(ctx context.Context, c *cli.Command) error {
login := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,26 +15,27 @@
package user
import (
"context"
"fmt"
"os"
"text/template"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var userInfoCmd = &cli.Command{
Name: "info",
Usage: "show user details",
var userShowCmd = &cli.Command{
Name: "show",
Usage: "show user information",
ArgsUsage: "<username>",
Action: userInfo,
Action: userShow,
Flags: []cli.Flag{common.FormatFlag(tmplUserInfo)},
}
func userInfo(c *cli.Context) error {
client, err := internal.NewClient(c)
func userShow(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,52 +15,49 @@
package common
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/shared/logger"
"go.woodpecker-ci.org/woodpecker/v3/shared/logger"
)
var GlobalFlags = append([]cli.Flag{
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_CONFIG"},
Sources: cli.EnvVars("WOODPECKER_CONFIG"),
Name: "config",
Aliases: []string{"c"},
Usage: "path to config file",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_SERVER"},
Sources: cli.EnvVars("WOODPECKER_SERVER"),
Name: "server",
Aliases: []string{"s"},
Usage: "server address",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_TOKEN"},
Sources: cli.EnvVars("WOODPECKER_TOKEN"),
Name: "token",
Aliases: []string{"t"},
Usage: "server auth token",
},
&cli.BoolFlag{
EnvVars: []string{"WOODPECKER_DISABLE_UPDATE_CHECK"},
Sources: cli.EnvVars("WOODPECKER_DISABLE_UPDATE_CHECK"),
Name: "disable-update-check",
Usage: "disable update check",
},
&cli.BoolFlag{
EnvVars: []string{"WOODPECKER_SKIP_VERIFY"},
Sources: cli.EnvVars("WOODPECKER_SKIP_VERIFY"),
Name: "skip-verify",
Usage: "skip ssl verification",
Hidden: true,
},
&cli.StringFlag{
EnvVars: []string{"SOCKS_PROXY"},
Sources: cli.EnvVars("SOCKS_PROXY"),
Name: "socks-proxy",
Usage: "socks proxy address",
Hidden: true,
},
&cli.BoolFlag{
EnvVars: []string{"SOCKS_PROXY_OFF"},
Sources: cli.EnvVars("SOCKS_PROXY_OFF"),
Name: "socks-proxy-off",
Usage: "socks proxy ignored",
Hidden: true,
},
}, logger.GlobalLoggerFlags...)

View file

@ -6,10 +6,10 @@ import (
"time"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal/config"
"go.woodpecker-ci.org/woodpecker/v2/cli/update"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal/config"
"go.woodpecker-ci.org/woodpecker/v3/cli/update"
)
var (
@ -17,12 +17,12 @@ var (
cancelWaitForUpdate context.CancelCauseFunc
)
func Before(c *cli.Context) error {
if err := setupGlobalLogger(c); err != nil {
return err
func Before(ctx context.Context, c *cli.Command) (context.Context, error) {
if err := setupGlobalLogger(ctx, c); err != nil {
return ctx, err
}
go func() {
go func(context.Context) {
if c.Bool("disable-update-check") {
return
}
@ -35,31 +35,31 @@ func Before(c *cli.Context) error {
waitForUpdateCheck, cancelWaitForUpdate = context.WithCancelCause(context.Background())
defer cancelWaitForUpdate(errors.New("update check finished"))
log.Debug().Msg("Checking for updates ...")
log.Debug().Msg("checking for updates ...")
newVersion, err := update.CheckForUpdate(waitForUpdateCheck, false)
newVersion, err := update.CheckForUpdate(waitForUpdateCheck, false) //nolint:contextcheck
if err != nil {
log.Error().Err(err).Msgf("Failed to check for updates")
log.Error().Err(err).Msgf("failed to check for updates")
return
}
if newVersion != nil {
log.Warn().Msgf("A new version of woodpecker-cli is available: %s. Update by running: %s update", newVersion.Version, c.App.Name)
log.Warn().Msgf("new version of woodpecker-cli is available: %s, update with: %s update", newVersion.Version, c.Root().Name)
} else {
log.Debug().Msgf("No update required")
log.Debug().Msgf("no update required")
}
}()
}(ctx)
return config.Load(c)
return ctx, config.Load(ctx, c)
}
func After(_ *cli.Context) error {
func After(_ context.Context, _ *cli.Command) error {
if waitForUpdateCheck != nil {
select {
case <-waitForUpdateCheck.Done():
// When the actual command already finished, we still wait 500ms for the update check to finish
case <-time.After(time.Millisecond * 500):
log.Debug().Msg("Update check stopped due to timeout")
log.Debug().Msg("update check stopped due to timeout")
cancelWaitForUpdate(errors.New("update check timeout"))
}
}

View file

@ -15,13 +15,14 @@
package common
import (
"context"
"fmt"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/shared/constant"
"go.woodpecker-ci.org/woodpecker/v3/shared/constant"
)
func DetectPipelineConfig() (isDir bool, config string, _ error) {
@ -37,16 +38,16 @@ func DetectPipelineConfig() (isDir bool, config string, _ error) {
return false, "", fmt.Errorf("could not detect pipeline config")
}
func RunPipelineFunc(c *cli.Context, fileFunc, dirFunc func(*cli.Context, string) error) error {
func RunPipelineFunc(ctx context.Context, c *cli.Command, fileFunc, dirFunc func(context.Context, *cli.Command, string) error) error {
if c.Args().Len() == 0 {
isDir, path, err := DetectPipelineConfig()
if err != nil {
return err
}
if isDir {
return dirFunc(c, path)
return dirFunc(ctx, c, path)
}
return fileFunc(c, path)
return fileFunc(ctx, c, path)
}
multiArgs := c.Args().Len() > 1
@ -59,11 +60,11 @@ func RunPipelineFunc(c *cli.Context, fileFunc, dirFunc func(*cli.Context, string
fmt.Println("#", fi.Name())
}
if fi.IsDir() {
if err := dirFunc(c, arg); err != nil {
if err := dirFunc(ctx, c, arg); err != nil {
return err
}
} else {
if err := fileFunc(c, arg); err != nil {
if err := fileFunc(ctx, c, arg); err != nil {
return err
}
}

View file

@ -15,11 +15,13 @@
package common
import (
"github.com/urfave/cli/v2"
"context"
"go.woodpecker-ci.org/woodpecker/v2/shared/logger"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/shared/logger"
)
func setupGlobalLogger(c *cli.Context) error {
return logger.SetupGlobalLogger(c, false)
func setupGlobalLogger(ctx context.Context, c *cli.Command) error {
return logger.SetupGlobalLogger(ctx, c, false)
}

View file

@ -17,7 +17,7 @@
package exec
import "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy"
import "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/dummy"
func init() { //nolint:gochecknoinits
backends = append(backends, dummy.New())

View file

@ -25,23 +25,26 @@ import (
"strings"
"github.com/drone/envsubst"
"github.com/oklog/ulid/v2"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/lint"
"go.woodpecker-ci.org/woodpecker/v2/pipeline"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/docker"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local"
backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix"
pipelineLog "go.woodpecker-ci.org/woodpecker/v2/pipeline/log"
"go.woodpecker-ci.org/woodpecker/v2/shared/utils"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/lint"
"go.woodpecker-ci.org/woodpecker/v3/pipeline"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/backend"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/docker"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/kubernetes"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/local"
backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/compiler"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/matrix"
pipelineLog "go.woodpecker-ci.org/woodpecker/v3/pipeline/log"
"go.woodpecker-ci.org/woodpecker/v3/shared/constant"
"go.woodpecker-ci.org/woodpecker/v3/shared/utils"
)
// Command exports the exec command.
@ -59,11 +62,11 @@ var backends = []backend_types.Backend{
local.New(),
}
func run(c *cli.Context) error {
return common.RunPipelineFunc(c, execFile, execDir)
func run(ctx context.Context, c *cli.Command) error {
return common.RunPipelineFunc(ctx, c, execFile, execDir)
}
func execDir(c *cli.Context, dir string) error {
func execDir(ctx context.Context, c *cli.Command, dir string) error {
// TODO: respect pipeline dependency
repoPath := c.String("repo-path")
if repoPath != "" {
@ -74,6 +77,7 @@ func execDir(c *cli.Context, dir string) error {
if runtime.GOOS == "windows" {
repoPath = convertPathForWindows(repoPath)
}
// TODO: respect depends_on and do parallel runs with output to multiple _windows_ e.g. tmux like
return filepath.Walk(dir, func(path string, info os.FileInfo, e error) error {
if e != nil {
return e
@ -82,7 +86,7 @@ func execDir(c *cli.Context, dir string) error {
// check if it is a regular file (not dir)
if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
fmt.Println("#", info.Name())
_ = runExec(c, path, repoPath) // TODO: should we drop errors or store them and report back?
_ = runExec(ctx, c, path, repoPath, false) // TODO: should we drop errors or store them and report back?
fmt.Println("")
return nil
}
@ -91,7 +95,7 @@ func execDir(c *cli.Context, dir string) error {
})
}
func execFile(c *cli.Context, file string) error {
func execFile(ctx context.Context, c *cli.Command, file string) error {
repoPath := c.String("repo-path")
if repoPath != "" {
repoPath, _ = filepath.Abs(repoPath)
@ -101,10 +105,10 @@ func execFile(c *cli.Context, file string) error {
if runtime.GOOS == "windows" {
repoPath = convertPathForWindows(repoPath)
}
return runExec(c, file, repoPath)
return runExec(ctx, c, file, repoPath, true)
}
func runExec(c *cli.Context, file, repoPath string) error {
func runExec(ctx context.Context, c *cli.Command, file, repoPath string, singleExec bool) error {
dat, err := os.ReadFile(file)
if err != nil {
return err
@ -119,7 +123,7 @@ func runExec(c *cli.Context, file, repoPath string) error {
axes = append(axes, matrix.Axis{})
}
for _, axis := range axes {
err := execWithAxis(c, file, repoPath, axis)
err := execWithAxis(ctx, c, file, repoPath, axis, singleExec)
if err != nil {
return err
}
@ -127,8 +131,20 @@ func runExec(c *cli.Context, file, repoPath string) error {
return nil
}
func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error {
metadata := metadataFromContext(c, axis)
func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, axis matrix.Axis, singleExec bool) error {
metadataWorkflow := &metadata.Workflow{}
if !singleExec {
// TODO: proper try to use the engine to generate the same metadata for workflows
// https://github.com/woodpecker-ci/woodpecker/pull/3967
metadataWorkflow.Name = strings.TrimSuffix(strings.TrimSuffix(file, ".yaml"), ".yml")
}
metadata, err := metadataFromContext(ctx, c, axis, metadataWorkflow)
if err != nil {
return fmt.Errorf("could not create metadata: %w", err)
} else if metadata == nil {
return fmt.Errorf("metadata is nil")
}
environ := metadata.Environ()
var secrets []compiler.Secret
for key, val := range metadata.Workflow.Matrix {
@ -166,6 +182,9 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
return err
}
// emulate server behavior https://github.com/woodpecker-ci/woodpecker/blob/eebaa10d104cbc3fa7ce4c0e344b0b7978405135/server/pipeline/stepbuilder/stepBuilder.go#L289-L295
prefix := "wp_" + ulid.Make().String()
// configure volumes for local execution
volumes := c.StringSlice("volumes")
if c.Bool("local") {
@ -180,18 +199,28 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
workspacePath = c.String("workspace-path")
}
volumes = append(volumes, c.String("prefix")+"_default:"+workspaceBase)
volumes = append(volumes, prefix+"_default:"+workspaceBase)
volumes = append(volumes, repoPath+":"+path.Join(workspaceBase, workspacePath))
}
privilegedPlugins := c.StringSlice("plugins-privileged")
// lint the yaml file
err = linter.New(linter.WithTrusted(true)).Lint([]*linter.WorkflowConfig{{
err = linter.New(
linter.WithTrusted(linter.TrustedConfiguration{
Security: c.Bool("repo-trusted-security"),
Network: c.Bool("repo-trusted-network"),
Volumes: c.Bool("repo-trusted-volumes"),
}),
linter.PrivilegedPlugins(privilegedPlugins),
linter.WithTrustedClonePlugins(constant.TrustedClonePlugins),
).Lint([]*linter.WorkflowConfig{{
File: path.Base(file),
RawConfig: confStr,
Workflow: conf,
}})
if err != nil {
str, err := lint.FormatLintError(file, err)
str, err := lint.FormatLintError(file, err, false)
fmt.Print(str)
if err != nil {
return err
@ -201,7 +230,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
// compiles the yaml file
compiled, err := compiler.New(
compiler.WithEscalated(
c.StringSlice("privileged")...,
privilegedPlugins...,
),
compiler.WithVolumes(volumes...),
compiler.WithWorkspace(
@ -211,9 +240,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
compiler.WithNetworks(
c.StringSlice("network")...,
),
compiler.WithPrefix(
c.String("prefix"),
),
compiler.WithPrefix(prefix),
compiler.WithProxy(compiler.ProxyOptions{
NoProxy: c.String("backend-no-proxy"),
HTTPProxy: c.String("backend-http-proxy"),
@ -227,7 +254,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
c.String("netrc-password"),
c.String("netrc-machine"),
),
compiler.WithMetadata(metadata),
compiler.WithMetadata(*metadata),
compiler.WithSecret(secrets...),
compiler.WithEnviron(pipelineEnv),
).Compile(conf)
@ -235,7 +262,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
return err
}
backendCtx := context.WithValue(c.Context, backend_types.CliContext, c)
backendCtx := context.WithValue(ctx, backend_types.CliCommand, c)
backendEngine, err := backend.FindBackend(backendCtx, backends, c.String("backend-engine"))
if err != nil {
return err
@ -245,21 +272,21 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
return err
}
ctx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout"))
pipelineCtx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout"))
defer cancel()
ctx = utils.WithContextSigtermCallback(ctx, func() {
fmt.Println("ctrl+c received, terminating process")
pipelineCtx = utils.WithContextSigtermCallback(pipelineCtx, func() {
fmt.Printf("ctrl+c received, terminating current pipeline '%s'\n", confStr)
})
return pipeline.New(compiled,
pipeline.WithContext(ctx),
pipeline.WithContext(pipelineCtx), //nolint:contextcheck
pipeline.WithTracer(pipeline.DefaultTracer),
pipeline.WithLogger(defaultLogger),
pipeline.WithBackend(backendEngine),
pipeline.WithDescription(map[string]string{
"CLI": "exec",
}),
).Run(c.Context)
).Run(ctx)
}
// convertPathForWindows converts a path to use slash separators
@ -281,8 +308,7 @@ func convertPathForWindows(path string) string {
return filepath.ToSlash(path)
}
const maxLogLineLength = 1024 * 1024 // 1mb
var defaultLogger = pipeline.Logger(func(step *backend_types.Step, rc io.ReadCloser) error {
logWriter := NewLineWriter(step.Name, step.UUID)
return pipelineLog.CopyLineByLine(logWriter, rc, maxLogLineLength)
return pipelineLog.CopyLineByLine(logWriter, rc, pipeline.MaxLogLineLength)
})

View file

@ -17,53 +17,49 @@ package exec
import (
"time"
"github.com/urfave/cli/v2"
"go.woodpecker-ci.org/woodpecker/v2/shared/constant"
"github.com/urfave/cli/v3"
)
var flags = []cli.Flag{
&cli.BoolFlag{
EnvVars: []string{"WOODPECKER_LOCAL"},
Sources: cli.EnvVars("WOODPECKER_LOCAL"),
Name: "local",
Usage: "run from local directory",
Value: true,
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_REPO_PATH"},
Sources: cli.EnvVars("WOODPECKER_REPO_PATH"),
Name: "repo-path",
Usage: "path to local repository",
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_METADATA_FILE"),
Name: "metadata-file",
Usage: "path to pipeline metadata file (normally downloaded from UI). Parameters can be adjusted by applying additional cli flags",
},
&cli.DurationFlag{
EnvVars: []string{"WOODPECKER_TIMEOUT"},
Sources: cli.EnvVars("WOODPECKER_TIMEOUT"),
Name: "timeout",
Usage: "pipeline timeout",
Value: time.Hour,
},
&cli.StringSliceFlag{
EnvVars: []string{"WOODPECKER_VOLUMES"},
Sources: cli.EnvVars("WOODPECKER_VOLUMES"),
Name: "volumes",
Usage: "pipeline volumes",
},
&cli.StringSliceFlag{
EnvVars: []string{"WOODPECKER_NETWORKS"},
Sources: cli.EnvVars("WOODPECKER_NETWORKS"),
Name: "network",
Usage: "external networks",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_PREFIX"},
Name: "prefix",
Value: "woodpecker",
Usage: "prefix used for containers, volumes, networks, ... created by woodpecker",
Hidden: true,
},
&cli.StringSliceFlag{
Name: "privileged",
Usage: "privileged plugins",
Value: cli.NewStringSlice(constant.PrivilegedPlugins...),
Sources: cli.EnvVars("WOODPECKER_PLUGINS_PRIVILEGED"),
Name: "plugins-privileged",
Usage: "Allow plugins to run in privileged mode, if environment variable is defined but empty there will be none",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_BACKEND"},
Sources: cli.EnvVars("WOODPECKER_BACKEND"),
Name: "backend-engine",
Usage: "backend engine to run pipelines on",
Value: "auto-detect",
@ -73,17 +69,17 @@ var flags = []cli.Flag{
// backend options for pipeline compiler
//
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_BACKEND_NO_PROXY", "NO_PROXY", "no_proxy"},
Sources: cli.EnvVars("WOODPECKER_BACKEND_NO_PROXY", "NO_PROXY", "no_proxy"),
Usage: "if set, pass the environment variable down as \"NO_PROXY\" to steps",
Name: "backend-no-proxy",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_BACKEND_HTTP_PROXY", "HTTP_PROXY", "http_proxy"},
Sources: cli.EnvVars("WOODPECKER_BACKEND_HTTP_PROXY", "HTTP_PROXY", "http_proxy"),
Usage: "if set, pass the environment variable down as \"HTTP_PROXY\" to steps",
Name: "backend-http-proxy",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_BACKEND_HTTPS_PROXY", "HTTPS_PROXY", "https_proxy"},
Sources: cli.EnvVars("WOODPECKER_BACKEND_HTTPS_PROXY", "HTTPS_PROXY", "https_proxy"),
Usage: "if set, pass the environment variable down as \"HTTPS_PROXY\" to steps",
Name: "backend-https-proxy",
},
@ -97,12 +93,12 @@ var flags = []cli.Flag{
// workspace default
//
&cli.StringFlag{
EnvVars: []string{"CI_WORKSPACE_BASE"},
Sources: cli.EnvVars("CI_WORKSPACE_BASE"),
Name: "workspace-base",
Value: "/woodpecker",
},
&cli.StringFlag{
EnvVars: []string{"CI_WORKSPACE_PATH"},
Sources: cli.EnvVars("CI_WORKSPACE_PATH"),
Name: "workspace-path",
Value: "src",
},
@ -110,218 +106,298 @@ var flags = []cli.Flag{
// netrc parameters
//
&cli.StringFlag{
EnvVars: []string{"CI_NETRC_USERNAME"},
Sources: cli.EnvVars("CI_NETRC_USERNAME"),
Name: "netrc-username",
},
&cli.StringFlag{
EnvVars: []string{"CI_NETRC_PASSWORD"},
Sources: cli.EnvVars("CI_NETRC_PASSWORD"),
Name: "netrc-password",
},
&cli.StringFlag{
EnvVars: []string{"CI_NETRC_MACHINE"},
Sources: cli.EnvVars("CI_NETRC_MACHINE"),
Name: "netrc-machine",
},
//
// metadata parameters
//
&cli.StringFlag{
EnvVars: []string{"CI_SYSTEM_PLATFORM"},
Sources: cli.EnvVars("CI_SYSTEM_PLATFORM"),
Name: "system-platform",
Usage: "Set the metadata environment variable \"CI_SYSTEM_PLATFORM\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_SYSTEM_NAME"},
Sources: cli.EnvVars("CI_SYSTEM_HOST"),
Name: "system-host",
Usage: "Set the metadata environment variable \"CI_SYSTEM_HOST\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_SYSTEM_NAME"),
Name: "system-name",
Usage: "Set the metadata environment variable \"CI_SYSTEM_NAME\".",
Value: "woodpecker",
},
&cli.StringFlag{
EnvVars: []string{"CI_SYSTEM_URL"},
Sources: cli.EnvVars("CI_SYSTEM_URL"),
Name: "system-url",
Usage: "Set the metadata environment variable \"CI_SYSTEM_URL\".",
Value: "https://github.com/woodpecker-ci/woodpecker",
},
&cli.StringFlag{
EnvVars: []string{"CI_REPO"},
Sources: cli.EnvVars("CI_REPO"),
Name: "repo",
Usage: "full repo name",
Usage: "Set the full name to derive metadata environment variables \"CI_REPO\", \"CI_REPO_NAME\" and \"CI_REPO_OWNER\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_REPO_REMOTE_ID"},
Sources: cli.EnvVars("CI_REPO_REMOTE_ID"),
Name: "repo-remote-id",
Usage: "Set the metadata environment variable \"CI_REPO_REMOTE_ID\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_REPO_URL"},
Sources: cli.EnvVars("CI_REPO_URL"),
Name: "repo-url",
Usage: "Set the metadata environment variable \"CI_REPO_URL\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_REPO_CLONE_URL"},
Sources: cli.EnvVars("CI_REPO_DEFAULT_BRANCH"),
Name: "repo-default-branch",
Usage: "Set the metadata environment variable \"CI_REPO_DEFAULT_BRANCH\".",
Value: "main",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_REPO_CLONE_URL"),
Name: "repo-clone-url",
Usage: "Set the metadata environment variable \"CI_REPO_CLONE_URL\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_REPO_CLONE_SSH_URL"},
Sources: cli.EnvVars("CI_REPO_CLONE_SSH_URL"),
Name: "repo-clone-ssh-url",
Usage: "Set the metadata environment variable \"CI_REPO_CLONE_SSH_URL\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_REPO_PRIVATE"},
Sources: cli.EnvVars("CI_REPO_PRIVATE"),
Name: "repo-private",
Usage: "Set the metadata environment variable \"CI_REPO_PRIVATE\".",
},
&cli.BoolFlag{
EnvVars: []string{"CI_REPO_TRUSTED"},
Name: "repo-trusted",
Sources: cli.EnvVars("CI_REPO_TRUSTED_NETWORK"),
Name: "repo-trusted-network",
Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED_NETWORK\".",
},
&cli.BoolFlag{
Sources: cli.EnvVars("CI_REPO_TRUSTED_VOLUMES"),
Name: "repo-trusted-volumes",
Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED_VOLUMES\".",
},
&cli.BoolFlag{
Sources: cli.EnvVars("CI_REPO_TRUSTED_SECURITY"),
Name: "repo-trusted-security",
Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED_SECURITY\".",
},
&cli.IntFlag{
EnvVars: []string{"CI_PIPELINE_NUMBER"},
Sources: cli.EnvVars("CI_PIPELINE_NUMBER"),
Name: "pipeline-number",
Usage: "Set the metadata environment variable \"CI_PIPELINE_NUMBER\".",
},
&cli.IntFlag{
EnvVars: []string{"CI_PIPELINE_PARENT"},
Sources: cli.EnvVars("CI_PIPELINE_PARENT"),
Name: "pipeline-parent",
Usage: "Set the metadata environment variable \"CI_PIPELINE_PARENT\".",
},
&cli.Int64Flag{
EnvVars: []string{"CI_PIPELINE_CREATED"},
&cli.IntFlag{
Sources: cli.EnvVars("CI_PIPELINE_CREATED"),
Name: "pipeline-created",
Usage: "Set the metadata environment variable \"CI_PIPELINE_CREATED\".",
},
&cli.Int64Flag{
EnvVars: []string{"CI_PIPELINE_STARTED"},
&cli.IntFlag{
Sources: cli.EnvVars("CI_PIPELINE_STARTED"),
Name: "pipeline-started",
},
&cli.Int64Flag{
EnvVars: []string{"CI_PIPELINE_FINISHED"},
Name: "pipeline-finished",
Usage: "Set the metadata environment variable \"CI_PIPELINE_STARTED\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_PIPELINE_STATUS"},
Name: "pipeline-status",
},
&cli.StringFlag{
EnvVars: []string{"CI_PIPELINE_EVENT"},
Sources: cli.EnvVars("CI_PIPELINE_EVENT"),
Name: "pipeline-event",
Usage: "Set the metadata environment variable \"CI_PIPELINE_EVENT\".",
Value: "manual",
},
&cli.StringFlag{
EnvVars: []string{"CI_PIPELINE_URL"},
Sources: cli.EnvVars("CI_PIPELINE_FORGE_URL"),
Name: "pipeline-url",
Usage: "Set the metadata environment variable \"CI_PIPELINE_FORGE_URL\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_PIPELINE_DEPLOY_TARGET", "CI_PIPELINE_TARGET"}, // TODO: remove CI_PIPELINE_TARGET in 3.x
Sources: cli.EnvVars("CI_PIPELINE_DEPLOY_TARGET"),
Name: "pipeline-deploy-to",
Usage: "Set the metadata environment variable \"CI_PIPELINE_DEPLOY_TARGET\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_PIPELINE_DEPLOY_TASK", "CI_PIPELINE_TASK"}, // TODO: remove CI_PIPELINE_TASK in 3.x
Sources: cli.EnvVars("CI_PIPELINE_DEPLOY_TASK"),
Name: "pipeline-deploy-task",
Usage: "Set the metadata environment variable \"CI_PIPELINE_DEPLOY_TASK\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_COMMIT_SHA"},
Sources: cli.EnvVars("CI_PIPELINE_FILES"),
Usage: "Set the metadata environment variable \"CI_PIPELINE_FILES\", either json formatted list of strings, or comma separated string list.",
Name: "pipeline-changed-files",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_COMMIT_SHA"),
Name: "commit-sha",
Usage: "Set the metadata environment variable \"CI_COMMIT_SHA\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_COMMIT_REF"},
Sources: cli.EnvVars("CI_COMMIT_REF"),
Name: "commit-ref",
Usage: "Set the metadata environment variable \"CI_COMMIT_REF\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_COMMIT_REFSPEC"},
Sources: cli.EnvVars("CI_COMMIT_REFSPEC"),
Name: "commit-refspec",
Usage: "Set the metadata environment variable \"CI_COMMIT_REFSPEC\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_COMMIT_BRANCH"},
Sources: cli.EnvVars("CI_COMMIT_BRANCH"),
Name: "commit-branch",
Usage: "Set the metadata environment variable \"CI_COMMIT_BRANCH\".",
Value: "main",
},
&cli.StringFlag{
EnvVars: []string{"CI_COMMIT_MESSAGE"},
Sources: cli.EnvVars("CI_COMMIT_MESSAGE"),
Name: "commit-message",
Usage: "Set the metadata environment variable \"CI_COMMIT_MESSAGE\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_COMMIT_AUTHOR_NAME"},
Sources: cli.EnvVars("CI_COMMIT_AUTHOR"),
Name: "commit-author-name",
Usage: "Set the metadata environment variable \"CI_COMMIT_AUTHOR\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_COMMIT_AUTHOR_AVATAR"},
Sources: cli.EnvVars("CI_COMMIT_AUTHOR_AVATAR"),
Name: "commit-author-avatar",
Usage: "Set the metadata environment variable \"CI_COMMIT_AUTHOR_AVATAR\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_COMMIT_AUTHOR_EMAIL"},
Sources: cli.EnvVars("CI_COMMIT_AUTHOR_EMAIL"),
Name: "commit-author-email",
},
&cli.IntFlag{
EnvVars: []string{"CI_PREV_PIPELINE_NUMBER"},
Name: "prev-pipeline-number",
},
&cli.Int64Flag{
EnvVars: []string{"CI_PREV_PIPELINE_CREATED"},
Name: "prev-pipeline-created",
},
&cli.Int64Flag{
EnvVars: []string{"CI_PREV_PIPELINE_STARTED"},
Name: "prev-pipeline-started",
},
&cli.Int64Flag{
EnvVars: []string{"CI_PREV_PIPELINE_FINISHED"},
Name: "prev-pipeline-finished",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_PIPELINE_STATUS"},
Name: "prev-pipeline-status",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_PIPELINE_EVENT"},
Name: "prev-pipeline-event",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_PIPELINE_URL"},
Name: "prev-pipeline-url",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_COMMIT_SHA"},
Name: "prev-commit-sha",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_COMMIT_REF"},
Name: "prev-commit-ref",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_COMMIT_REFSPEC"},
Name: "prev-commit-refspec",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_COMMIT_BRANCH"},
Name: "prev-commit-branch",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_COMMIT_MESSAGE"},
Name: "prev-commit-message",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_COMMIT_AUTHOR_NAME"},
Name: "prev-commit-author-name",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_COMMIT_AUTHOR_AVATAR"},
Name: "prev-commit-author-avatar",
},
&cli.StringFlag{
EnvVars: []string{"CI_PREV_COMMIT_AUTHOR_EMAIL"},
Name: "prev-commit-author-email",
},
&cli.IntFlag{
EnvVars: []string{"CI_WORKFLOW_NAME"},
Name: "workflow-name",
},
&cli.IntFlag{
EnvVars: []string{"CI_WORKFLOW_NUMBER"},
Name: "workflow-number",
},
&cli.IntFlag{
EnvVars: []string{"CI_STEP_NAME"},
Name: "step-name",
Usage: "Set the metadata environment variable \"CI_COMMIT_AUTHOR_EMAIL\".",
},
&cli.StringSliceFlag{
EnvVars: []string{"CI_ENV"},
Sources: cli.EnvVars("CI_COMMIT_PULL_REQUEST_LABELS"),
Name: "commit-pull-labels",
Usage: "Set the metadata environment variable \"CI_COMMIT_PULL_REQUEST_LABELS\".",
},
&cli.BoolFlag{
Sources: cli.EnvVars("CI_COMMIT_PRERELEASE"),
Name: "commit-release-is-pre",
Usage: "Set the metadata environment variable \"CI_COMMIT_PRERELEASE\".",
},
&cli.IntFlag{
Sources: cli.EnvVars("CI_PREV_PIPELINE_NUMBER"),
Name: "prev-pipeline-number",
Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_NUMBER\".",
},
&cli.IntFlag{
Sources: cli.EnvVars("CI_PREV_PIPELINE_CREATED"),
Name: "prev-pipeline-created",
Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_CREATED\".",
},
&cli.IntFlag{
Sources: cli.EnvVars("CI_PREV_PIPELINE_STARTED"),
Name: "prev-pipeline-started",
Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_STARTED\".",
},
&cli.IntFlag{
Sources: cli.EnvVars("CI_PREV_PIPELINE_FINISHED"),
Name: "prev-pipeline-finished",
Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_FINISHED\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_PIPELINE_STATUS"),
Name: "prev-pipeline-status",
Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_STATUS\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_PIPELINE_EVENT"),
Name: "prev-pipeline-event",
Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_EVENT\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_PIPELINE_FORGE_URL"),
Name: "prev-pipeline-url",
Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_FORGE_URL\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_PIPELINE_DEPLOY_TARGET"),
Name: "prev-pipeline-deploy-to",
Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_DEPLOY_TARGET\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_PIPELINE_DEPLOY_TASK"),
Name: "prev-pipeline-deploy-task",
Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_DEPLOY_TASK\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_COMMIT_SHA"),
Name: "prev-commit-sha",
Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_SHA\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_COMMIT_REF"),
Name: "prev-commit-ref",
Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_REF\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_COMMIT_REFSPEC"),
Name: "prev-commit-refspec",
Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_REFSPEC\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_COMMIT_BRANCH"),
Name: "prev-commit-branch",
Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_BRANCH\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_COMMIT_MESSAGE"),
Name: "prev-commit-message",
Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_MESSAGE\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_COMMIT_AUTHOR"),
Name: "prev-commit-author-name",
Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_AUTHOR\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_COMMIT_AUTHOR_AVATAR"),
Name: "prev-commit-author-avatar",
Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_AUTHOR_AVATAR\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_PREV_COMMIT_AUTHOR_EMAIL"),
Name: "prev-commit-author-email",
Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_AUTHOR_EMAIL\".",
},
&cli.StringFlag{
Sources: cli.EnvVars("CI_WORKFLOW_NAME"),
Name: "workflow-name",
Usage: "Set the metadata environment variable \"CI_WORKFLOW_NAME\".",
},
&cli.IntFlag{
Sources: cli.EnvVars("CI_WORKFLOW_NUMBER"),
Name: "workflow-number",
Usage: "Set the metadata environment variable \"CI_WORKFLOW_NUMBER\".",
},
&cli.StringSliceFlag{
Sources: cli.EnvVars("CI_ENV"),
Name: "env",
Usage: "Set the metadata environment variable \"CI_ENV\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_FORGE_TYPE"},
Sources: cli.EnvVars("CI_FORGE_TYPE"),
Name: "forge-type",
Usage: "Set the metadata environment variable \"CI_FORGE_TYPE\".",
},
&cli.StringFlag{
EnvVars: []string{"CI_FORGE_URL"},
Sources: cli.EnvVars("CI_FORGE_URL"),
Name: "forge-url",
Usage: "Set the metadata environment variable \"CI_FORGE_URL\".",
},
}

View file

@ -15,105 +15,147 @@
package exec
import (
"context"
"encoding/json"
"fmt"
"os"
"runtime"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix"
"go.woodpecker-ci.org/woodpecker/v2/version"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/matrix"
"go.woodpecker-ci.org/woodpecker/v3/version"
)
// return the metadata from the cli context.
func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata {
func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis, w *metadata.Workflow) (*metadata.Metadata, error) {
m := &metadata.Metadata{}
if c.IsSet("metadata-file") {
metadataFile, err := os.Open(c.String("metadata-file"))
if err != nil {
return nil, err
}
defer metadataFile.Close()
if err := json.NewDecoder(metadataFile).Decode(m); err != nil {
return nil, err
}
}
platform := c.String("system-platform")
if platform == "" {
platform = runtime.GOOS + "/" + runtime.GOARCH
}
fullRepoName := c.String("repo-name")
repoOwner := ""
repoName := ""
if idx := strings.LastIndex(fullRepoName, "/"); idx != -1 {
repoOwner = fullRepoName[:idx]
repoName = fullRepoName[idx+1:]
metadataFileAndOverrideOrDefault(c, "repo-name", func(fullRepoName string) {
if idx := strings.LastIndex(fullRepoName, "/"); idx != -1 {
m.Repo.Owner = fullRepoName[:idx]
m.Repo.Name = fullRepoName[idx+1:]
}
}, c.String)
var err error
metadataFileAndOverrideOrDefault(c, "pipeline-changed-files", func(changedFilesRaw string) {
var changedFiles []string
if len(changedFilesRaw) != 0 && changedFilesRaw[0] == '[' {
if jsonErr := json.Unmarshal([]byte(changedFilesRaw), &changedFiles); jsonErr != nil {
err = fmt.Errorf("pipeline-changed-files detected json but could not parse it: %w", jsonErr)
}
} else {
for _, file := range strings.Split(changedFilesRaw, ",") {
changedFiles = append(changedFiles, strings.TrimSpace(file))
}
}
m.Curr.Commit.ChangedFiles = changedFiles
}, c.String)
if err != nil {
return nil, err
}
return metadata.Metadata{
Repo: metadata.Repo{
Name: repoName,
Owner: repoOwner,
RemoteID: c.String("repo-remote-id"),
ForgeURL: c.String("repo-url"),
CloneURL: c.String("repo-clone-url"),
CloneSSHURL: c.String("repo-clone-ssh-url"),
Private: c.Bool("repo-private"),
Trusted: c.Bool("repo-trusted"),
},
Curr: metadata.Pipeline{
Number: c.Int64("pipeline-number"),
Parent: c.Int64("pipeline-parent"),
Created: c.Int64("pipeline-created"),
Started: c.Int64("pipeline-started"),
Finished: c.Int64("pipeline-finished"),
Status: c.String("pipeline-status"),
Event: c.String("pipeline-event"),
ForgeURL: c.String("pipeline-url"),
DeployTo: c.String("pipeline-deploy-to"),
DeployTask: c.String("pipeline-deploy-task"),
Commit: metadata.Commit{
Sha: c.String("commit-sha"),
Ref: c.String("commit-ref"),
Refspec: c.String("commit-refspec"),
Branch: c.String("commit-branch"),
Message: c.String("commit-message"),
Author: metadata.Author{
Name: c.String("commit-author-name"),
Email: c.String("commit-author-email"),
Avatar: c.String("commit-author-avatar"),
},
},
},
Prev: metadata.Pipeline{
Number: c.Int64("prev-pipeline-number"),
Created: c.Int64("prev-pipeline-created"),
Started: c.Int64("prev-pipeline-started"),
Finished: c.Int64("prev-pipeline-finished"),
Status: c.String("prev-pipeline-status"),
Event: c.String("prev-pipeline-event"),
ForgeURL: c.String("prev-pipeline-url"),
Commit: metadata.Commit{
Sha: c.String("prev-commit-sha"),
Ref: c.String("prev-commit-ref"),
Refspec: c.String("prev-commit-refspec"),
Branch: c.String("prev-commit-branch"),
Message: c.String("prev-commit-message"),
Author: metadata.Author{
Name: c.String("prev-commit-author-name"),
Email: c.String("prev-commit-author-email"),
Avatar: c.String("prev-commit-author-avatar"),
},
},
},
Workflow: metadata.Workflow{
Name: c.String("workflow-name"),
Number: c.Int("workflow-number"),
Matrix: axis,
},
Step: metadata.Step{
Name: c.String("step-name"),
Number: c.Int("step-number"),
},
Sys: metadata.System{
Name: c.String("system-name"),
URL: c.String("system-url"),
Platform: platform,
Version: version.Version,
},
Forge: metadata.Forge{
Type: c.String("forge-type"),
URL: c.String("forge-url"),
},
// Repo
metadataFileAndOverrideOrDefault(c, "repo-remote-id", func(s string) { m.Repo.RemoteID = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-url", func(s string) { m.Repo.ForgeURL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-default-branch", func(s string) { m.Repo.Branch = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-clone-url", func(s string) { m.Repo.CloneURL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-clone-ssh-url", func(s string) { m.Repo.CloneSSHURL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "repo-private", func(b bool) { m.Repo.Private = b }, c.Bool)
metadataFileAndOverrideOrDefault(c, "repo-trusted-network", func(b bool) { m.Repo.Trusted.Network = b }, c.Bool)
metadataFileAndOverrideOrDefault(c, "repo-trusted-security", func(b bool) { m.Repo.Trusted.Security = b }, c.Bool)
metadataFileAndOverrideOrDefault(c, "repo-trusted-volumes", func(b bool) { m.Repo.Trusted.Volumes = b }, c.Bool)
// Current Pipeline
metadataFileAndOverrideOrDefault(c, "pipeline-number", func(i int64) { m.Curr.Number = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-parent", func(i int64) { m.Curr.Parent = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-created", func(i int64) { m.Curr.Created = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-started", func(i int64) { m.Curr.Started = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-finished", func(i int64) { m.Curr.Finished = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "pipeline-status", func(s string) { m.Curr.Status = s }, c.String)
metadataFileAndOverrideOrDefault(c, "pipeline-event", func(s string) { m.Curr.Event = s }, c.String)
metadataFileAndOverrideOrDefault(c, "pipeline-url", func(s string) { m.Curr.ForgeURL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "pipeline-deploy-to", func(s string) { m.Curr.DeployTo = s }, c.String)
metadataFileAndOverrideOrDefault(c, "pipeline-deploy-task", func(s string) { m.Curr.DeployTask = s }, c.String)
// Current Pipeline Commit
metadataFileAndOverrideOrDefault(c, "commit-sha", func(s string) { m.Curr.Commit.Sha = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-ref", func(s string) { m.Curr.Commit.Ref = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-refspec", func(s string) { m.Curr.Commit.Refspec = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-branch", func(s string) { m.Curr.Commit.Branch = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-message", func(s string) { m.Curr.Commit.Message = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-author-name", func(s string) { m.Curr.Commit.Author.Name = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-author-email", func(s string) { m.Curr.Commit.Author.Email = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-author-avatar", func(s string) { m.Curr.Commit.Author.Avatar = s }, c.String)
metadataFileAndOverrideOrDefault(c, "commit-pull-labels", func(sl []string) { m.Curr.Commit.PullRequestLabels = sl }, c.StringSlice)
metadataFileAndOverrideOrDefault(c, "commit-release-is-pre", func(b bool) { m.Curr.Commit.IsPrerelease = b }, c.Bool)
// Previous Pipeline
metadataFileAndOverrideOrDefault(c, "prev-pipeline-number", func(i int64) { m.Prev.Number = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-created", func(i int64) { m.Prev.Created = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-started", func(i int64) { m.Prev.Started = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-finished", func(i int64) { m.Prev.Finished = i }, c.Int)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-status", func(s string) { m.Prev.Status = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-event", func(s string) { m.Prev.Event = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-pipeline-url", func(s string) { m.Prev.ForgeURL = s }, c.String)
// Previous Pipeline Commit
metadataFileAndOverrideOrDefault(c, "prev-commit-sha", func(s string) { m.Prev.Commit.Sha = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-ref", func(s string) { m.Prev.Commit.Ref = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-refspec", func(s string) { m.Prev.Commit.Refspec = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-branch", func(s string) { m.Prev.Commit.Branch = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-message", func(s string) { m.Prev.Commit.Message = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-author-name", func(s string) { m.Prev.Commit.Author.Name = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-author-email", func(s string) { m.Prev.Commit.Author.Email = s }, c.String)
metadataFileAndOverrideOrDefault(c, "prev-commit-author-avatar", func(s string) { m.Prev.Commit.Author.Avatar = s }, c.String)
// Workflow
metadataFileAndOverrideOrDefault(c, "workflow-name", func(s string) { m.Workflow.Name = s }, c.String)
metadataFileAndOverrideOrDefault(c, "workflow-number", func(i int64) { m.Workflow.Number = int(i) }, c.Int)
m.Workflow.Matrix = axis
// System
metadataFileAndOverrideOrDefault(c, "system-name", func(s string) { m.Sys.Name = s }, c.String)
metadataFileAndOverrideOrDefault(c, "system-url", func(s string) { m.Sys.URL = s }, c.String)
metadataFileAndOverrideOrDefault(c, "system-host", func(s string) { m.Sys.Host = s }, c.String)
m.Sys.Platform = platform
m.Sys.Version = version.Version
// Forge
metadataFileAndOverrideOrDefault(c, "forge-type", func(s string) { m.Forge.Type = s }, c.String)
metadataFileAndOverrideOrDefault(c, "forge-url", func(s string) { m.Forge.URL = s }, c.String)
if w != nil {
m.Workflow = *w
}
return m, nil
}
// metadataFileAndOverrideOrDefault will either use the flag default or if metadata file is set only overload if explicit set.
func metadataFileAndOverrideOrDefault[T any](c *cli.Command, flag string, setter func(T), getter func(string) T) {
if !c.IsSet("metadata-file") || c.IsSet(flag) {
setter(getter(flag))
}
}

142
cli/exec/metadata_test.go Normal file
View file

@ -0,0 +1,142 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package exec
import (
"context"
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/matrix"
)
func TestMetadataFromContext(t *testing.T) {
sampleMetadata := &metadata.Metadata{
Repo: metadata.Repo{Owner: "test-user", Name: "test-repo"},
Curr: metadata.Pipeline{Number: 5},
}
runCommand := func(flags []cli.Flag, fn func(c *cli.Command)) {
c := &cli.Command{
Flags: flags,
Action: func(_ context.Context, c *cli.Command) error {
fn(c)
return nil
},
}
assert.NoError(t, c.Run(context.Background(), []string{"woodpecker-cli"}))
}
t.Run("LoadFromFile", func(t *testing.T) {
tempFileName := createTempFile(t, sampleMetadata)
flags := []cli.Flag{
&cli.StringFlag{Name: "metadata-file"},
}
runCommand(flags, func(c *cli.Command) {
_ = c.Set("metadata-file", tempFileName)
m, err := metadataFromContext(context.Background(), c, nil, nil)
require.NoError(t, err)
assert.Equal(t, "test-repo", m.Repo.Name)
assert.Equal(t, int64(5), m.Curr.Number)
})
})
t.Run("OverrideFromFlags", func(t *testing.T) {
tempFileName := createTempFile(t, sampleMetadata)
flags := []cli.Flag{
&cli.StringFlag{Name: "metadata-file"},
&cli.StringFlag{Name: "repo-name"},
&cli.IntFlag{Name: "pipeline-number"},
}
runCommand(flags, func(c *cli.Command) {
_ = c.Set("metadata-file", tempFileName)
_ = c.Set("repo-name", "aUser/override-repo")
_ = c.Set("pipeline-number", "10")
m, err := metadataFromContext(context.Background(), c, nil, nil)
require.NoError(t, err)
assert.Equal(t, "override-repo", m.Repo.Name)
assert.Equal(t, int64(10), m.Curr.Number)
})
})
t.Run("InvalidFile", func(t *testing.T) {
tempFile, err := os.CreateTemp("", "invalid.json")
require.NoError(t, err)
t.Cleanup(func() { os.Remove(tempFile.Name()) })
_, err = tempFile.Write([]byte("invalid json"))
require.NoError(t, err)
flags := []cli.Flag{
&cli.StringFlag{Name: "metadata-file"},
}
runCommand(flags, func(c *cli.Command) {
_ = c.Set("metadata-file", tempFile.Name())
_, err = metadataFromContext(context.Background(), c, nil, nil)
assert.Error(t, err)
})
})
t.Run("DefaultValues", func(t *testing.T) {
flags := []cli.Flag{
&cli.StringFlag{Name: "repo-name", Value: "test/default-repo"},
&cli.IntFlag{Name: "pipeline-number", Value: 1},
}
runCommand(flags, func(c *cli.Command) {
m, err := metadataFromContext(context.Background(), c, nil, nil)
require.NoError(t, err)
if assert.NotNil(t, m) {
assert.Equal(t, "test", m.Repo.Owner)
assert.Equal(t, "default-repo", m.Repo.Name)
assert.Equal(t, int64(1), m.Curr.Number)
}
})
})
t.Run("MatrixAxis", func(t *testing.T) {
runCommand([]cli.Flag{}, func(c *cli.Command) {
axis := matrix.Axis{"go": "1.16", "os": "linux"}
m, err := metadataFromContext(context.Background(), c, axis, nil)
require.NoError(t, err)
assert.EqualValues(t, map[string]string{"go": "1.16", "os": "linux"}, m.Workflow.Matrix)
})
})
}
func createTempFile(t *testing.T, content any) string {
t.Helper()
tempFile, err := os.CreateTemp("", "metadata.json")
require.NoError(t, err)
t.Cleanup(func() { os.Remove(tempFile.Name()) })
err = json.NewEncoder(tempFile).Encode(content)
require.NoError(t, err)
return tempFile.Name()
}

View file

@ -15,13 +15,14 @@
package info
import (
"context"
"os"
"text/template"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
// Command exports the info command.
@ -33,8 +34,8 @@ var Command = &cli.Command{
Flags: []cli.Flag{common.FormatFlag(tmplInfo, true)},
}
func info(c *cli.Context) error {
client, err := internal.NewClient(c)
func info(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -1,6 +1,7 @@
package config
import (
"context"
"encoding/json"
"errors"
"os"
@ -8,7 +9,7 @@ import (
"github.com/adrg/xdg"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"github.com/zalando/go-keyring"
)
@ -32,18 +33,18 @@ func (c *Config) MergeIfNotSet(c2 *Config) {
var skipSetupForCommands = []string{"setup", "help", "h", "version", "update", "lint", "exec", ""}
func Load(c *cli.Context) error {
func Load(ctx context.Context, c *cli.Command) error {
if firstArg := c.Args().First(); slices.Contains(skipSetupForCommands, firstArg) {
return nil
}
config, err := Get(c, c.String("config"))
config, err := Get(ctx, c, c.String("config"))
if err != nil {
return err
}
if config.ServerURL == "" || config.Token == "" {
log.Info().Msg("The woodpecker-cli is not yet set up. Please run `woodpecker-cli setup` or provide the required environment variables / flags.")
log.Info().Msg("woodpecker-cli is not set up, run `woodpecker-cli setup` or provide required environment variables/flags")
return errors.New("woodpecker-cli is not configured")
}
@ -62,7 +63,7 @@ func Load(c *cli.Context) error {
return err
}
log.Debug().Any("config", config).Msg("Loaded config")
log.Debug().Any("config", config).Msg("loaded config")
return nil
}
@ -80,11 +81,11 @@ func getConfigPath(configPath string) (string, error) {
return configPath, nil
}
func Get(ctx *cli.Context, _configPath string) (*Config, error) {
c := &Config{
LogLevel: ctx.String("log-level"),
Token: ctx.String("token"),
ServerURL: ctx.String("server"),
func Get(_ context.Context, c *cli.Command, _configPath string) (*Config, error) {
conf := &Config{
LogLevel: c.String("log-level"),
Token: c.String("token"),
ServerURL: c.String("server"),
}
configPath, err := getConfigPath(_configPath)
@ -92,16 +93,16 @@ func Get(ctx *cli.Context, _configPath string) (*Config, error) {
return nil, err
}
log.Debug().Str("configPath", configPath).Msg("Checking for config file")
log.Debug().Str("configPath", configPath).Msg("checking for config file")
content, err := os.ReadFile(configPath)
switch {
case err != nil && !os.IsNotExist(err):
log.Debug().Err(err).Msg("Failed to read the config file")
log.Debug().Err(err).Msg("failed to read the config file")
return nil, err
case err != nil && os.IsNotExist(err):
log.Debug().Msg("The config file does not exist")
log.Debug().Msg("config file does not exist")
default:
configFromFile := &Config{}
@ -109,33 +110,33 @@ func Get(ctx *cli.Context, _configPath string) (*Config, error) {
if err != nil {
return nil, err
}
c.MergeIfNotSet(configFromFile)
log.Debug().Msg("Loaded config from file")
conf.MergeIfNotSet(configFromFile)
log.Debug().Msg("loaded config from file")
}
// if server or token are explicitly set, use them
if ctx.IsSet("server") || ctx.IsSet("token") {
return c, nil
if c.IsSet("server") || c.IsSet("token") {
return conf, nil
}
// load token from keyring
service := ctx.App.Name
secret, err := keyring.Get(service, c.ServerURL)
service := c.Root().Name
secret, err := keyring.Get(service, conf.ServerURL)
if errors.Is(err, keyring.ErrUnsupportedPlatform) {
log.Warn().Msg("Keyring is not supported on this platform")
return c, nil
log.Warn().Msg("keyring is not supported on this platform")
return conf, nil
}
if errors.Is(err, keyring.ErrNotFound) {
log.Warn().Msg("Token not found in keyring")
return c, nil
log.Warn().Msg("token not found in keyring")
return conf, nil
}
c.Token = secret
conf.Token = secret
return c, nil
return conf, nil
}
func Save(ctx *cli.Context, _configPath string, c *Config) error {
config, err := json.Marshal(c)
func Save(_ context.Context, c *cli.Command, _configPath string, conf *Config) error {
config, err := json.Marshal(conf)
if err != nil {
return err
}
@ -146,8 +147,8 @@ func Save(ctx *cli.Context, _configPath string, c *Config) error {
}
// save token to keyring
service := ctx.App.Name
err = keyring.Set(service, c.ServerURL, c.Token)
service := c.Root().Name
err = keyring.Set(service, conf.ServerURL, conf.Token)
if err != nil {
return err
}

View file

@ -15,6 +15,7 @@
package internal
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
@ -25,15 +26,15 @@ import (
vsc_url "github.com/gitsight/go-vcsurl"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"golang.org/x/net/proxy"
"golang.org/x/oauth2"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
// NewClient returns a new client from the CLI context.
func NewClient(c *cli.Context) (woodpecker.Client, error) {
func NewClient(ctx context.Context, c *cli.Command) (woodpecker.Client, error) {
var (
skip = c.Bool("skip-verify")
socks = c.String("socks-proxy")
@ -63,8 +64,7 @@ func NewClient(c *cli.Context) (woodpecker.Client, error) {
}
config := new(oauth2.Config)
client := config.Client(
c.Context,
client := config.Client(ctx,
&oauth2.Token{
AccessToken: token,
},
@ -161,3 +161,40 @@ func ParseKeyPair(p []string) map[string]string {
}
return params
}
/*
ParseStep parses the step id form a string which may either be the step PID (step number) or a step name.
These rules apply:
- Step PID take precedence over step name when searching for a match.
- First match is used, when there are multiple steps with the same name.
Strictly speaking, this is not parsing, but a lookup.
*/
func ParseStep(client woodpecker.Client, repoID, number int64, stepArg string) (stepID int64, err error) {
pipeline, err := client.Pipeline(repoID, number)
if err != nil {
return 0, err
}
stepPID, err := strconv.ParseInt(stepArg, 10, 64)
if err == nil {
for _, wf := range pipeline.Workflows {
for _, step := range wf.Children {
if int64(step.PID) == stepPID {
return step.ID, nil
}
}
}
}
for _, wf := range pipeline.Workflows {
for _, step := range wf.Children {
if step.Name == stepArg {
return step.ID, nil
}
}
}
return 0, fmt.Errorf("no step with number or name '%s' found", stepArg)
}

View file

@ -15,17 +15,19 @@
package lint
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter"
"go.woodpecker-ci.org/woodpecker/v3/shared/constant"
)
// Command exports the info command.
@ -34,13 +36,31 @@ var Command = &cli.Command{
Usage: "lint a pipeline configuration file",
ArgsUsage: "[path/to/.woodpecker.yaml]",
Action: lint,
Flags: []cli.Flag{
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_PLUGINS_PRIVILEGED"),
Name: "plugins-privileged",
Usage: "allow plugins to run in privileged mode, if set empty, there is no",
},
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_PLUGINS_TRUSTED_CLONE"),
Name: "plugins-trusted-clone",
Usage: "plugins that are trusted to handle Git credentials in cloning steps",
Value: constant.TrustedClonePlugins,
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_LINT_STRICT"),
Name: "strict",
Usage: "treat warnings as errors",
},
},
}
func lint(c *cli.Context) error {
return common.RunPipelineFunc(c, lintFile, lintDir)
func lint(ctx context.Context, c *cli.Command) error {
return common.RunPipelineFunc(ctx, c, lintFile, lintDir)
}
func lintDir(c *cli.Context, dir string) error {
func lintDir(ctx context.Context, c *cli.Command, dir string) error {
var errorStrings []string
if err := filepath.Walk(dir, func(path string, info os.FileInfo, e error) error {
if e != nil {
@ -50,7 +70,7 @@ func lintDir(c *cli.Context, dir string) error {
// check if it is a regular file (not dir)
if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
fmt.Println("#", info.Name())
if err := lintFile(c, path); err != nil {
if err := lintFile(ctx, c, path); err != nil {
errorStrings = append(errorStrings, err.Error())
}
fmt.Println("")
@ -68,7 +88,7 @@ func lintDir(c *cli.Context, dir string) error {
return nil
}
func lintFile(_ *cli.Context, file string) error {
func lintFile(_ context.Context, c *cli.Command, file string) error {
fi, err := os.Open(file)
if err != nil {
return err
@ -82,7 +102,7 @@ func lintFile(_ *cli.Context, file string) error {
rawConfig := string(buf)
c, err := yaml.ParseString(rawConfig)
parsedConfig, err := yaml.ParseString(rawConfig)
if err != nil {
return err
}
@ -90,13 +110,21 @@ func lintFile(_ *cli.Context, file string) error {
config := &linter.WorkflowConfig{
File: path.Base(file),
RawConfig: rawConfig,
Workflow: c,
Workflow: parsedConfig,
}
// TODO: lint multiple files at once to allow checks for sth like "depends_on" to work
err = linter.New(linter.WithTrusted(true)).Lint([]*linter.WorkflowConfig{config})
err = linter.New(
linter.WithTrusted(linter.TrustedConfiguration{
Network: true,
Volumes: true,
Security: true,
}),
linter.PrivilegedPlugins(c.StringSlice("plugins-privileged")),
linter.WithTrustedClonePlugins(c.StringSlice("plugins-trusted-clone")),
).Lint([]*linter.WorkflowConfig{config})
if err != nil {
str, err := FormatLintError(config.File, err)
str, err := FormatLintError(config.File, err, c.Bool("strict"))
if str != "" {
fmt.Print(str)

View file

@ -7,10 +7,10 @@ import (
term_env "github.com/muesli/termenv"
pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
pipeline_errors "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors"
)
func FormatLintError(file string, err error) (string, error) {
func FormatLintError(file string, err error, strict bool) (string, error) {
if err == nil {
return "", nil
}
@ -24,7 +24,7 @@ func FormatLintError(file string, err error) (string, error) {
for _, err := range linterErrors {
line := " "
if err.IsWarning {
if !strict && err.IsWarning {
line = fmt.Sprintf("%s ⚠️ ", line)
amountWarnings++
} else {

View file

@ -15,16 +15,18 @@
package org
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/org/registry"
"go.woodpecker-ci.org/woodpecker/v3/cli/org/registry"
"go.woodpecker-ci.org/woodpecker/v3/cli/org/secret"
)
// Command exports the org command set.
var Command = &cli.Command{
Name: "org",
Usage: "manage organizations",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
registry.Command,
secret.Command,
},
}

View file

@ -17,25 +17,25 @@ package registry
import (
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
// Command exports the registry command set.
var Command = &cli.Command{
Name: "registry",
Usage: "manage organization registries",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
registryShowCmd,
registryUpdateCmd,
},
}
func parseTargetArgs(client woodpecker.Client, c *cli.Context) (orgID int64, err error) {
func parseTargetArgs(client woodpecker.Client, c *cli.Command) (orgID int64, err error) {
orgIDOrName := c.String("organization")
if orgIDOrName == "" {
orgIDOrName = c.Args().First()

View file

@ -15,19 +15,20 @@
package registry
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
Usage: "add a registry",
ArgsUsage: "[org-id|org-full-name]",
Action: registryCreate,
Flags: []cli.Flag{
@ -48,14 +49,14 @@ var registryCreateCmd = &cli.Command{
},
}
func registryCreate(c *cli.Context) error {
func registryCreate(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
username = c.String("username")
password = c.String("password")
)
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,13 +15,15 @@
package registry
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var registryListCmd = &cli.Command{
@ -35,10 +37,10 @@ var registryListCmd = &cli.Command{
},
}
func registryList(c *cli.Context) error {
func registryList(ctx context.Context, c *cli.Command) error {
format := c.String("format") + "\n"
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -48,7 +50,9 @@ func registryList(c *cli.Context) error {
return err
}
list, err := client.OrgRegistryList(orgID)
opt := woodpecker.RegistryListOptions{}
list, err := client.OrgRegistryList(orgID, opt)
if err != nil {
return err
}

View file

@ -15,10 +15,12 @@
package registry
import (
"github.com/urfave/cli/v2"
"context"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var registryDeleteCmd = &cli.Command{
@ -36,10 +38,10 @@ var registryDeleteCmd = &cli.Command{
},
}
func registryDelete(c *cli.Context) error {
func registryDelete(ctx context.Context, c *cli.Command) error {
hostname := c.String("hostname")
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,14 +15,15 @@
package registry
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var registryUpdateCmd = &cli.Command{
@ -48,14 +49,14 @@ var registryUpdateCmd = &cli.Command{
},
}
func registryUpdate(c *cli.Context) error {
func registryUpdate(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
username = c.String("username")
password = c.String("password")
)
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,20 +15,21 @@
package registry
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
var registryShowCmd = &cli.Command{
Name: "show",
Usage: "show registry information",
ArgsUsage: "[org-id|org-full-name]",
Action: registryInfo,
Action: registryShow,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
@ -40,13 +41,13 @@ var registryInfoCmd = &cli.Command{
},
}
func registryInfo(c *cli.Context) error {
func registryShow(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
format = c.String("format") + "\n"
)
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

60
cli/org/secret/secret.go Normal file
View file

@ -0,0 +1,60 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"strconv"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
// Command exports the secret command.
var Command = &cli.Command{
Name: "secret",
Usage: "manage secrets",
Commands: []*cli.Command{
secretCreateCmd,
secretDeleteCmd,
secretListCmd,
secretShowCmd,
secretUpdateCmd,
},
}
func parseTargetArgs(client woodpecker.Client, c *cli.Command) (orgID int64, err error) {
orgIDOrName := c.String("organization")
if orgIDOrName == "" {
orgIDOrName = c.Args().First()
}
if orgIDOrName == "" {
if err := cli.ShowSubcommandHelp(c); err != nil {
return -1, err
}
}
if orgID, err := strconv.ParseInt(orgIDOrName, 10, 64); err == nil {
return orgID, nil
}
org, err := client.OrgLookup(orgIDOrName)
if err != nil {
return -1, err
}
return org.ID, nil
}

View file

@ -15,28 +15,24 @@
package secret
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var secretCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a secret",
Usage: "add a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretCreate,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
@ -56,8 +52,8 @@ var secretCreateCmd = &cli.Command{
},
}
func secretCreate(c *cli.Context) error {
client, err := internal.NewClient(c)
func secretCreate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -80,22 +76,12 @@ func secretCreate(c *cli.Context) error {
secret.Value = string(out)
}
global, orgID, repoID, err := parseTargetArgs(client, c)
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
if global {
_, err = client.GlobalSecretCreate(secret)
return err
}
if orgID != -1 {
_, err = client.OrgSecretCreate(orgID, secret)
return err
}
_, err = client.SecretCreate(repoID, secret)
_, err = client.OrgSecretCreate(orgID, secret)
return err
}

View file

@ -0,0 +1,87 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"html/template"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var secretListCmd = &cli.Command{
Name: "ls",
Usage: "list secrets",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretList,
Flags: []cli.Flag{
common.OrgFlag,
common.FormatFlag(tmplSecretList, true),
},
}
func secretList(ctx context.Context, c *cli.Command) error {
format := c.String("format") + "\n"
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
opt := woodpecker.SecretListOptions{}
list, err := client.OrgSecretList(orgID, opt)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err
}
for _, secret := range list {
if err := tmpl.Execute(os.Stdout, secret); err != nil {
return err
}
}
return nil
}
// Template for secret list items.
var tmplSecretList = "\x1b[33m{{ .Name }} \x1b[0m" + `
Events: {{ list .Events }}
{{- if .Images }}
Images: {{ list .Images }}
{{- else }}
Images: <any>
{{- end }}
`
var secretFuncMap = template.FuncMap{
"list": func(s []string) string {
return strings.Join(s, ", ")
},
}

View file

@ -0,0 +1,54 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var secretDeleteCmd = &cli.Command{
Name: "rm",
Usage: "remove a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretDelete,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
},
}
func secretDelete(ctx context.Context, c *cli.Command) error {
secretName := c.String("name")
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
return client.OrgSecretDelete(orgID, secretName)
}

View file

@ -0,0 +1,83 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var secretUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretUpdate,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
&cli.StringFlag{
Name: "value",
Usage: "secret value",
},
&cli.StringSliceFlag{
Name: "event",
Usage: "limit secret to these event",
},
&cli.StringSliceFlag{
Name: "image",
Usage: "limit secret to these image",
},
},
}
func secretUpdate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
secret := &woodpecker.Secret{
Name: strings.ToLower(c.String("name")),
Value: c.String("value"),
Images: c.StringSlice("image"),
Events: c.StringSlice("event"),
}
if strings.HasPrefix(secret.Value, "@") {
path := strings.TrimPrefix(secret.Value, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
secret.Value = string(out)
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
_, err = client.OrgSecretUpdate(orgID, secret)
return err
}

View file

@ -0,0 +1,74 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"context"
"fmt"
"html/template"
"os"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var secretShowCmd = &cli.Command{
Name: "show",
Usage: "show secret information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretShow,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
},
common.FormatFlag(tmplSecretList, true),
},
}
func secretShow(ctx context.Context, c *cli.Command) error {
var (
secretName = c.String("name")
format = c.String("format") + "\n"
)
if secretName == "" {
return fmt.Errorf("secret name is missing")
}
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
secret, err := client.OrgSecret(orgID, secretName)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, secret)
}

View file

@ -9,7 +9,7 @@ import (
"text/tabwriter"
"unicode"
"github.com/mitchellh/mapstructure"
"github.com/go-viper/mapstructure/v2"
)
// NewTable creates a new Table.
@ -147,12 +147,12 @@ func (o *Table) Write(columns []string, obj any) error {
colName := strings.ToLower(col)
if alias, ok := o.fieldAlias[colName]; ok {
if fn, ok := o.fieldMapping[alias]; ok {
out = append(out, fn(obj))
out = append(out, sanitizeString(fn(obj)))
continue
}
}
if fn, ok := o.fieldMapping[colName]; ok {
out = append(out, fn(obj))
out = append(out, sanitizeString(fn(obj)))
continue
}
if value, ok := dataL[strings.ReplaceAll(colName, "_", "")]; ok {
@ -165,10 +165,10 @@ func (o *Table) Write(columns []string, obj any) error {
continue
}
if s, ok := value.(string); ok {
out = append(out, NA(s))
out = append(out, NA(sanitizeString(s)))
continue
}
out = append(out, fmt.Sprintf("%v", value))
out = append(out, sanitizeString(value))
}
}
_, _ = fmt.Fprintln(o.w, strings.Join(out, "\t"))
@ -201,3 +201,9 @@ func fieldName(name string) string {
}
return string(out)
}
func sanitizeString(value any) string {
str := fmt.Sprintf("%v", value)
replacer := strings.NewReplacer("\n", " ", "\r", " ")
return strings.TrimSpace(replacer.Replace(str))
}

View file

@ -15,12 +15,13 @@
package pipeline
import (
"context"
"fmt"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var pipelineApproveCmd = &cli.Command{
@ -30,9 +31,9 @@ var pipelineApproveCmd = &cli.Command{
Action: pipelineApprove,
}
func pipelineApprove(c *cli.Context) (err error) {
func pipelineApprove(ctx context.Context, c *cli.Command) (err error) {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,13 +15,14 @@
package pipeline
import (
"context"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var pipelineCreateCmd = &cli.Command{
@ -42,9 +43,9 @@ var pipelineCreateCmd = &cli.Command{
}...),
}
func pipelineCreate(c *cli.Context) error {
func pipelineCreate(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -73,5 +74,5 @@ func pipelineCreate(c *cli.Context) error {
return err
}
return pipelineOutput(c, []woodpecker.Pipeline{*pipeline})
return pipelineOutput(c, []*woodpecker.Pipeline{pipeline})
}

View file

@ -15,12 +15,13 @@
package pipeline
import (
"context"
"fmt"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var pipelineDeclineCmd = &cli.Command{
@ -30,9 +31,9 @@ var pipelineDeclineCmd = &cli.Command{
Action: pipelineDecline,
}
func pipelineDecline(c *cli.Context) (err error) {
func pipelineDecline(ctx context.Context, c *cli.Command) (err error) {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,16 +15,17 @@
package deploy
import (
"context"
"fmt"
"html/template"
"os"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
// Command exports the deploy command.
@ -52,13 +53,13 @@ var Command = &cli.Command{
&cli.StringSliceFlag{
Name: "param",
Aliases: []string{"p"},
Usage: "custom parameters to be injected into the step environment. Format: KEY=value",
Usage: "custom parameters to inject into the step environment. Format: KEY=value",
},
},
}
func deploy(c *cli.Context) error {
client, err := internal.NewClient(c)
func deploy(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -79,14 +80,14 @@ func deploy(c *cli.Context) error {
return err
}
branch = repo.DefaultBranch
branch = repo.Branch
}
pipelineArg := c.Args().Get(1)
var number int64
if pipelineArg == "last" {
// Fetch the pipeline number from the last pipeline
pipelines, err := client.PipelineList(repoID)
pipelines, err := client.PipelineList(repoID, woodpecker.PipelineListOptions{})
if err != nil {
return err
}
@ -120,9 +121,12 @@ func deploy(c *cli.Context) error {
return fmt.Errorf("please specify the target environment (i.e. production)")
}
params := internal.ParseKeyPair(c.StringSlice("param"))
opt := woodpecker.DeployOptions{
DeployTo: env,
Params: internal.ParseKeyPair(c.StringSlice("param")),
}
deploy, err := client.Deploy(repoID, number, env, params)
deploy, err := client.Deploy(repoID, number, opt)
if err != nil {
return err
}

View file

@ -15,12 +15,13 @@
package pipeline
import (
"context"
"fmt"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var pipelineKillCmd = &cli.Command{
@ -31,14 +32,14 @@ var pipelineKillCmd = &cli.Command{
Hidden: true,
}
func pipelineKill(c *cli.Context) (err error) {
func pipelineKill(ctx context.Context, c *cli.Command) (err error) {
number, err := strconv.ParseInt(c.Args().Get(1), 10, 64)
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -47,7 +48,7 @@ func pipelineKill(c *cli.Context) (err error) {
return err
}
err = client.PipelineKill(repoID, number)
err = client.PipelineDelete(repoID, number)
if err != nil {
return err
}

View file

@ -15,16 +15,18 @@
package pipeline
import (
"github.com/urfave/cli/v2"
"context"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var pipelineLastCmd = &cli.Command{
Name: "last",
Usage: "show latest pipeline details",
Usage: "show latest pipeline information",
ArgsUsage: "<repo-id|repo-full-name>",
Action: pipelineLast,
Flags: append(common.OutputFlags("table"), []cli.Flag{
@ -36,9 +38,9 @@ var pipelineLastCmd = &cli.Command{
}...),
}
func pipelineLast(c *cli.Context) error {
func pipelineLast(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -47,10 +49,14 @@ func pipelineLast(c *cli.Context) error {
return err
}
pipeline, err := client.PipelineLast(repoID, c.String("branch"))
opt := woodpecker.PipelineLastOptions{
Branch: c.String("branch"),
}
pipeline, err := client.PipelineLast(repoID, opt)
if err != nil {
return err
}
return pipelineOutput(c, []woodpecker.Pipeline{*pipeline})
return pipelineOutput(c, []*woodpecker.Pipeline{pipeline})
}

View file

@ -15,88 +15,114 @@
package pipeline
import (
"github.com/urfave/cli/v2"
"context"
"time"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
//nolint:mnd
var pipelineListCmd = &cli.Command{
Name: "ls",
Usage: "show pipeline history",
ArgsUsage: "<repo-id|repo-full-name>",
Action: List,
Flags: append(common.OutputFlags("table"), []cli.Flag{
&cli.StringFlag{
Name: "branch",
Usage: "branch filter",
},
&cli.StringFlag{
Name: "event",
Usage: "event filter",
},
&cli.StringFlag{
Name: "status",
Usage: "status filter",
},
&cli.IntFlag{
Name: "limit",
Usage: "limit the list size",
Value: 25,
},
}...),
func buildPipelineListCmd() *cli.Command {
return &cli.Command{
Name: "ls",
Usage: "show pipeline history",
ArgsUsage: "<repo-id|repo-full-name>",
Action: List,
Flags: append(common.OutputFlags("table"), []cli.Flag{
&cli.StringFlag{
Name: "branch",
Usage: "branch filter",
},
&cli.StringFlag{
Name: "event",
Usage: "event filter",
},
&cli.StringFlag{
Name: "status",
Usage: "status filter",
},
&cli.IntFlag{
Name: "limit",
Usage: "limit the list size",
Value: 25,
},
&cli.TimestampFlag{
Name: "before",
Usage: "only return pipelines before this date (RFC3339)",
Config: cli.TimestampConfig{
Layouts: []string{
time.RFC3339,
},
},
},
&cli.TimestampFlag{
Name: "after",
Usage: "only return pipelines after this date (RFC3339)",
Config: cli.TimestampConfig{
Layouts: []string{
time.RFC3339,
},
},
},
}...),
}
}
func List(c *cli.Context) error {
client, err := internal.NewClient(c)
func List(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
resources, err := pipelineList(c, client)
pipelines, err := pipelineList(c, client)
if err != nil {
return err
}
return pipelineOutput(c, resources)
return pipelineOutput(c, pipelines)
}
func pipelineList(c *cli.Context, client woodpecker.Client) ([]woodpecker.Pipeline, error) {
resources := make([]woodpecker.Pipeline, 0)
func pipelineList(c *cli.Command, client woodpecker.Client) ([]*woodpecker.Pipeline, error) {
repoIDOrFullName := c.Args().First()
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return resources, err
return nil, err
}
pipelines, err := client.PipelineList(repoID)
if err != nil {
return resources, err
opt := woodpecker.PipelineListOptions{}
if before := c.Timestamp("before"); !before.IsZero() {
opt.Before = before
}
if after := c.Timestamp("after"); !after.IsZero() {
opt.After = after
}
branch := c.String("branch")
event := c.String("event")
status := c.String("status")
limit := c.Int("limit")
limit := int(c.Int("limit"))
var count int
for _, pipeline := range pipelines {
if count >= limit {
break
}
if branch != "" && pipeline.Branch != branch {
continue
}
if event != "" && pipeline.Event != event {
continue
}
if status != "" && pipeline.Status != status {
continue
}
resources = append(resources, *pipeline)
count++
pipelines, err := shared_utils.Paginate(func(page int) ([]*woodpecker.Pipeline, error) {
return client.PipelineList(repoID,
woodpecker.PipelineListOptions{
ListOptions: woodpecker.ListOptions{
Page: page,
},
Before: opt.Before,
After: opt.After,
Branch: branch,
Events: []string{event},
Status: status,
},
)
}, limit)
if err != nil {
return nil, err
}
return resources, nil
return pipelines, nil
}

View file

@ -1,16 +1,17 @@
package pipeline
import (
"context"
"errors"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker/mocks"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker/mocks"
)
func TestPipelineList(t *testing.T) {
@ -21,7 +22,7 @@ func TestPipelineList(t *testing.T) {
pipelines []*woodpecker.Pipeline
pipelineErr error
args []string
expected []woodpecker.Pipeline
expected []*woodpecker.Pipeline
wantErr error
}{
{
@ -33,53 +34,12 @@ func TestPipelineList(t *testing.T) {
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "repo/name"},
expected: []woodpecker.Pipeline{
expected: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
},
{
name: "filter by branch",
repoID: 1,
pipelines: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "--branch", "main", "repo/name"},
expected: []woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
},
{
name: "filter by event",
repoID: 1,
pipelines: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "--event", "push", "repo/name"},
expected: []woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
},
{
name: "filter by status",
repoID: 1,
pipelines: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "--status", "success", "repo/name"},
expected: []woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
},
},
{
name: "limit results",
repoID: 1,
@ -89,7 +49,7 @@ func TestPipelineList(t *testing.T) {
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "--limit", "2", "repo/name"},
expected: []woodpecker.Pipeline{
expected: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
},
@ -106,14 +66,20 @@ func TestPipelineList(t *testing.T) {
for _, tt := range testtases {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks.NewClient(t)
mockClient.On("PipelineList", mock.Anything).Return(tt.pipelines, tt.pipelineErr)
mockClient.On("PipelineList", mock.Anything, mock.Anything).Return(func(_ int64, opt woodpecker.PipelineListOptions) ([]*woodpecker.Pipeline, error) {
if tt.pipelineErr != nil {
return nil, tt.pipelineErr
}
if opt.Page == 1 {
return tt.pipelines, nil
}
return []*woodpecker.Pipeline{}, nil
}).Maybe()
mockClient.On("RepoLookup", mock.Anything).Return(&woodpecker.Repo{ID: tt.repoID}, nil)
app := &cli.App{Writer: io.Discard}
c := cli.NewContext(app, nil, nil)
command := pipelineListCmd
command.Action = func(c *cli.Context) error {
command := buildPipelineListCmd()
command.Writer = io.Discard
command.Action = func(_ context.Context, c *cli.Command) error {
pipelines, err := pipelineList(c, mockClient)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
@ -126,7 +92,7 @@ func TestPipelineList(t *testing.T) {
return nil
}
_ = command.Run(c, tt.args...)
_ = command.Run(context.Background(), tt.args)
})
}
}

View file

@ -15,14 +15,15 @@
package log
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Command exports the log command set.
var Command = &cli.Command{
Name: "log",
Usage: "manage logs",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
logPurgeCmd,
logShowCmd,
},
}

View file

@ -15,55 +15,64 @@
package log
import (
"context"
"fmt"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var logPurgeCmd = &cli.Command{
Name: "purge",
Usage: "purge a log",
ArgsUsage: "<repo-id|repo-full-name> <pipeline> [step]",
ArgsUsage: "<repo-id|repo-full-name> <pipeline> [step-number|step-name]",
Action: logPurge,
}
func logPurge(c *cli.Context) (err error) {
client, err := internal.NewClient(c)
func logPurge(ctx context.Context, c *cli.Command) (err error) {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
repoIDOrFullName := c.Args().First()
if len(repoIDOrFullName) == 0 {
return fmt.Errorf("missing required argument repo-id / repo-full-name")
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
return fmt.Errorf("invalid repo '%s': %w", repoIDOrFullName, err)
}
number, err := strconv.ParseInt(c.Args().Get(1), 10, 64)
pipelineArg := c.Args().Get(1)
if len(pipelineArg) == 0 {
return fmt.Errorf("missing required argument pipeline")
}
number, err := strconv.ParseInt(pipelineArg, 10, 64)
if err != nil {
return err
}
stepArg := c.Args().Get(2) //nolint:mnd
// TODO: Add lookup by name: stepID, err := internal.ParseStep(client, repoID, stepIDOrName)
var stepID int64
if len(stepArg) != 0 {
stepID, err = strconv.ParseInt(stepArg, 10, 64)
stepID, err = internal.ParseStep(client, repoID, number, stepArg)
if err != nil {
return err
}
}
if stepID > 0 {
fmt.Printf("Purging logs for pipeline %s#%d step %d\n", repoIDOrFullName, number, stepID)
err = client.StepLogsPurge(repoID, number, stepID)
} else {
fmt.Printf("Purging logs for pipeline %s#%d\n", repoIDOrFullName, number)
err = client.LogsPurge(repoID, number)
}
if err != nil {
return err
}
fmt.Printf("Purging logs for pipeline %s#%d\n", repoIDOrFullName, number)
return nil
}

View file

@ -0,0 +1,112 @@
// Copyright 2022 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package log
import (
"context"
"fmt"
"os"
"strconv"
"text/template"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var logShowCmd = &cli.Command{
Name: "show",
Usage: "show pipeline logs",
ArgsUsage: "<repo-id|repo-full-name> <pipeline> [step-number|step-name]",
Action: logShow,
}
func logShow(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
if len(repoIDOrFullName) == 0 {
return fmt.Errorf("missing required argument repo-id / repo-full-name")
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return fmt.Errorf("invalid repo '%s': %w ", repoIDOrFullName, err)
}
pipelineArg := c.Args().Get(1)
if len(pipelineArg) == 0 {
return fmt.Errorf("missing required argument pipeline")
}
number, err := strconv.ParseInt(pipelineArg, 10, 64)
if err != nil {
return fmt.Errorf("invalid pipeline '%s': %w", pipelineArg, err)
}
stepArg := c.Args().Get(2) //nolint:mnd
if len(stepArg) == 0 {
return pipelineLog(client, repoID, number)
}
step, err := internal.ParseStep(client, repoID, number, stepArg)
if err != nil {
return fmt.Errorf("invalid step '%s': %w", stepArg, err)
}
return stepLog(client, repoID, number, step)
}
func pipelineLog(client woodpecker.Client, repoID, number int64) error {
pipeline, err := client.Pipeline(repoID, number)
if err != nil {
return err
}
tmpl, err := template.New("_").Parse(tmplPipelineLogs + "\n")
if err != nil {
return err
}
for _, workflow := range pipeline.Workflows {
for _, step := range workflow.Children {
if err := tmpl.Execute(os.Stdout, map[string]any{"workflow": workflow, "step": step}); err != nil {
return err
}
err := stepLog(client, repoID, number, step.ID)
if err != nil {
return err
}
}
}
return nil
}
func stepLog(client woodpecker.Client, repoID, number, step int64) error {
logs, err := client.StepLogEntries(repoID, number, step)
if err != nil {
return err
}
for _, log := range logs {
fmt.Println(string(log.Data))
}
return nil
}
// template for pipeline ps information.
var tmplPipelineLogs = "\x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m"

View file

@ -1,66 +0,0 @@
// Copyright 2022 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pipeline
import (
"fmt"
"strconv"
"github.com/urfave/cli/v2"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var pipelineLogsCmd = &cli.Command{
Name: "logs",
Usage: "show pipeline logs",
ArgsUsage: "<repo-id|repo-full-name> [pipeline] [stepID]",
Action: pipelineLogs,
}
func pipelineLogs(c *cli.Context) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
}
numberArgIndex := 1
number, err := strconv.ParseInt(c.Args().Get(numberArgIndex), 10, 64)
if err != nil {
return err
}
stepArgIndex := 2
step, err := strconv.ParseInt(c.Args().Get(stepArgIndex), 10, 64)
if err != nil {
return err
}
logs, err := client.StepLogEntries(repoID, number, step)
if err != nil {
return err
}
for _, log := range logs {
fmt.Print(string(log.Data))
}
return nil
}

View file

@ -20,33 +20,37 @@ import (
"os"
"text/template"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/output"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/output"
"go.woodpecker-ci.org/woodpecker/v3/cli/pipeline/deploy"
"go.woodpecker-ci.org/woodpecker/v3/cli/pipeline/log"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
// Command exports the pipeline command set.
var Command = &cli.Command{
Name: "pipeline",
Usage: "manage pipelines",
Subcommands: []*cli.Command{
pipelineListCmd,
pipelineLastCmd,
pipelineLogsCmd,
pipelineInfoCmd,
pipelineStopCmd,
pipelineStartCmd,
Commands: []*cli.Command{
pipelineApproveCmd,
pipelineDeclineCmd,
pipelineQueueCmd,
pipelineKillCmd,
pipelinePsCmd,
pipelineCreateCmd,
pipelineDeclineCmd,
deploy.Command,
pipelineKillCmd,
pipelineLastCmd,
buildPipelineListCmd(),
log.Command,
pipelinePsCmd,
pipelinePurgeCmd,
pipelineQueueCmd,
pipelineShowCmd,
pipelineStartCmd,
pipelineStopCmd,
},
}
func pipelineOutput(c *cli.Context, resources []woodpecker.Pipeline, fd ...io.Writer) error {
func pipelineOutput(c *cli.Command, pipelines []*woodpecker.Pipeline, fd ...io.Writer) error {
outFmt, outOpt := output.ParseOutputOptions(c.String("output"))
noHeader := c.Bool("output-no-headers")
@ -70,7 +74,7 @@ func pipelineOutput(c *cli.Context, resources []woodpecker.Pipeline, fd ...io.Wr
if err != nil {
return err
}
if err := tmpl.Execute(out, resources); err != nil {
if err := tmpl.Execute(out, pipelines); err != nil {
return err
}
case "table":
@ -85,7 +89,7 @@ func pipelineOutput(c *cli.Context, resources []woodpecker.Pipeline, fd ...io.Wr
if !noHeader {
table.WriteHeader(cols)
}
for _, resource := range resources {
for _, resource := range pipelines {
if err := table.Write(cols, resource); err != nil {
return err
}

View file

@ -2,14 +2,15 @@ package pipeline
import (
"bytes"
"context"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
func TestPipelineOutput(t *testing.T) {
@ -22,7 +23,7 @@ func TestPipelineOutput(t *testing.T) {
{
name: "table output with default columns",
args: []string{},
expected: "NUMBER STATUS EVENT BRANCH MESSAGE AUTHOR\n1 success push main message John Doe\n",
expected: "NUMBER STATUS EVENT BRANCH MESSAGE AUTHOR\n1 success push main message multiline John Doe\n",
},
{
name: "table output with custom columns",
@ -32,7 +33,7 @@ func TestPipelineOutput(t *testing.T) {
{
name: "table output with no header",
args: []string{"output", "--output-no-headers"},
expected: "1 success push main message John Doe\n",
expected: "1 success push main message multiline John Doe\n",
},
{
name: "go-template output",
@ -46,41 +47,40 @@ func TestPipelineOutput(t *testing.T) {
},
}
pipelines := []woodpecker.Pipeline{
pipelines := []*woodpecker.Pipeline{
{
Number: 1,
Status: "success",
Event: "push",
Branch: "main",
Message: "message",
Author: "John Doe",
Message: "message\nmultiline",
Author: "John Doe\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := &cli.App{Writer: io.Discard}
c := cli.NewContext(app, nil, nil)
command := &cli.Command{
Writer: io.Discard,
Name: "output",
Flags: common.OutputFlags("table"),
Action: func(_ context.Context, c *cli.Command) error {
var buf bytes.Buffer
err := pipelineOutput(c, pipelines, &buf)
command := &cli.Command{}
command.Name = "output"
command.Flags = common.OutputFlags("table")
command.Action = func(c *cli.Context) error {
var buf bytes.Buffer
err := pipelineOutput(c, pipelines, &buf)
if tt.wantErr {
assert.Error(t, err)
return nil
}
assert.NoError(t, err)
assert.Equal(t, tt.expected, buf.String())
if tt.wantErr {
assert.Error(t, err)
return nil
}
assert.NoError(t, err)
assert.Equal(t, tt.expected, buf.String())
return nil
},
}
_ = command.Run(c, tt.args...)
_ = command.Run(context.Background(), tt.args)
})
}
}

View file

@ -15,33 +15,36 @@
package pipeline
import (
"context"
"fmt"
"os"
"strconv"
"text/template"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var pipelinePsCmd = &cli.Command{
Name: "ps",
Usage: "show pipeline steps",
ArgsUsage: "<repo-id|repo-full-name> [pipeline]",
ArgsUsage: "<repo-id|repo-full-name> <pipeline>",
Action: pipelinePs,
Flags: []cli.Flag{common.FormatFlag(tmplPipelinePs)},
}
func pipelinePs(c *cli.Context) error {
func pipelinePs(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return err
return fmt.Errorf("invalid repo '%s': %w", repoIDOrFullName, err)
}
pipelineArg := c.Args().Get(1)
@ -49,7 +52,7 @@ func pipelinePs(c *cli.Context) error {
if pipelineArg == "last" || len(pipelineArg) == 0 {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(repoID, "")
pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{})
if err != nil {
return err
}
@ -58,7 +61,7 @@ func pipelinePs(c *cli.Context) error {
} else {
number, err = strconv.ParseInt(pipelineArg, 10, 64)
if err != nil {
return err
return fmt.Errorf("invalid pipeline '%s': %w", pipelineArg, err)
}
}
@ -72,9 +75,9 @@ func pipelinePs(c *cli.Context) error {
return err
}
for _, step := range pipeline.Workflows {
for _, child := range step.Children {
if err := tmpl.Execute(os.Stdout, child); err != nil {
for _, workflow := range pipeline.Workflows {
for _, step := range workflow.Children {
if err := tmpl.Execute(os.Stdout, map[string]any{"workflow": workflow, "step": step}); err != nil {
return err
}
}
@ -83,8 +86,11 @@ func pipelinePs(c *cli.Context) error {
return nil
}
// Template for pipeline ps information.
var tmplPipelinePs = "\x1b[33mStep #{{ .PID }} \x1b[0m" + `
Step: {{ .Name }}
State: {{ .State }}
// template for pipeline ps information.
var tmplPipelinePs = "\x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m" + `
Step: {{ .step.Name }}
Started: {{ .step.Started }}
Stopped: {{ .step.Stopped }}
Type: {{ .step.Type }}
State: {{ .step.State }}
`

164
cli/pipeline/purge.go Normal file
View file

@ -0,0 +1,164 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pipeline
import (
"context"
"errors"
"fmt"
"net/http"
"time"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
//nolint:mnd
var pipelinePurgeCmd = &cli.Command{
Name: "purge",
Usage: "purge pipelines",
ArgsUsage: "<repo-id|repo-full-name>",
Action: Purge,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "older-than",
Usage: "remove pipelines older than the specified time limit",
Required: true,
},
&cli.IntFlag{
Name: "keep-min",
Usage: "minimum number of pipelines to keep",
Value: 10,
},
&cli.BoolFlag{
Name: "dry-run",
Usage: "disable non-read api calls",
Value: false,
},
},
}
func Purge(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
return pipelinePurge(c, client)
}
func pipelinePurge(c *cli.Command, client woodpecker.Client) (err error) {
repoIDOrFullName := c.Args().First()
if len(repoIDOrFullName) == 0 {
return fmt.Errorf("missing required argument repo-id / repo-full-name")
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return fmt.Errorf("invalid repo '%s': %w", repoIDOrFullName, err)
}
olderThan := c.String("older-than")
keepMin := c.Int("keep-min")
dryRun := c.Bool("dry-run")
duration, err := time.ParseDuration(olderThan)
if err != nil {
return err
}
var pipelinesKeep []*woodpecker.Pipeline
if keepMin > 0 {
pipelinesKeep, err = fetchPipelinesToKeep(client, repoID, int(keepMin))
if err != nil {
return err
}
}
pipelines, err := fetchPipelines(client, repoID, duration)
if err != nil {
return err
}
// Create a map of pipeline IDs to keep
keepMap := make(map[int64]struct{})
for _, p := range pipelinesKeep {
keepMap[p.Number] = struct{}{}
}
// Filter pipelines to only include those not in keepMap
var pipelinesToPurge []*woodpecker.Pipeline
for _, p := range pipelines {
if _, exists := keepMap[p.Number]; !exists {
pipelinesToPurge = append(pipelinesToPurge, p)
}
}
msgPrefix := ""
if dryRun {
msgPrefix = "DRY-RUN: "
}
for i, p := range pipelinesToPurge {
// cspell:words spurge
log.Debug().Msgf("%spurge %v/%v pipelines from repo '%v' (pipeline %v)", msgPrefix, i+1, len(pipelinesToPurge), repoIDOrFullName, p.Number)
if dryRun {
continue
}
err := client.PipelineDelete(repoID, p.Number)
if err != nil {
var clientErr *woodpecker.ClientError
if errors.As(err, &clientErr) && clientErr.StatusCode == http.StatusUnprocessableEntity {
log.Error().Err(err).Msgf("failed to delete pipeline %d", p.Number)
continue
}
return err
}
}
return nil
}
func fetchPipelinesToKeep(client woodpecker.Client, repoID int64, keepMin int) ([]*woodpecker.Pipeline, error) {
if keepMin <= 0 {
return nil, nil
}
return shared_utils.Paginate(func(page int) ([]*woodpecker.Pipeline, error) {
return client.PipelineList(repoID,
woodpecker.PipelineListOptions{
ListOptions: woodpecker.ListOptions{
Page: page,
},
},
)
}, keepMin)
}
func fetchPipelines(client woodpecker.Client, repoID int64, duration time.Duration) ([]*woodpecker.Pipeline, error) {
return shared_utils.Paginate(func(page int) ([]*woodpecker.Pipeline, error) {
return client.PipelineList(repoID,
woodpecker.PipelineListOptions{
ListOptions: woodpecker.ListOptions{
Page: page,
},
Before: time.Now().Add(-duration),
},
)
}, -1)
}

126
cli/pipeline/purge_test.go Normal file
View file

@ -0,0 +1,126 @@
package pipeline
import (
"context"
"errors"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker/mocks"
)
func TestPipelinePurge(t *testing.T) {
tests := []struct {
name string
repoID int64
args []string
pipelinesKeep []*woodpecker.Pipeline
pipelines []*woodpecker.Pipeline
mockDeleteError error
wantDelete int
wantErr error
}{
{
name: "success with no pipelines to purge",
repoID: 1,
args: []string{"purge", "--older-than", "1h", "repo/name"},
pipelinesKeep: []*woodpecker.Pipeline{
{Number: 1},
},
pipelines: []*woodpecker.Pipeline{},
},
{
name: "success with pipelines to purge",
repoID: 1,
args: []string{"purge", "--older-than", "1h", "repo/name"},
pipelinesKeep: []*woodpecker.Pipeline{
{Number: 1},
},
pipelines: []*woodpecker.Pipeline{
{Number: 1},
{Number: 2},
{Number: 3},
},
wantDelete: 2,
},
{
name: "error on invalid duration",
repoID: 1,
args: []string{"purge", "--older-than", "invalid", "repo/name"},
wantErr: errors.New("time: invalid duration \"invalid\""),
},
{
name: "continue on 422 error",
repoID: 1,
args: []string{"purge", "--older-than", "1h", "repo/name"},
pipelinesKeep: []*woodpecker.Pipeline{
{Number: 1},
},
pipelines: []*woodpecker.Pipeline{
{Number: 1},
{Number: 2},
{Number: 3},
},
wantDelete: 2,
mockDeleteError: &woodpecker.ClientError{
StatusCode: 422,
Message: "test error",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks.NewClient(t)
mockClient.On("RepoLookup", mock.Anything).Maybe().Return(&woodpecker.Repo{ID: tt.repoID}, nil)
mockClient.On("PipelineList", mock.Anything, mock.Anything).Return(func(_ int64, opt woodpecker.PipelineListOptions) ([]*woodpecker.Pipeline, error) {
// Return keep pipelines for first call
if opt.Before.IsZero() {
if opt.Page == 1 {
return tt.pipelinesKeep, nil
}
return []*woodpecker.Pipeline{}, nil
}
// Return pipelines to purge for calls with Before filter
if !opt.Before.IsZero() {
if opt.Page == 1 {
return tt.pipelines, nil
}
return []*woodpecker.Pipeline{}, nil
}
return []*woodpecker.Pipeline{}, nil
}).Maybe()
if tt.mockDeleteError != nil {
mockClient.On("PipelineDelete", tt.repoID, mock.Anything).Return(tt.mockDeleteError)
} else if tt.wantDelete > 0 {
mockClient.On("PipelineDelete", tt.repoID, mock.Anything).Return(nil).Times(tt.wantDelete)
}
command := pipelinePurgeCmd
command.Writer = io.Discard
command.Action = func(_ context.Context, c *cli.Command) error {
err := pipelinePurge(c, mockClient)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
return nil
}
assert.NoError(t, err)
return nil
}
_ = command.Run(context.Background(), tt.args)
})
}
}

View file

@ -15,14 +15,15 @@
package pipeline
import (
"context"
"fmt"
"os"
"text/template"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var pipelineQueueCmd = &cli.Command{
@ -33,8 +34,8 @@ var pipelineQueueCmd = &cli.Command{
Flags: []cli.Flag{common.FormatFlag(tmplPipelineQueue)},
}
func pipelineQueue(c *cli.Context) error {
client, err := internal.NewClient(c)
func pipelineQueue(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,26 +15,27 @@
package pipeline
import (
"context"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var pipelineInfoCmd = &cli.Command{
Name: "info",
Usage: "show pipeline details",
var pipelineShowCmd = &cli.Command{
Name: "show",
Usage: "show pipeline information",
ArgsUsage: "<repo-id|repo-full-name> [pipeline]",
Action: pipelineInfo,
Action: pipelineShow,
Flags: common.OutputFlags("table"),
}
func pipelineInfo(c *cli.Context) error {
func pipelineShow(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -47,7 +48,7 @@ func pipelineInfo(c *cli.Context) error {
var number int64
if pipelineArg == "last" || len(pipelineArg) == 0 {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(repoID, "")
pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{})
if err != nil {
return err
}
@ -64,5 +65,5 @@ func pipelineInfo(c *cli.Context) error {
return err
}
return pipelineOutput(c, []woodpecker.Pipeline{*pipeline})
return pipelineOutput(c, []*woodpecker.Pipeline{pipeline})
}

View file

@ -15,13 +15,15 @@
package pipeline
import (
"context"
"errors"
"fmt"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var pipelineStartCmd = &cli.Command{
@ -33,14 +35,14 @@ var pipelineStartCmd = &cli.Command{
&cli.StringSliceFlag{
Name: "param",
Aliases: []string{"p"},
Usage: "custom parameters to be injected into the step environment. Format: KEY=value",
Usage: "custom parameters to inject into the step environment. Format: KEY=value",
},
},
}
func pipelineStart(c *cli.Context) (err error) {
func pipelineStart(ctx context.Context, c *cli.Command) (err error) {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -53,7 +55,7 @@ func pipelineStart(c *cli.Context) (err error) {
var number int64
if pipelineArg == "last" {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(repoID, "")
pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{})
if err != nil {
return err
}
@ -68,9 +70,11 @@ func pipelineStart(c *cli.Context) (err error) {
}
}
params := internal.ParseKeyPair(c.StringSlice("param"))
opt := woodpecker.PipelineStartOptions{
Params: internal.ParseKeyPair(c.StringSlice("param")),
}
pipeline, err := client.PipelineStart(repoID, number, params)
pipeline, err := client.PipelineStart(repoID, number, opt)
if err != nil {
return err
}

View file

@ -15,12 +15,13 @@
package pipeline
import (
"context"
"fmt"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
)
var pipelineStopCmd = &cli.Command{
@ -30,9 +31,9 @@ var pipelineStopCmd = &cli.Command{
Action: pipelineStop,
}
func pipelineStop(c *cli.Context) (err error) {
func pipelineStop(ctx context.Context, c *cli.Command) (err error) {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,18 +15,18 @@
package cron
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Command exports the cron command set.
var Command = &cli.Command{
Name: "cron",
Usage: "manage cron jobs",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
cronCreateCmd,
cronDeleteCmd,
cronUpdateCmd,
cronInfoCmd,
cronListCmd,
cronShowCmd,
cronUpdateCmd,
},
}

View file

@ -15,14 +15,15 @@
package cron
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var cronCreateCmd = &cli.Command{
@ -50,9 +51,9 @@ var cronCreateCmd = &cli.Command{
},
}
func cronCreate(c *cli.Context) error {
func cronCreate(ctx context.Context, c *cli.Command) error {
var (
jobName = c.String("name")
cronName = c.String("name")
branch = c.String("branch")
schedule = c.String("schedule")
repoIDOrFullName = c.String("repository")
@ -62,7 +63,7 @@ func cronCreate(c *cli.Context) error {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -73,7 +74,7 @@ func cronCreate(c *cli.Context) error {
}
cron := &woodpecker.Cron{
Name: jobName,
Name: cronName,
Branch: branch,
Schedule: schedule,
}

View file

@ -15,13 +15,15 @@
package cron
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/cli/common"
"go.woodpecker-ci.org/woodpecker/v3/cli/internal"
"go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker"
)
var cronListCmd = &cli.Command{
@ -35,7 +37,7 @@ var cronListCmd = &cli.Command{
},
}
func cronList(c *cli.Context) error {
func cronList(ctx context.Context, c *cli.Command) error {
var (
format = c.String("format") + "\n"
repoIDOrFullName = c.String("repository")
@ -43,7 +45,7 @@ func cronList(c *cli.Context) error {
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -51,7 +53,8 @@ func cronList(c *cli.Context) error {
if err != nil {
return err
}
list, err := client.CronList(repoID)
opt := woodpecker.CronListOptions{}
list, err := client.CronList(repoID, opt)
if err != nil {
return err
}

Some files were not shown because too many files have changed in this diff Show more