Merge remote-tracking branch 'upstream/main' into github-refresh

This commit is contained in:
Anbraten 2024-09-24 18:58:50 +02:00
commit 03a87a087e
No known key found for this signature in database
GPG key ID: B1222603899C6B25
619 changed files with 21084 additions and 12013 deletions

View file

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

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.
-->

10
.github/renovate.json vendored
View file

@ -1,6 +1,16 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>woodpecker-ci/renovate-config"], "extends": ["github>woodpecker-ci/renovate-config"],
"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>.*)\""
],
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}"
}
],
"packageRules": [ "packageRules": [
{ {
"matchCurrentVersion": "<1.0.0", "matchCurrentVersion": "<1.0.0",

View file

@ -184,3 +184,5 @@ issues:
run: run:
timeout: 15m timeout: 15m
build-tags:
- test

View file

@ -24,11 +24,11 @@ repos:
- id: checkmake - id: checkmake
exclude: '^docker/Dockerfile.make$' # actually a Dockerfile and not a makefile exclude: '^docker/Dockerfile.make$' # actually a Dockerfile and not a makefile
- repo: https://github.com/hadolint/hadolint - repo: https://github.com/hadolint/hadolint
rev: v2.12.0 rev: v2.13.0-beta
hooks: hooks:
- id: hadolint - id: hadolint
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0 rev: v4.0.0-alpha.8
hooks: hooks:
- id: prettier - id: prettier
- repo: https://github.com/adrienverge/yamllint.git - repo: https://github.com/adrienverge/yamllint.git

View file

@ -8,11 +8,9 @@
}, },
"go.lintTool": "golangci-lint", "go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"], "go.lintFlags": ["--fast"],
"go.buildTags": "test",
"eslint.workingDirectories": ["./web"], "eslint.workingDirectories": ["./web"],
"prettier.ignorePath": "./web/.prettierignore", "prettier.ignorePath": "./web/.prettierignore",
// Enable the ESlint flat config support
// (remove this if your ESLint extension above v3.0.5)
"eslint.experimental.useFlatConfig": true,
// Auto fix // Auto fix
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit", "source.fixAll.eslint": "explicit",

View file

@ -1,12 +1,15 @@
when: when:
event: tag - event: tag
- event: pull_request
branch: ${CI_REPO_DEFAULT_BRANCH}
path: Makefile
variables: variables:
- &golang_image 'docker.io/golang:1.22' - &golang_image 'docker.io/golang:1.23'
- &node_image 'docker.io/node:22-alpine' - &node_image 'docker.io/node:22-alpine'
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x' - &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x'
# cspell:words bindata netgo TARGZ # cspell:words bindata netgo
steps: steps:
build-web: build-web:
@ -35,7 +38,7 @@ steps:
environment: environment:
PLATFORMS: linux|arm64/v8;linux|amd64;windows|amd64 PLATFORMS: linux|arm64/v8;linux|amd64;windows|amd64
TAGS: bindata sqlite sqlite_unlock_notify netgo TAGS: bindata sqlite sqlite_unlock_notify netgo
TARGZ: '1' ARCHIVE_IT: '1'
build-tarball: build-tarball:
depends_on: depends_on:
@ -50,6 +53,8 @@ steps:
- vendor - vendor
image: *golang_image image: *golang_image
commands: commands:
- apt update
- apt install -y zip
- make release-agent - make release-agent
build-cli: build-cli:
@ -57,6 +62,8 @@ steps:
- vendor - vendor
image: *golang_image image: *golang_image
commands: commands:
- apt update
- apt install -y zip
- make release-cli - make release-cli
build-deb-rpm: build-deb-rpm:
@ -96,7 +103,10 @@ steps:
from_secret: github_token from_secret: github_token
files: files:
- dist/*.tar.gz - dist/*.tar.gz
- dist/*.zip
- dist/*.deb - dist/*.deb
- dist/*.rpm - dist/*.rpm
- dist/checksums.txt - dist/checksums.txt
title: ${CI_COMMIT_TAG##v} title: ${CI_COMMIT_TAG##v}
when:
event: tag

View file

@ -1,15 +1,15 @@
variables: variables:
- &golang_image 'docker.io/golang:1.22' - &golang_image 'docker.io/golang:1.23'
- &node_image 'docker.io/node:22-alpine' - &node_image 'docker.io/node:22-alpine'
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x' - &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x'
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:4.0.0' - &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:4.2.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_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_server 'linux/arm/v7,linux/arm64/v8,linux/amd64,linux/ppc64le,linux/riscv64'
- &platforms_preview 'linux/amd64' - &platforms_preview 'linux/amd64'
- &platforms_alpine 'linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/amd64,linux/ppc64le' - &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}' - &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 # vars used on push / tag events only
- publish_logins: &publish_logins # Default DockerHub login - publish_logins: &publish_logins # Default DockerHub login

View file

@ -1,6 +1,6 @@
steps: steps:
release-helper: release-helper:
image: woodpeckerci/plugin-ready-release-go:1.1.2 image: woodpeckerci/plugin-ready-release-go:1.2.0
pull: true pull: true
settings: settings:
release_branch: ${CI_REPO_DEFAULT_BRANCH} release_branch: ${CI_REPO_DEFAULT_BRANCH}

View file

@ -5,7 +5,7 @@ when:
steps: steps:
- name: lint-editorconfig - name: lint-editorconfig
image: docker.io/mstruebing/editorconfig-checker:v3.0.1 image: docker.io/mstruebing/editorconfig-checker:v3.0.3
depends_on: [] depends_on: []
when: when:
- event: pull_request - event: pull_request
@ -23,15 +23,15 @@ steps:
- tree --gitignore -I 012_columns_rename_procs_to_steps.go -I versioned_docs -I '*opensource.svg'| pnpx cspell lint --no-progress stdin - tree --gitignore -I 012_columns_rename_procs_to_steps.go -I versioned_docs -I '*opensource.svg'| pnpx cspell lint --no-progress stdin
- name: prettier - name: prettier
image: docker.io/woodpeckerci/plugin-prettier:0.1.0 image: docker.io/woodpeckerci/plugin-prettier:0.2.0
depends_on: [] depends_on: []
settings: settings:
version: 3.2.5 version: 3.3.3
- name: links - name: links
image: docker.io/lycheeverse/lychee:0.15.1 image: docker.io/lycheeverse/lychee:0.15.1
depends_on: [] depends_on: []
commands: commands:
- lychee pipeline/frontend/yaml/linter/schema/schema.json - lychee pipeline/frontend/yaml/linter/schema/schema.json
- lychee --exclude localhost docs/docs/ - lychee --user-agent "curl/8.4.0" --exclude localhost docs/docs/
- lychee --exclude localhost docs/src/pages/ - lychee --user-agent "curl/8.4.0" --exclude localhost docs/src/pages/

View file

@ -40,6 +40,7 @@ steps:
- go run go.woodpecker-ci.org/woodpecker/v2/cmd/cli lint - go run go.woodpecker-ci.org/woodpecker/v2/cmd/cli lint
environment: environment:
WOODPECKER_DISABLE_UPDATE_CHECK: true WOODPECKER_DISABLE_UPDATE_CHECK: true
WOODPECKER_PLUGINS_PRIVILEGED: 'docker.io/woodpeckerci/plugin-docker-buildx:4.2.0'
when: when:
- event: pull_request - event: pull_request
path: path:

View file

@ -58,6 +58,7 @@ steps:
test: test:
depends_on: depends_on:
- install-dependencies - install-dependencies
- format-check # wait for it else test artifacts are falsely detected as wrong
image: *node_image image: *node_image
directory: web/ directory: web/
commands: commands:

View file

@ -1,5 +1,133 @@
# Changelog # Changelog
## [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)]
- 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 ## [2.6.0](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.6.0) - 2024-06-13
### ❤️ Thanks to all contributors! ❤️ ### ❤️ Thanks to all contributors! ❤️

134
Makefile
View file

@ -8,6 +8,8 @@ ifeq ($(TARGETOS),windows)
BIN_SUFFIX := .exe BIN_SUFFIX := .exe
endif endif
DIST_DIR ?= dist
VERSION ?= next VERSION ?= next
VERSION_NUMBER ?= 0.0.0 VERSION_NUMBER ?= 0.0.0
CI_COMMIT_SHA ?= $(shell git rev-parse HEAD) CI_COMMIT_SHA ?= $(shell git rev-parse HEAD)
@ -27,6 +29,7 @@ else
endif endif
endif endif
TAGS ?=
LDFLAGS := -X go.woodpecker-ci.org/woodpecker/v2/version.Version=${VERSION} LDFLAGS := -X go.woodpecker-ci.org/woodpecker/v2/version.Version=${VERSION}
STATIC_BUILD ?= true STATIC_BUILD ?= true
ifeq ($(STATIC_BUILD),true) ifeq ($(STATIC_BUILD),true)
@ -103,7 +106,7 @@ clean: ## Clean build artifacts
.PHONY: clean-all .PHONY: clean-all
clean-all: clean ## Clean all artifacts clean-all: clean ## Clean all artifacts
rm -rf dist web/dist docs/build docs/node_modules web/node_modules rm -rf ${DIST_DIR} web/dist docs/build docs/node_modules web/node_modules
# delete generated # delete generated
rm -rf docs/docs/40-cli.md docs/swagger.json rm -rf docs/docs/40-cli.md docs/swagger.json
@ -160,20 +163,20 @@ lint-ui: ui-dependencies ## Lint UI code
(cd web/; pnpm lint --quiet) (cd web/; pnpm lint --quiet)
test-agent: ## Test agent code test-agent: ## Test agent code
go test -race -cover -coverprofile agent-coverage.out -timeout 30s 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/v2/cmd/agent go.woodpecker-ci.org/woodpecker/v2/agent/...
test-server: ## Test server code test-server: ## Test server code
go test -race -cover -coverprofile server-coverage.out -timeout 30s 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/v2/cmd/server $(shell go list go.woodpecker-ci.org/woodpecker/v2/server/... | grep -v '/store')
test-cli: ## Test cli code test-cli: ## Test cli code
go test -race -cover -coverprofile cli-coverage.out -timeout 30s 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/v2/cmd/cli go.woodpecker-ci.org/woodpecker/v2/cli/...
test-server-datastore: ## Test server datastore test-server-datastore: ## Test server datastore
go test -timeout 120s -run TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/... go test -timeout 300s -tags 'test $(TAGS)' -run TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/...
go test -race -timeout 30s -skip 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/...
test-server-datastore-coverage: ## Test server datastore with coverage report test-server-datastore-coverage: ## Test server datastore with coverage report
go test -race -cover -coverprofile datastore-coverage.out -timeout 180s 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/v2/server/store/...
test-ui: ui-dependencies ## Test UI code test-ui: ui-dependencies ## Test UI code
(cd web/; pnpm run lint) (cd web/; pnpm run lint)
@ -182,7 +185,7 @@ test-ui: ui-dependencies ## Test UI code
(cd web/; pnpm run test) (cd web/; pnpm run test)
test-lib: ## Test lib code test-lib: ## Test lib code
go test -race -cover -coverprofile coverage.out -timeout 30s $(shell go list ./... | grep -v '/cmd\|/agent\|/cli\|/server') go test -race -cover -coverprofile coverage.out -timeout 60s -tags 'test $(TAGS)' $(shell go list ./... | grep -v '/cmd\|/agent\|/cli\|/server')
.PHONY: test .PHONY: test
test: test-agent test-server test-server-datastore test-cli test-lib ## Run all tests test: test-agent test-server test-server-datastore test-cli test-lib ## Run all tests
@ -193,16 +196,16 @@ build-ui: ## Build UI
(cd web/; pnpm install --frozen-lockfile; pnpm build) (cd web/; pnpm install --frozen-lockfile; pnpm build)
build-server: build-ui generate-swagger ## Build server build-server: build-ui generate-swagger ## Build server
CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '${LDFLAGS}' -o dist/woodpecker-server${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/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-agent: ## Build agent build-agent: ## Build agent
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '${LDFLAGS}' -o dist/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/v2/cmd/agent
build-cli: ## Build cli build-cli: ## Build cli
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '${LDFLAGS}' -o dist/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/v2/cmd/cli
build-tarball: ## Build tar archive build-tarball: ## Build tar archive
mkdir -p dist && tar chzvf dist/woodpecker-src.tar.gz \ mkdir -p ${DIST_DIR} && tar chzvf ${DIST_DIR}/woodpecker-src.tar.gz \
--exclude="*.exe" \ --exclude="*.exe" \
--exclude="./.pnpm-store" \ --exclude="./.pnpm-store" \
--exclude="node_modules" \ --exclude="node_modules" \
@ -224,7 +227,7 @@ cross-compile-server: ## Cross compile the server
TARGETARCH_BUILDX=$(subst arm64/v8,arm64,$(subst arm/v7,arm,$(word 2,$(subst |, ,$(platform))))) \ TARGETARCH_BUILDX=$(subst arm64/v8,arm64,$(subst arm/v7,arm,$(word 2,$(subst |, ,$(platform))))) \
make release-server-xgo || exit 1; \ make release-server-xgo || exit 1; \
) )
tree dist tree ${DIST_DIR}
release-server-xgo: check-xgo ## Create server binaries for release using xgo release-server-xgo: check-xgo ## Create server binaries for release using xgo
@echo "Building for:" @echo "Building for:"
@ -232,58 +235,79 @@ release-server-xgo: check-xgo ## Create server binaries for release using xgo
@echo "arch orgi:$(TARGETARCH)" @echo "arch orgi:$(TARGETARCH)"
@echo "arch (xgo):$(TARGETARCH_XGO)" @echo "arch (xgo):$(TARGETARCH_XGO)"
@echo "arch (buildx):$(TARGETARCH_BUILDX)" @echo "arch (buildx):$(TARGETARCH_BUILDX)"
# build via xgo
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest ./dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX) -tags 'netgo osusergo grpcnotrace $(TAGS)' -ldflags '-linkmode external $(LDFLAGS)' -targets '$(TARGETOS)/$(TARGETARCH_XGO)' -out woodpecker-server -pkg cmd/server . 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
mkdir -p ./dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX); \ @if [ "$${XGO_IN_XGO:-0}" -eq "1" ]; then \
mv -vf /build/woodpecker-server* ./dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \ echo "inside xgo image"; \
else echo "outside xgo image"; \ mkdir -p ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX); \
[ -f "./dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX)" ] && rm -v ./dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \ mv -vf /build/woodpecker-server* ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \
mv -v ./dist/server/$(TARGETOS)_$(TARGETARCH_XGO)/woodpecker-server* ./dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \ 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
# 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 fi
@[ "$${TARGZ:-0}" -eq "1" ] && tar -cvzf dist/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz -C dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX) woodpecker-server$(BIN_SUFFIX) || echo "skip creating 'dist/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz'"
release-server: ## Create server binaries for release release-server: ## Create server binaries for release
# compile # compile
GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=${CGO_ENABLED} go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o dist/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/v2/cmd/server
# tar binary files # tar binary files
tar -cvzf dist/woodpecker-server_$(TARGETOS)_$(TARGETARCH).tar.gz -C dist/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 release-agent: ## Create agent binaries for release
# compile # compile
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o dist/agent/linux_amd64/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/v2/cmd/agent
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o dist/agent/linux_arm64/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/agent/linux_arm/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/agent/windows_amd64/woodpecker-agent.exe 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/agent/darwin_amd64/woodpecker-agent 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/agent/darwin_arm64/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
# tar binary files # tar binary files
tar -cvzf dist/woodpecker-agent_linux_amd64.tar.gz -C dist/agent/linux_amd64 woodpecker-agent tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_amd64.tar.gz -C ${DIST_DIR}/agent/linux_amd64 woodpecker-agent
tar -cvzf dist/woodpecker-agent_linux_arm64.tar.gz -C dist/agent/linux_arm64 woodpecker-agent tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_arm64.tar.gz -C ${DIST_DIR}/agent/linux_arm64 woodpecker-agent
tar -cvzf dist/woodpecker-agent_linux_arm.tar.gz -C dist/agent/linux_arm woodpecker-agent tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_arm.tar.gz -C ${DIST_DIR}/agent/linux_arm woodpecker-agent
tar -cvzf dist/woodpecker-agent_windows_amd64.tar.gz -C dist/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/woodpecker-agent_darwin_amd64.tar.gz -C dist/agent/darwin_amd64 woodpecker-agent tar -cvzf ${DIST_DIR}/woodpecker-agent_darwin_arm64.tar.gz -C ${DIST_DIR}/agent/darwin_arm64 woodpecker-agent
tar -cvzf dist/woodpecker-agent_darwin_arm64.tar.gz -C dist/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 release-cli: ## Create cli binaries for release
# compile # compile
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o dist/cli/linux_amd64/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/v2/cmd/cli
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o dist/cli/linux_arm64/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/cli/linux_arm/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/cli/windows_amd64/woodpecker-cli.exe 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/cli/darwin_amd64/woodpecker-cli 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/cli/darwin_arm64/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
# tar binary files # tar binary files
tar -cvzf dist/woodpecker-cli_linux_amd64.tar.gz -C dist/cli/linux_amd64 woodpecker-cli tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_amd64.tar.gz -C ${DIST_DIR}/cli/linux_amd64 woodpecker-cli
tar -cvzf dist/woodpecker-cli_linux_arm64.tar.gz -C dist/cli/linux_arm64 woodpecker-cli tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_arm64.tar.gz -C ${DIST_DIR}/cli/linux_arm64 woodpecker-cli
tar -cvzf dist/woodpecker-cli_linux_arm.tar.gz -C dist/cli/linux_arm woodpecker-cli tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_arm.tar.gz -C ${DIST_DIR}/cli/linux_arm woodpecker-cli
tar -cvzf dist/woodpecker-cli_windows_amd64.tar.gz -C dist/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/woodpecker-cli_darwin_amd64.tar.gz -C dist/cli/darwin_amd64 woodpecker-cli tar -cvzf ${DIST_DIR}/woodpecker-cli_darwin_arm64.tar.gz -C ${DIST_DIR}/cli/darwin_arm64 woodpecker-cli
tar -cvzf dist/woodpecker-cli_darwin_arm64.tar.gz -C dist/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 release-checksums: ## Create checksums for all release files
# generate shas for tar files # generate shas for tar files
(cd dist/; sha256sum *.* > checksums.txt) (cd ${DIST_DIR}/; sha256sum *.* > checksums.txt)
.PHONY: release .PHONY: release
release: release-frontend release-server release-agent release-cli ## Release all binaries release: release-frontend release-server release-agent release-cli ## Release all binaries
@ -292,16 +316,16 @@ bundle-prepare: ## Prepare the bundles
go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.6.0 go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.6.0
bundle-agent: bundle-prepare ## Create bundles for agent bundle-agent: bundle-prepare ## Create bundles for agent
VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/agent.yaml --target ./dist --packager deb VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/agent.yaml --target ${DIST_DIR} --packager deb
VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/agent.yaml --target ./dist --packager rpm VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/agent.yaml --target ${DIST_DIR} --packager rpm
bundle-server: bundle-prepare ## Create bundles for server bundle-server: bundle-prepare ## Create bundles for server
VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/server.yaml --target ./dist --packager deb VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/server.yaml --target ${DIST_DIR} --packager deb
VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/server.yaml --target ./dist --packager rpm VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/server.yaml --target ${DIST_DIR} --packager rpm
bundle-cli: bundle-prepare ## Create bundles for cli bundle-cli: bundle-prepare ## Create bundles for cli
VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/cli.yaml --target ./dist --packager deb VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/cli.yaml --target ${DIST_DIR} --packager deb
VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/cli.yaml --target ./dist --packager rpm VERSION_NUMBER=$(VERSION_NUMBER) nfpm package --config ./nfpm/cli.yaml --target ${DIST_DIR} --packager rpm
.PHONY: bundle .PHONY: bundle
bundle: bundle-agent bundle-server bundle-cli ## Create all bundles bundle: bundle-agent bundle-server bundle-cli ## Create all bundles

View file

@ -43,55 +43,48 @@
</p> </p>
<br/> <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> <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/"> ## Plugins
<img src="https://translate.woodpecker-ci.org/widgets/woodpecker-ci/-/ui/multi-blue.svg" alt="Translation status" />
</a>
## 👋 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. [![Star History Chart](https://api.star-history.com/svg?repos=woodpecker-ci/woodpecker&type=Date)](https://star-history.com/#woodpecker-ci/woodpecker&Date)
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)
## License ## 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

@ -26,17 +26,12 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" "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
)
func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, workflow *rpc.Workflow) pipeline.Logger { func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, workflow *rpc.Workflow) pipeline.Logger {
return func(step *backend.Step, rc io.Reader) error { return func(step *backend.Step, rc io.ReadCloser) error {
defer rc.Close()
logger := _logger.With(). logger := _logger.With().
Str("image", step.Image). Str("image", step.Image).
Str("workflowID", workflow.ID).
Logger() Logger()
uploads.Add(1) uploads.Add(1)
@ -49,7 +44,7 @@ func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, w
logger.Debug().Msg("log stream opened") logger.Debug().Msg("log stream opened")
logStream := log.NewLineWriter(r.client, step.UUID, secrets...) 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") logger.Error().Err(err).Msg("copy limited logStream part")
} }

View file

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

View file

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

View file

@ -25,29 +25,44 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
grpcproto "google.golang.org/protobuf/proto"
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" 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"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto" "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto"
) )
// Set grpc version on compile time to compare against server version response. const (
const ClientGrpcVersion int32 = proto.Version // 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 { type client struct {
client proto.WoodpeckerClient client proto.WoodpeckerClient
conn *grpc.ClientConn conn *grpc.ClientConn
logs chan *proto.LogEntry
} }
// NewGrpcClient returns a new grpc Client. // 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 := new(client)
client.client = proto.NewWoodpeckerClient(conn) client.client = proto.NewWoodpeckerClient(conn)
client.conn = conn client.conn = conn
client.logs = make(chan *proto.LogEntry, 10) // max memory use: 10 lines * 1 MiB
go client.processLogs(ctx)
return client return client
} }
func (c *client) Close() error { func (c *client) Close() error {
close(c.logs)
return c.conn.Close() return c.conn.Close()
} }
@ -72,28 +87,28 @@ func (c *client) Version(ctx context.Context) (*rpc.Version, error) {
} }
// Next returns the next workflow in the queue. // 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 res *proto.NextResponse
var err error var err error
retry := c.newBackOff() retry := c.newBackOff()
req := new(proto.NextRequest) req := new(proto.NextRequest)
req.Filter = new(proto.Filter) req.Filter = new(proto.Filter)
req.Filter.Labels = f.Labels req.Filter.Labels = filter.Labels
for { for {
res, err = c.client.Next(ctx, req) res, err = c.client.Next(ctx, req)
if err == nil { if err == nil {
break break
} }
// TODO: remove after adding continuous data exchange by something like #536
if strings.Contains(err.Error(), "\"too_many_pings\"") {
// 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: done(): code: %v", status.Code(err))
}
switch 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: next(): context canceled")
return nil, nil
}
log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err))
return nil, err
case case
codes.Aborted, codes.Aborted,
codes.DataLoss, codes.DataLoss,
@ -101,14 +116,22 @@ func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error)
codes.Internal, codes.Internal,
codes.Unavailable: codes.Unavailable:
// non-fatal errors // non-fatal errors
// TODO: remove after adding continuous data exchange by something like #536
if strings.Contains(err.Error(), "\"too_many_pings\"") {
// 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.Warn().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err))
}
default: default:
log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err))
return nil, err return nil, err
} }
select { select {
case <-time.After(retry.NextBackOff()): case <-time.After(retry.NextBackOff()):
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, nil
} }
} }
@ -127,19 +150,25 @@ func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error)
} }
// Wait blocks until the workflow is complete. // Wait blocks until the workflow is complete.
func (c *client) Wait(ctx context.Context, id string) (err error) { func (c *client) Wait(ctx context.Context, workflowID string) (err error) {
retry := c.newBackOff() retry := c.newBackOff()
req := new(proto.WaitRequest) req := new(proto.WaitRequest)
req.Id = id req.Id = workflowID
for { for {
_, err = c.client.Wait(ctx, req) _, err = c.client.Wait(ctx, req)
if err == nil { if err == nil {
break break
} }
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
switch 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 case
codes.Aborted, codes.Aborted,
codes.DataLoss, codes.DataLoss,
@ -147,7 +176,9 @@ func (c *client) Wait(ctx context.Context, id string) (err error) {
codes.Internal, codes.Internal,
codes.Unavailable: codes.Unavailable:
// non-fatal errors // non-fatal errors
log.Warn().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
default: default:
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
return err return err
} }
@ -161,17 +192,14 @@ func (c *client) Wait(ctx context.Context, id string) (err error) {
} }
// Init signals the workflow is initialized. // Init signals the workflow is initialized.
func (c *client) Init(ctx context.Context, id string, state rpc.State) (err error) { func (c *client) Init(ctx context.Context, workflowID string, state rpc.WorkflowState) (err error) {
retry := c.newBackOff() retry := c.newBackOff()
req := new(proto.InitRequest) req := new(proto.InitRequest)
req.Id = id req.Id = workflowID
req.State = new(proto.State) req.State = new(proto.WorkflowState)
req.State.Error = state.Error
req.State.ExitCode = int32(state.ExitCode)
req.State.Exited = state.Exited
req.State.Finished = state.Finished
req.State.Started = state.Started req.State.Started = state.Started
req.State.StepUuid = state.StepUUID req.State.Finished = state.Finished
req.State.Error = state.Error
for { for {
_, err = c.client.Init(ctx, req) _, err = c.client.Init(ctx, req)
if err == nil { if err == nil {
@ -181,6 +209,14 @@ func (c *client) Init(ctx context.Context, id string, state rpc.State) (err erro
log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err)) log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
switch 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 case
codes.Aborted, codes.Aborted,
codes.DataLoss, codes.DataLoss,
@ -188,7 +224,9 @@ func (c *client) Init(ctx context.Context, id string, state rpc.State) (err erro
codes.Internal, codes.Internal,
codes.Unavailable: codes.Unavailable:
// non-fatal errors // non-fatal errors
log.Warn().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
default: default:
log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
return err return err
} }
@ -201,18 +239,15 @@ func (c *client) Init(ctx context.Context, id string, state rpc.State) (err erro
return nil return nil
} }
// Done signals the work is complete. // Done signals the workflow is complete.
func (c *client) Done(ctx context.Context, id string, state rpc.State) (err error) { func (c *client) Done(ctx context.Context, workflowID string, state rpc.WorkflowState) (err error) {
retry := c.newBackOff() retry := c.newBackOff()
req := new(proto.DoneRequest) req := new(proto.DoneRequest)
req.Id = id req.Id = workflowID
req.State = new(proto.State) req.State = new(proto.WorkflowState)
req.State.Error = state.Error
req.State.ExitCode = int32(state.ExitCode)
req.State.Exited = state.Exited
req.State.Finished = state.Finished
req.State.Started = state.Started req.State.Started = state.Started
req.State.StepUuid = state.StepUUID req.State.Finished = state.Finished
req.State.Error = state.Error
for { for {
_, err = c.client.Done(ctx, req) _, err = c.client.Done(ctx, req)
if err == nil { if err == nil {
@ -222,6 +257,14 @@ func (c *client) Done(ctx context.Context, id string, state rpc.State) (err erro
log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err)) log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
switch 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 case
codes.Aborted, codes.Aborted,
codes.DataLoss, codes.DataLoss,
@ -229,7 +272,9 @@ func (c *client) Done(ctx context.Context, id string, state rpc.State) (err erro
codes.Internal, codes.Internal,
codes.Unavailable: codes.Unavailable:
// non-fatal errors // non-fatal errors
log.Warn().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
default: default:
log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
return err return err
} }
@ -243,10 +288,10 @@ func (c *client) Done(ctx context.Context, id string, state rpc.State) (err erro
} }
// Extend extends the workflow deadline. // Extend extends the workflow deadline.
func (c *client) Extend(ctx context.Context, id string) (err error) { func (c *client) Extend(ctx context.Context, workflowID string) (err error) {
retry := c.newBackOff() retry := c.newBackOff()
req := new(proto.ExtendRequest) req := new(proto.ExtendRequest)
req.Id = id req.Id = workflowID
for { for {
_, err = c.client.Extend(ctx, req) _, err = c.client.Extend(ctx, req)
if err == nil { if err == nil {
@ -256,6 +301,14 @@ func (c *client) Extend(ctx context.Context, id string) (err error) {
log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err)) log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
switch 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 case
codes.Aborted, codes.Aborted,
codes.DataLoss, codes.DataLoss,
@ -263,7 +316,9 @@ func (c *client) Extend(ctx context.Context, id string) (err error) {
codes.Internal, codes.Internal,
codes.Unavailable: codes.Unavailable:
// non-fatal errors // non-fatal errors
log.Warn().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
default: default:
log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
return err return err
} }
@ -277,17 +332,17 @@ func (c *client) Extend(ctx context.Context, id string) (err error) {
} }
// Update updates the workflow state. // Update updates the workflow state.
func (c *client) Update(ctx context.Context, id string, state rpc.State) (err error) { func (c *client) Update(ctx context.Context, workflowID string, state rpc.StepState) (err error) {
retry := c.newBackOff() retry := c.newBackOff()
req := new(proto.UpdateRequest) req := new(proto.UpdateRequest)
req.Id = id req.Id = workflowID
req.State = new(proto.State) req.State = new(proto.StepState)
req.State.Error = state.Error
req.State.ExitCode = int32(state.ExitCode)
req.State.Exited = state.Exited
req.State.Finished = state.Finished
req.State.Started = state.Started
req.State.StepUuid = state.StepUUID req.State.StepUuid = state.StepUUID
req.State.Started = state.Started
req.State.Finished = state.Finished
req.State.Exited = state.Exited
req.State.ExitCode = int32(state.ExitCode)
req.State.Error = state.Error
for { for {
_, err = c.client.Update(ctx, req) _, err = c.client.Update(ctx, req)
if err == nil { if err == nil {
@ -297,6 +352,14 @@ func (c *client) Update(ctx context.Context, id string, state rpc.State) (err er
log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err)) log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
switch 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 case
codes.Aborted, codes.Aborted,
codes.DataLoss, codes.DataLoss,
@ -304,7 +367,9 @@ func (c *client) Update(ctx context.Context, id string, state rpc.State) (err er
codes.Internal, codes.Internal,
codes.Unavailable: codes.Unavailable:
// non-fatal errors // non-fatal errors
log.Warn().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
default: default:
log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
return err return err
} }
@ -317,25 +382,82 @@ func (c *client) Update(ctx context.Context, id string, state rpc.State) (err er
return nil return nil
} }
// Log writes the workflow log entry. // EnqueueLog queues the log entry to be written in a batch later.
func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) { func (c *client) EnqueueLog(logEntry *rpc.LogEntry) {
retry := c.newBackOff() c.logs <- &proto.LogEntry{
req := new(proto.LogRequest) StepUuid: logEntry.StepUUID,
req.LogEntry = new(proto.LogEntry) Data: logEntry.Data,
req.LogEntry.StepUuid = logEntry.StepUUID Line: int32(logEntry.Line),
req.LogEntry.Data = logEntry.Data Time: logEntry.Time,
req.LogEntry.Line = int32(logEntry.Line) Type: int32(logEntry.Type),
req.LogEntry.Time = logEntry.Time }
req.LogEntry.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 { 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 { if err == nil {
break break
} }
log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
switch 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 case
codes.Aborted, codes.Aborted,
codes.DataLoss, codes.DataLoss,
@ -343,7 +465,9 @@ func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) {
codes.Internal, codes.Internal,
codes.Unavailable: codes.Unavailable:
// non-fatal errors // non-fatal errors
log.Warn().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
default: default:
log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
return err return err
} }
@ -383,6 +507,14 @@ func (c *client) ReportHealth(ctx context.Context) (err error) {
return nil return nil
} }
switch 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: report_health(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err))
return err
case case
codes.Aborted, codes.Aborted,
codes.DataLoss, codes.DataLoss,
@ -390,7 +522,9 @@ func (c *client) ReportHealth(ctx context.Context) (err error) {
codes.Internal, codes.Internal,
codes.Unavailable: codes.Unavailable:
// non-fatal errors // non-fatal errors
log.Warn().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err))
default: default:
log.Error().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err))
return err return err
} }

View file

@ -23,12 +23,12 @@ import (
"time" "time"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/tevino/abool/v2"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"go.woodpecker-ci.org/woodpecker/v2/pipeline" "go.woodpecker-ci.org/woodpecker/v2/pipeline"
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" 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"
"go.woodpecker-ci.org/woodpecker/v2/shared/constant"
"go.woodpecker-ci.org/woodpecker/v2/shared/utils" "go.woodpecker-ci.org/woodpecker/v2/shared/utils"
) )
@ -50,41 +50,41 @@ 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") log.Debug().Msg("request next execution")
meta, _ := metadata.FromOutgoingContext(runnerCtx) meta, _ := metadata.FromOutgoingContext(runnerCtx)
ctxMeta := metadata.NewOutgoingContext(context.Background(), meta) ctxMeta := metadata.NewOutgoingContext(context.Background(), meta)
// get the next workflow from the queue // get the next workflow from the queue
work, err := r.client.Next(runnerCtx, r.filter) workflow, err := r.client.Next(runnerCtx, r.filter)
if err != nil { if err != nil {
return err return err
} }
if work == nil { if workflow == nil {
return nil return nil
} }
timeout := time.Hour timeout := time.Hour
if minutes := work.Timeout; minutes != 0 { if minutes := workflow.Timeout; minutes != 0 {
timeout = time.Duration(minutes) * time.Minute timeout = time.Duration(minutes) * time.Minute
} }
repoName := extractRepositoryName(work.Config) // hack repoName := extractRepositoryName(workflow.Config) // hack
pipelineNumber := extractPipelineNumber(work.Config) // hack pipelineNumber := extractPipelineNumber(workflow.Config) // hack
r.counter.Add( r.counter.Add(
work.ID, workflow.ID,
timeout, timeout,
repoName, repoName,
pipelineNumber, pipelineNumber,
) )
defer r.counter.Done(work.ID) defer r.counter.Done(workflow.ID)
logger := log.With(). logger := log.With().
Str("repo", repoName). Str("repo", repoName).
Str("pipeline", pipelineNumber). Str("pipeline", pipelineNumber).
Str("id", work.ID). Str("workflow_id", workflow.ID).
Logger() Logger()
logger.Debug().Msg("received execution") logger.Debug().Msg("received execution")
@ -99,17 +99,16 @@ func (r *Runner) Run(runnerCtx context.Context) error { //nolint:contextcheck
logger.Error().Msg("Received sigterm termination signal") logger.Error().Msg("Received sigterm termination signal")
}) })
canceled := abool.New() canceled := false
go func() { go func() {
logger.Debug().Msg("listen for cancel signal") logger.Debug().Msg("listen for cancel signal")
if err := r.client.Wait(workflowCtx, work.ID); err != nil { if err := r.client.Wait(workflowCtx, workflow.ID); err != nil {
canceled.SetTo(true) canceled = true
logger.Warn().Err(err).Msg("cancel signal received") logger.Warn().Err(err).Msg("cancel signal received")
cancel() cancel()
} else { } else {
logger.Debug().Msg("stop listening for cancel signal") logger.Debug().Msg("done listening for cancel signal")
} }
}() }()
@ -118,81 +117,74 @@ func (r *Runner) Run(runnerCtx context.Context) error { //nolint:contextcheck
select { select {
case <-workflowCtx.Done(): case <-workflowCtx.Done():
logger.Debug().Msg("pipeline done") logger.Debug().Msg("pipeline done")
return return
case <-time.After(time.Minute):
logger.Debug().Msg("pipeline lease renewed")
if err := r.client.Extend(workflowCtx, work.ID); err != nil { 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") log.Error().Err(err).Msg("extending pipeline deadline failed")
} }
} }
} }
}() }()
state := rpc.State{} state := rpc.WorkflowState{}
state.Started = time.Now().Unix() state.Started = time.Now().Unix()
err = r.client.Init(runnerCtx, work.ID, state) err = r.client.Init(runnerCtx, workflow.ID, state)
if err != nil { if err != nil {
logger.Error().Err(err).Msg("pipeline initialization failed") logger.Error().Err(err).Msg("workflow initialization failed")
// TODO: should we return here?
} }
var uploads sync.WaitGroup var uploads sync.WaitGroup
//nolint:contextcheck //nolint:contextcheck
err = pipeline.New(work.Config, err = pipeline.New(workflow.Config,
pipeline.WithContext(workflowCtx), pipeline.WithContext(workflowCtx),
pipeline.WithTaskUUID(fmt.Sprint(work.ID)), pipeline.WithTaskUUID(fmt.Sprint(workflow.ID)),
pipeline.WithLogger(r.createLogger(logger, &uploads, work)), pipeline.WithLogger(r.createLogger(logger, &uploads, workflow)),
pipeline.WithTracer(r.createTracer(ctxMeta, logger, work)), pipeline.WithTracer(r.createTracer(ctxMeta, &uploads, logger, workflow)),
pipeline.WithBackend(*r.backend), pipeline.WithBackend(*r.backend),
pipeline.WithDescription(map[string]string{ pipeline.WithDescription(map[string]string{
"ID": work.ID, "workflow_id": workflow.ID,
"Repo": repoName, "repo": repoName,
"Pipeline": pipelineNumber, "pipeline_number": pipelineNumber,
}), }),
).Run(runnerCtx) ).Run(runnerCtx)
state.Finished = time.Now().Unix() state.Finished = time.Now().Unix()
state.Exited = true
if canceled.IsSet() { if errors.Is(err, pipeline.ErrCancel) {
state.Error = "" canceled = true
state.ExitCode = pipeline.ExitCodeKilled } else if canceled {
} else if err != nil { err = errors.Join(err, pipeline.ErrCancel)
pExitError := &pipeline.ExitError{} }
switch {
case errors.As(err, &pExitError): if err != nil {
state.ExitCode = pExitError.Code
case errors.Is(err, pipeline.ErrCancel):
state.Error = ""
state.ExitCode = pipeline.ExitCodeKilled
canceled.SetTo(true)
default:
state.ExitCode = 1
state.Error = err.Error() state.Error = err.Error()
} }
}
logger.Debug(). logger.Debug().
Str("error", state.Error). Str("error", state.Error).
Int("exit_code", state.ExitCode). Bool("canceled", canceled).
Bool("canceled", canceled.IsSet()). Msg("workflow finished")
Msg("pipeline complete")
logger.Debug().Msg("uploading logs") logger.Debug().Msg("uploading logs and traces / states ...")
uploads.Wait() uploads.Wait()
logger.Debug().Msg("uploading logs complete") logger.Debug().Msg("uploaded logs and traces / states")
logger.Debug(). logger.Debug().
Str("error", state.Error). Str("error", state.Error).
Int("exit_code", state.ExitCode). Msg("updating workflow status")
Msg("updating pipeline status")
if err := r.client.Done(runnerCtx, work.ID, state); err != nil { doneCtx := runnerCtx
logger.Error().Err(err).Msg("updating pipeline status failed") 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 { } else {
logger.Debug().Msg("updating pipeline status complete") logger.Debug().Msg("updating workflow status complete")
} }
return nil return nil

View file

@ -18,6 +18,7 @@ import (
"context" "context"
"runtime" "runtime"
"strconv" "strconv"
"sync"
"time" "time"
"github.com/rs/zerolog" "github.com/rs/zerolog"
@ -26,17 +27,19 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
) )
func (r *Runner) createTracer(ctxMeta context.Context, logger zerolog.Logger, workflow *rpc.Workflow) pipeline.TraceFunc { func (r *Runner) createTracer(ctxMeta context.Context, uploads *sync.WaitGroup, logger zerolog.Logger, workflow *rpc.Workflow) pipeline.TraceFunc {
return func(state *pipeline.State) error { return func(state *pipeline.State) error {
uploads.Add(1)
stepLogger := logger.With(). stepLogger := logger.With().
Str("image", state.Pipeline.Step.Image). Str("image", state.Pipeline.Step.Image).
Str("workflowID", workflow.ID). Str("workflow_id", workflow.ID).
Err(state.Process.Error). Err(state.Process.Error).
Int("exit_code", state.Process.ExitCode). Int("exit_code", state.Process.ExitCode).
Bool("exited", state.Process.Exited). Bool("exited", state.Process.Exited).
Logger() Logger()
stepState := rpc.State{ stepState := rpc.StepState{
StepUUID: state.Pipeline.Step.UUID, StepUUID: state.Pipeline.Step.UUID,
Exited: state.Process.Exited, Exited: state.Process.Exited,
ExitCode: state.Process.ExitCode, ExitCode: state.Process.ExitCode,
@ -57,6 +60,7 @@ func (r *Runner) createTracer(ctxMeta context.Context, logger zerolog.Logger, wo
} }
stepLogger.Debug().Msg("update step status complete") stepLogger.Debug().Msg("update step status complete")
uploads.Done()
}() }()
if state.Process.Exited { if state.Process.Exited {
return nil return nil
@ -68,21 +72,12 @@ func (r *Runner) createTracer(ctxMeta context.Context, logger zerolog.Logger, wo
// TODO: find better way to update this state and move it to pipeline to have the same env in cli-exec // 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_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_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 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_STARTED"] = strconv.FormatInt(state.Pipeline.Time, 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 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 return nil
} }
} }

View file

@ -1,4 +1,4 @@
// Copyright 2021 Woodpecker Authors // Copyright 2024 Woodpecker Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -12,16 +12,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package migration package admin
import ( import (
"src.techknowlogick.com/xormigrate" "github.com/urfave/cli/v3"
"xorm.io/xorm"
"go.woodpecker-ci.org/woodpecker/v2/cli/admin/registry"
) )
var alterTableReposDropCounter = xormigrate.Migration{ // Command exports the admin command set.
ID: "alter-table-drop-counter", var Command = &cli.Command{
MigrateSession: func(sess *xorm.Session) error { Name: "admin",
return dropTableColumns(sess, "repos", "repo_counter") Usage: "administer server settings",
Commands: []*cli.Command{
registry.Command,
}, },
} }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Woodpecker Authors // Copyright 2024 Woodpecker Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -15,14 +15,14 @@
package registry package registry
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// Command exports the registry command set. // Command exports the registry command set.
var Command = &cli.Command{ var Command = &cli.Command{
Name: "registry", Name: "registry",
Usage: "manage registries", Usage: "manage global registries",
Subcommands: []*cli.Command{ Commands: []*cli.Command{
registryCreateCmd, registryCreateCmd,
registryDeleteCmd, registryDeleteCmd,
registryUpdateCmd, registryUpdateCmd,

View file

@ -0,0 +1,76 @@
// 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 registry
import (
"context"
"os"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
Action: registryCreate,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "hostname",
Usage: "registry hostname",
Value: "docker.io",
},
&cli.StringFlag{
Name: "username",
Usage: "registry username",
},
&cli.StringFlag{
Name: "password",
Usage: "registry password",
},
},
}
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(ctx, c)
if err != nil {
return err
}
registry := &woodpecker.Registry{
Address: hostname,
Username: username,
Password: password,
}
if strings.HasPrefix(registry.Password, "@") {
path := strings.TrimPrefix(registry.Password, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
registry.Password = string(out)
}
_, err = client.GlobalRegistryCreate(registry)
return err
}

View file

@ -0,0 +1,63 @@
// 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 registry
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
Action: registryInfo,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "hostname",
Usage: "registry hostname",
Value: "docker.io",
},
common.FormatFlag(tmplRegistryList, true),
},
}
func registryInfo(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
format = c.String("format") + "\n"
)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
registry, err := client.GlobalRegistry(hostname)
if err != nil {
return err
}
tmpl, err := template.New("_").Parse(format)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, registry)
}

View file

@ -0,0 +1,66 @@
// 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 registry
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryListCmd = &cli.Command{
Name: "ls",
Usage: "list registries",
Action: registryList,
Flags: []cli.Flag{
common.FormatFlag(tmplRegistryList, true),
},
}
func registryList(ctx context.Context, c *cli.Command) error {
format := c.String("format") + "\n"
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
list, err := client.GlobalRegistryList()
if err != nil {
return err
}
tmpl, err := template.New("_").Parse(format)
if err != nil {
return err
}
for _, registry := range list {
if err := tmpl.Execute(os.Stdout, registry); err != nil {
return err
}
}
return nil
}
// Template for registry list information.
var tmplRegistryList = "\x1b[33m{{ .Address }} \x1b[0m" + `
Username: {{ .Username }}
Email: {{ .Email }}
`

View file

@ -0,0 +1,47 @@
// 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 registry
import (
"context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryDeleteCmd = &cli.Command{
Name: "rm",
Usage: "remove a registry",
Action: registryDelete,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "hostname",
Usage: "registry hostname",
Value: "docker.io",
},
},
}
func registryDelete(ctx context.Context, c *cli.Command) error {
hostname := c.String("hostname")
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
return client.GlobalRegistryDelete(hostname)
}

View file

@ -0,0 +1,79 @@
// 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 registry
import (
"context"
"os"
"strings"
"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"
)
var registryUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a registry",
Action: registryUpdate,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "hostname",
Usage: "registry hostname",
Value: "docker.io",
},
&cli.StringFlag{
Name: "username",
Usage: "registry username",
},
&cli.StringFlag{
Name: "password",
Usage: "registry password",
},
},
}
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(ctx, c)
if err != nil {
return err
}
registry := &woodpecker.Registry{
Address: hostname,
Username: username,
Password: password,
}
if strings.HasPrefix(registry.Password, "@") {
path := strings.TrimPrefix(registry.Password, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
registry.Password = string(out)
}
_, err = client.GlobalRegistryUpdate(registry)
return err
}

View file

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

View file

@ -6,7 +6,7 @@ import (
"time" "time"
"github.com/rs/zerolog/log" "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/internal/config"
"go.woodpecker-ci.org/woodpecker/v2/cli/update" "go.woodpecker-ci.org/woodpecker/v2/cli/update"
@ -17,12 +17,12 @@ var (
cancelWaitForUpdate context.CancelCauseFunc cancelWaitForUpdate context.CancelCauseFunc
) )
func Before(c *cli.Context) error { func Before(ctx context.Context, c *cli.Command) error {
if err := setupGlobalLogger(c); err != nil { if err := setupGlobalLogger(ctx, c); err != nil {
return err return err
} }
go func() { go func(context.Context) {
if c.Bool("disable-update-check") { if c.Bool("disable-update-check") {
return return
} }
@ -37,23 +37,23 @@ func Before(c *cli.Context) error {
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 { if err != nil {
log.Error().Err(err).Msgf("Failed to check for updates") log.Error().Err(err).Msgf("Failed to check for updates")
return return
} }
if newVersion != nil { 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("A new version of woodpecker-cli is available: %s. Update by running: %s update", newVersion.Version, c.Root().Name)
} else { } else {
log.Debug().Msgf("No update required") log.Debug().Msgf("No update required")
} }
}() }(ctx)
return config.Load(c) return config.Load(ctx, c)
} }
func After(_ *cli.Context) error { func After(_ context.Context, _ *cli.Command) error {
if waitForUpdateCheck != nil { if waitForUpdateCheck != nil {
select { select {
case <-waitForUpdateCheck.Done(): case <-waitForUpdateCheck.Done():

View file

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

View file

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

View file

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

View file

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

View file

@ -15,10 +15,11 @@
package cron package cron
import ( import (
"context"
"html/template" "html/template"
"os" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -40,16 +41,16 @@ var cronInfoCmd = &cli.Command{
}, },
} }
func cronInfo(c *cli.Context) error { func cronInfo(ctx context.Context, c *cli.Command) error {
var ( var (
jobID = c.Int64("id") cronID = c.Int("id")
repoIDOrFullName = c.String("repository") repoIDOrFullName = c.String("repository")
format = c.String("format") + "\n" format = c.String("format") + "\n"
) )
if repoIDOrFullName == "" { if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First() repoIDOrFullName = c.Args().First()
} }
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }
@ -58,7 +59,7 @@ func cronInfo(c *cli.Context) error {
return err return err
} }
cron, err := client.CronGet(repoID, jobID) cron, err := client.CronGet(repoID, cronID)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,11 @@
package cron package cron
import ( import (
"context"
"html/template" "html/template"
"os" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -35,7 +36,7 @@ var cronListCmd = &cli.Command{
}, },
} }
func cronList(c *cli.Context) error { func cronList(ctx context.Context, c *cli.Command) error {
var ( var (
format = c.String("format") + "\n" format = c.String("format") + "\n"
repoIDOrFullName = c.String("repository") repoIDOrFullName = c.String("repository")
@ -43,7 +44,7 @@ func cronList(c *cli.Context) error {
if repoIDOrFullName == "" { if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First() repoIDOrFullName = c.Args().First()
} }
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,9 +15,10 @@
package cron package cron
import ( import (
"context"
"fmt" "fmt"
"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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -38,15 +39,15 @@ var cronDeleteCmd = &cli.Command{
}, },
} }
func cronDelete(c *cli.Context) error { func cronDelete(ctx context.Context, c *cli.Command) error {
var ( var (
jobID = c.Int64("id") cronID = c.Int("id")
repoIDOrFullName = c.String("repository") repoIDOrFullName = c.String("repository")
) )
if repoIDOrFullName == "" { if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First() repoIDOrFullName = c.Args().First()
} }
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }
@ -54,7 +55,7 @@ func cronDelete(c *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
err = client.CronDelete(repoID, jobID) err = client.CronDelete(repoID, cronID)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,11 @@
package cron package cron
import ( import (
"context"
"html/template" "html/template"
"os" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -53,10 +54,10 @@ var cronUpdateCmd = &cli.Command{
}, },
} }
func cronUpdate(c *cli.Context) error { func cronUpdate(ctx context.Context, c *cli.Command) error {
var ( var (
repoIDOrFullName = c.String("repository") repoIDOrFullName = c.String("repository")
jobID = c.Int64("id") cronID = c.Int("id")
jobName = c.String("name") jobName = c.String("name")
branch = c.String("branch") branch = c.String("branch")
schedule = c.String("schedule") schedule = c.String("schedule")
@ -65,7 +66,7 @@ func cronUpdate(c *cli.Context) error {
if repoIDOrFullName == "" { if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First() repoIDOrFullName = c.Args().First()
} }
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }
@ -74,7 +75,7 @@ func cronUpdate(c *cli.Context) error {
return err return err
} }
cron := &woodpecker.Cron{ cron := &woodpecker.Cron{
ID: jobID, ID: cronID,
Name: jobName, Name: jobName,
Branch: branch, Branch: branch,
Schedule: schedule, Schedule: schedule,

View file

@ -15,12 +15,13 @@
package deploy package deploy
import ( import (
"context"
"fmt" "fmt"
"html/template" "html/template"
"os" "os"
"strconv" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -30,7 +31,7 @@ import (
// Command exports the deploy command. // Command exports the deploy command.
var Command = &cli.Command{ var Command = &cli.Command{
Name: "deploy", Name: "deploy",
Usage: "deploy code", Usage: "trigger a pipeline with the 'deployment' event",
ArgsUsage: "<repo-id|repo-full-name> <pipeline> <environment>", ArgsUsage: "<repo-id|repo-full-name> <pipeline> <environment>",
Action: deploy, Action: deploy,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -38,7 +39,6 @@ var Command = &cli.Command{
&cli.StringFlag{ &cli.StringFlag{
Name: "branch", Name: "branch",
Usage: "branch filter", Usage: "branch filter",
Value: "main",
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "event", Name: "event",
@ -58,8 +58,8 @@ var Command = &cli.Command{
}, },
} }
func deploy(c *cli.Context) error { func deploy(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }
@ -74,6 +74,15 @@ func deploy(c *cli.Context) error {
event := c.String("event") event := c.String("event")
status := c.String("status") status := c.String("status")
if branch == "" {
repo, err := client.Repo(repoID)
if err != nil {
return err
}
branch = repo.DefaultBranch
}
pipelineArg := c.Args().Get(1) pipelineArg := c.Args().Get(1)
var number int64 var number int64
if pipelineArg == "last" { if pipelineArg == "last" {

View file

@ -1,4 +1,4 @@
// Copyright 2022 Woodpecker Authors // Copyright 2024 Woodpecker Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -12,16 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package migration //go:build test
// +build test
import ( package exec
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
var dropFiles = xormigrate.Migration{ import "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy"
ID: "drop-files",
MigrateSession: func(sess *xorm.Session) error { func init() { //nolint:gochecknoinits
return sess.DropTable("files") backends = append(backends, dummy.New())
},
} }

View file

@ -26,20 +26,22 @@ import (
"github.com/drone/envsubst" "github.com/drone/envsubst"
"github.com/rs/zerolog/log" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/lint"
"go.woodpecker-ci.org/woodpecker/v2/pipeline" "go.woodpecker-ci.org/woodpecker/v2/pipeline"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend" "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/docker"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local" "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local"
backendTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" 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"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler" "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/linter"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix"
pipelineLog "go.woodpecker-ci.org/woodpecker/v2/pipeline/log" pipelineLog "go.woodpecker-ci.org/woodpecker/v2/pipeline/log"
"go.woodpecker-ci.org/woodpecker/v2/shared/constant"
"go.woodpecker-ci.org/woodpecker/v2/shared/utils" "go.woodpecker-ci.org/woodpecker/v2/shared/utils"
) )
@ -52,11 +54,17 @@ var Command = &cli.Command{
Flags: utils.MergeSlices(flags, docker.Flags, kubernetes.Flags, local.Flags), Flags: utils.MergeSlices(flags, docker.Flags, kubernetes.Flags, local.Flags),
} }
func run(c *cli.Context) error { var backends = []backend_types.Backend{
return common.RunPipelineFunc(c, execFile, execDir) kubernetes.New(),
docker.New(),
local.New(),
} }
func execDir(c *cli.Context, dir string) error { func run(ctx context.Context, c *cli.Command) error {
return common.RunPipelineFunc(ctx, c, execFile, execDir)
}
func execDir(ctx context.Context, c *cli.Command, dir string) error {
// TODO: respect pipeline dependency // TODO: respect pipeline dependency
repoPath := c.String("repo-path") repoPath := c.String("repo-path")
if repoPath != "" { if repoPath != "" {
@ -75,7 +83,7 @@ func execDir(c *cli.Context, dir string) error {
// check if it is a regular file (not dir) // check if it is a regular file (not dir)
if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) { if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
fmt.Println("#", info.Name()) fmt.Println("#", info.Name())
_ = runExec(c, path, repoPath) // TODO: should we drop errors or store them and report back? _ = runExec(ctx, c, path, repoPath) // TODO: should we drop errors or store them and report back?
fmt.Println("") fmt.Println("")
return nil return nil
} }
@ -84,7 +92,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") repoPath := c.String("repo-path")
if repoPath != "" { if repoPath != "" {
repoPath, _ = filepath.Abs(repoPath) repoPath, _ = filepath.Abs(repoPath)
@ -94,10 +102,10 @@ func execFile(c *cli.Context, file string) error {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
repoPath = convertPathForWindows(repoPath) repoPath = convertPathForWindows(repoPath)
} }
return runExec(c, file, repoPath) return runExec(ctx, c, file, repoPath)
} }
func runExec(c *cli.Context, file, repoPath string) error { func runExec(ctx context.Context, c *cli.Command, file, repoPath string) error {
dat, err := os.ReadFile(file) dat, err := os.ReadFile(file)
if err != nil { if err != nil {
return err return err
@ -112,7 +120,7 @@ func runExec(c *cli.Context, file, repoPath string) error {
axes = append(axes, matrix.Axis{}) axes = append(axes, matrix.Axis{})
} }
for _, axis := range axes { for _, axis := range axes {
err := execWithAxis(c, file, repoPath, axis) err := execWithAxis(ctx, c, file, repoPath, axis)
if err != nil { if err != nil {
return err return err
} }
@ -120,8 +128,11 @@ func runExec(c *cli.Context, file, repoPath string) error {
return nil return nil
} }
func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error { func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, axis matrix.Axis) error {
metadata := metadataFromContext(c, axis) metadata, err := metadataFromContext(ctx, c, axis)
if err != nil {
return fmt.Errorf("could not create metadata: %w", err)
}
environ := metadata.Environ() environ := metadata.Environ()
var secrets []compiler.Secret var secrets []compiler.Secret
for key, val := range metadata.Workflow.Matrix { for key, val := range metadata.Workflow.Matrix {
@ -177,19 +188,30 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
volumes = append(volumes, repoPath+":"+path.Join(workspaceBase, workspacePath)) volumes = append(volumes, repoPath+":"+path.Join(workspaceBase, workspacePath))
} }
privilegedPlugins := c.StringSlice("plugins-privileged")
// lint the yaml file // lint the yaml file
if err := linter.New(linter.WithTrusted(true)).Lint([]*linter.WorkflowConfig{{ err = linter.New(
linter.WithTrusted(true),
linter.PrivilegedPlugins(privilegedPlugins),
linter.WithTrustedClonePlugins(constant.TrustedClonePlugins),
).Lint([]*linter.WorkflowConfig{{
File: path.Base(file), File: path.Base(file),
RawConfig: confStr, RawConfig: confStr,
Workflow: conf, Workflow: conf,
}}); err != nil { }})
if err != nil {
str, err := lint.FormatLintError(file, err)
fmt.Print(str)
if err != nil {
return err return err
} }
}
// compiles the yaml file // compiles the yaml file
compiled, err := compiler.New( compiled, err := compiler.New(
compiler.WithEscalated( compiler.WithEscalated(
c.StringSlice("privileged")..., privilegedPlugins...,
), ),
compiler.WithVolumes(volumes...), compiler.WithVolumes(volumes...),
compiler.WithWorkspace( compiler.WithWorkspace(
@ -223,12 +245,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
return err return err
} }
backendCtx := context.WithValue(c.Context, backendTypes.CliContext, c) backendCtx := context.WithValue(ctx, backend_types.CliCommand, c)
backends := []backendTypes.Backend{
kubernetes.New(),
docker.New(),
local.New(),
}
backendEngine, err := backend.FindBackend(backendCtx, backends, c.String("backend-engine")) backendEngine, err := backend.FindBackend(backendCtx, backends, c.String("backend-engine"))
if err != nil { if err != nil {
return err return err
@ -238,21 +255,21 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error
return err return err
} }
ctx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout")) pipelineCtx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout"))
defer cancel() defer cancel()
ctx = utils.WithContextSigtermCallback(ctx, func() { pipelineCtx = utils.WithContextSigtermCallback(pipelineCtx, func() {
fmt.Println("ctrl+c received, terminating process") fmt.Printf("ctrl+c received, terminating current pipeline '%s'\n", confStr)
}) })
return pipeline.New(compiled, return pipeline.New(compiled,
pipeline.WithContext(ctx), pipeline.WithContext(pipelineCtx), //nolint:contextcheck
pipeline.WithTracer(pipeline.DefaultTracer), pipeline.WithTracer(pipeline.DefaultTracer),
pipeline.WithLogger(defaultLogger), pipeline.WithLogger(defaultLogger),
pipeline.WithBackend(backendEngine), pipeline.WithBackend(backendEngine),
pipeline.WithDescription(map[string]string{ pipeline.WithDescription(map[string]string{
"CLI": "exec", "CLI": "exec",
}), }),
).Run(c.Context) ).Run(ctx)
} }
// convertPathForWindows converts a path to use slash separators // convertPathForWindows converts a path to use slash separators
@ -274,8 +291,7 @@ func convertPathForWindows(path string) string {
return filepath.ToSlash(path) return filepath.ToSlash(path)
} }
const maxLogLineLength = 1024 * 1024 // 1mb var defaultLogger = pipeline.Logger(func(step *backend_types.Step, rc io.ReadCloser) error {
var defaultLogger = pipeline.Logger(func(step *backendTypes.Step, rc io.Reader) error {
logWriter := NewLineWriter(step.Name, step.UUID) logWriter := NewLineWriter(step.Name, step.UUID)
return pipelineLog.CopyLineByLine(logWriter, rc, maxLogLineLength) return pipelineLog.CopyLineByLine(logWriter, rc, pipeline.MaxLogLineLength)
}) })

View file

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

View file

@ -15,10 +15,13 @@
package exec package exec
import ( import (
"context"
"encoding/json"
"fmt"
"runtime" "runtime"
"strings" "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/metadata"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix"
@ -26,7 +29,7 @@ import (
) )
// return the metadata from the cli context. // 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) (metadata.Metadata, error) {
platform := c.String("system-platform") platform := c.String("system-platform")
if platform == "" { if platform == "" {
platform = runtime.GOOS + "/" + runtime.GOARCH platform = runtime.GOOS + "/" + runtime.GOARCH
@ -40,28 +43,42 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata {
repoName = fullRepoName[idx+1:] repoName = fullRepoName[idx+1:]
} }
var changedFiles []string
changedFilesRaw := c.String("pipeline-files")
if len(changedFilesRaw) != 0 && changedFilesRaw[0] == '[' {
if err := json.Unmarshal([]byte(changedFilesRaw), &changedFiles); err != nil {
return metadata.Metadata{}, fmt.Errorf("pipeline-files detected json but could not parse it: %w", err)
}
} else {
for _, file := range strings.Split(changedFilesRaw, ",") {
changedFiles = append(changedFiles, strings.TrimSpace(file))
}
}
return metadata.Metadata{ return metadata.Metadata{
Repo: metadata.Repo{ Repo: metadata.Repo{
Name: repoName, Name: repoName,
Owner: repoOwner, Owner: repoOwner,
RemoteID: c.String("repo-remote-id"), RemoteID: c.String("repo-remote-id"),
ForgeURL: c.String("repo-url"), ForgeURL: c.String("repo-url"),
SCM: c.String("repo-scm"),
Branch: c.String("repo-default-branch"),
CloneURL: c.String("repo-clone-url"), CloneURL: c.String("repo-clone-url"),
CloneSSHURL: c.String("repo-clone-ssh-url"), CloneSSHURL: c.String("repo-clone-ssh-url"),
Private: c.Bool("repo-private"), Private: c.Bool("repo-private"),
Trusted: c.Bool("repo-trusted"), Trusted: c.Bool("repo-trusted"),
}, },
Curr: metadata.Pipeline{ Curr: metadata.Pipeline{
Number: c.Int64("pipeline-number"), Number: c.Int("pipeline-number"),
Parent: c.Int64("pipeline-parent"), Parent: c.Int("pipeline-parent"),
Created: c.Int64("pipeline-created"), Created: c.Int("pipeline-created"),
Started: c.Int64("pipeline-started"), Started: c.Int("pipeline-started"),
Finished: c.Int64("pipeline-finished"), Finished: c.Int("pipeline-finished"),
Status: c.String("pipeline-status"), Status: c.String("pipeline-status"),
Event: c.String("pipeline-event"), Event: c.String("pipeline-event"),
ForgeURL: c.String("pipeline-url"), ForgeURL: c.String("pipeline-url"),
Target: c.String("pipeline-target"), DeployTo: c.String("pipeline-deploy-to"),
Task: c.String("pipeline-task"), DeployTask: c.String("pipeline-deploy-task"),
Commit: metadata.Commit{ Commit: metadata.Commit{
Sha: c.String("commit-sha"), Sha: c.String("commit-sha"),
Ref: c.String("commit-ref"), Ref: c.String("commit-ref"),
@ -73,13 +90,16 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata {
Email: c.String("commit-author-email"), Email: c.String("commit-author-email"),
Avatar: c.String("commit-author-avatar"), Avatar: c.String("commit-author-avatar"),
}, },
PullRequestLabels: c.StringSlice("commit-pull-labels"),
IsPrerelease: c.Bool("commit-release-is-pre"),
ChangedFiles: changedFiles,
}, },
}, },
Prev: metadata.Pipeline{ Prev: metadata.Pipeline{
Number: c.Int64("prev-pipeline-number"), Number: c.Int("prev-pipeline-number"),
Created: c.Int64("prev-pipeline-created"), Created: c.Int("prev-pipeline-created"),
Started: c.Int64("prev-pipeline-started"), Started: c.Int("prev-pipeline-started"),
Finished: c.Int64("prev-pipeline-finished"), Finished: c.Int("prev-pipeline-finished"),
Status: c.String("prev-pipeline-status"), Status: c.String("prev-pipeline-status"),
Event: c.String("prev-pipeline-event"), Event: c.String("prev-pipeline-event"),
ForgeURL: c.String("prev-pipeline-url"), ForgeURL: c.String("prev-pipeline-url"),
@ -98,16 +118,17 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata {
}, },
Workflow: metadata.Workflow{ Workflow: metadata.Workflow{
Name: c.String("workflow-name"), Name: c.String("workflow-name"),
Number: c.Int("workflow-number"), Number: int(c.Int("workflow-number")),
Matrix: axis, Matrix: axis,
}, },
Step: metadata.Step{ Step: metadata.Step{
Name: c.String("step-name"), Name: c.String("step-name"),
Number: c.Int("step-number"), Number: int(c.Int("step-number")),
}, },
Sys: metadata.System{ Sys: metadata.System{
Name: c.String("system-name"), Name: c.String("system-name"),
URL: c.String("system-url"), URL: c.String("system-url"),
Host: c.String("system-host"),
Platform: platform, Platform: platform,
Version: version.Version, Version: version.Version,
}, },
@ -115,5 +136,5 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata {
Type: c.String("forge-type"), Type: c.String("forge-type"),
URL: c.String("forge-url"), URL: c.String("forge-url"),
}, },
} }, nil
} }

View file

@ -15,10 +15,11 @@
package info package info
import ( import (
"context"
"os" "os"
"text/template" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -33,8 +34,8 @@ var Command = &cli.Command{
Flags: []cli.Flag{common.FormatFlag(tmplInfo, true)}, Flags: []cli.Flag{common.FormatFlag(tmplInfo, true)},
} }
func info(c *cli.Context) error { func info(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,6 +1,7 @@
package config package config
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"os" "os"
@ -8,7 +9,7 @@ import (
"github.com/adrg/xdg" "github.com/adrg/xdg"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
"github.com/zalando/go-keyring" "github.com/zalando/go-keyring"
) )
@ -32,12 +33,12 @@ func (c *Config) MergeIfNotSet(c2 *Config) {
var skipSetupForCommands = []string{"setup", "help", "h", "version", "update", "lint", "exec", ""} 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) { if firstArg := c.Args().First(); slices.Contains(skipSetupForCommands, firstArg) {
return nil return nil
} }
config, err := Get(c, c.String("config")) config, err := Get(ctx, c, c.String("config"))
if err != nil { if err != nil {
return err return err
} }
@ -80,11 +81,11 @@ func getConfigPath(configPath string) (string, error) {
return configPath, nil return configPath, nil
} }
func Get(ctx *cli.Context, _configPath string) (*Config, error) { func Get(_ context.Context, c *cli.Command, _configPath string) (*Config, error) {
c := &Config{ conf := &Config{
LogLevel: ctx.String("log-level"), LogLevel: c.String("log-level"),
Token: ctx.String("token"), Token: c.String("token"),
ServerURL: ctx.String("server"), ServerURL: c.String("server"),
} }
configPath, err := getConfigPath(_configPath) configPath, err := getConfigPath(_configPath)
@ -109,33 +110,33 @@ func Get(ctx *cli.Context, _configPath string) (*Config, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
c.MergeIfNotSet(configFromFile) conf.MergeIfNotSet(configFromFile)
log.Debug().Msg("Loaded config from file") log.Debug().Msg("Loaded config from file")
} }
// if server or token are explicitly set, use them // if server or token are explicitly set, use them
if ctx.IsSet("server") || ctx.IsSet("token") { if c.IsSet("server") || c.IsSet("token") {
return c, nil return conf, nil
} }
// load token from keyring // load token from keyring
service := ctx.App.Name service := c.Root().Name
secret, err := keyring.Get(service, c.ServerURL) secret, err := keyring.Get(service, conf.ServerURL)
if errors.Is(err, keyring.ErrUnsupportedPlatform) { if errors.Is(err, keyring.ErrUnsupportedPlatform) {
log.Warn().Msg("Keyring is not supported on this platform") log.Warn().Msg("Keyring is not supported on this platform")
return c, nil return conf, nil
} }
if errors.Is(err, keyring.ErrNotFound) { if errors.Is(err, keyring.ErrNotFound) {
log.Warn().Msg("Token not found in keyring") log.Warn().Msg("Token not found in keyring")
return c, nil 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 { func Save(_ context.Context, c *cli.Command, _configPath string, conf *Config) error {
config, err := json.Marshal(c) config, err := json.Marshal(conf)
if err != nil { if err != nil {
return err return err
} }
@ -146,8 +147,8 @@ func Save(ctx *cli.Context, _configPath string, c *Config) error {
} }
// save token to keyring // save token to keyring
service := ctx.App.Name service := c.Root().Name
err = keyring.Set(service, c.ServerURL, c.Token) err = keyring.Set(service, conf.ServerURL, conf.Token)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,15 +15,18 @@
package internal package internal
import ( import (
"context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"net/http" "net/http"
"os/exec"
"strconv" "strconv"
"strings" "strings"
vsc_url "github.com/gitsight/go-vcsurl"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -31,7 +34,7 @@ import (
) )
// NewClient returns a new client from the CLI context. // 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 ( var (
skip = c.Bool("skip-verify") skip = c.Bool("skip-verify")
socks = c.String("socks-proxy") socks = c.String("socks-proxy")
@ -61,8 +64,7 @@ func NewClient(c *cli.Context) (woodpecker.Client, error) {
} }
config := new(oauth2.Config) config := new(oauth2.Config)
client := config.Client( client := config.Client(ctx,
c.Context,
&oauth2.Token{ &oauth2.Token{
AccessToken: token, AccessToken: token,
}, },
@ -90,8 +92,52 @@ func NewClient(c *cli.Context) (woodpecker.Client, error) {
return woodpecker.NewClient(server, client), nil return woodpecker.NewClient(server, client), nil
} }
func getRepoFromGit(remoteName string) (string, error) {
cmd := exec.Command("git", "remote", "get-url", remoteName)
stdout, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("could not get remote url: %w", err)
}
gitRemote := strings.TrimSpace(string(stdout))
log.Debug().Str("git-remote", gitRemote).Msg("extracted remote url from git")
if len(gitRemote) == 0 {
return "", fmt.Errorf("no repository provided")
}
u, err := vsc_url.Parse(gitRemote)
if err != nil {
return "", fmt.Errorf("could not parse git remote url: %w", err)
}
repoFullName := u.FullName
log.Debug().Str("repo", repoFullName).Msg("extracted repository from remote url")
return repoFullName, nil
}
// ParseRepo parses the repository owner and name from a string. // ParseRepo parses the repository owner and name from a string.
func ParseRepo(client woodpecker.Client, str string) (repoID int64, err error) { func ParseRepo(client woodpecker.Client, str string) (repoID int64, err error) {
if str == "" {
str, err = getRepoFromGit("upstream")
if err != nil {
log.Debug().Err(err).Msg("could not get repository from git upstream remote")
}
}
if str == "" {
str, err = getRepoFromGit("origin")
if err != nil {
log.Debug().Err(err).Msg("could not get repository from git origin remote")
}
}
if str == "" {
return 0, fmt.Errorf("no repository provided")
}
if strings.Contains(str, "/") { if strings.Contains(str, "/") {
repo, err := client.RepoLookup(str) repo, err := client.RepoLookup(str)
if err != nil { if err != nil {
@ -115,3 +161,40 @@ func ParseKeyPair(p []string) map[string]string {
} }
return params 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,20 +15,19 @@
package lint package lint
import ( import (
"errors" "context"
"fmt" "fmt"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
term_env "github.com/muesli/termenv" "github.com/urfave/cli/v3"
"github.com/urfave/cli/v2"
"go.woodpecker-ci.org/woodpecker/v2/cli/common" "go.woodpecker-ci.org/woodpecker/v2/cli/common"
pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter"
"go.woodpecker-ci.org/woodpecker/v2/shared/constant"
) )
// Command exports the info command. // Command exports the info command.
@ -37,13 +36,26 @@ var Command = &cli.Command{
Usage: "lint a pipeline configuration file", Usage: "lint a pipeline configuration file",
ArgsUsage: "[path/to/.woodpecker.yaml]", ArgsUsage: "[path/to/.woodpecker.yaml]",
Action: lint, 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 environment variable is defined but empty there will be none",
},
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_PLUGINS_TRUSTED_CLONE"),
Name: "plugins-trusted-clone",
Usage: "Plugins witch are trusted to handle the netrc info in clone steps",
Value: constant.TrustedClonePlugins,
},
},
} }
func lint(c *cli.Context) error { func lint(ctx context.Context, c *cli.Command) error {
return common.RunPipelineFunc(c, lintFile, lintDir) 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 var errorStrings []string
if err := filepath.Walk(dir, func(path string, info os.FileInfo, e error) error { if err := filepath.Walk(dir, func(path string, info os.FileInfo, e error) error {
if e != nil { if e != nil {
@ -53,7 +65,7 @@ func lintDir(c *cli.Context, dir string) error {
// check if it is a regular file (not dir) // check if it is a regular file (not dir)
if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) { if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
fmt.Println("#", info.Name()) fmt.Println("#", info.Name())
if err := lintFile(c, path); err != nil { if err := lintFile(ctx, c, path); err != nil {
errorStrings = append(errorStrings, err.Error()) errorStrings = append(errorStrings, err.Error())
} }
fmt.Println("") fmt.Println("")
@ -71,9 +83,7 @@ func lintDir(c *cli.Context, dir string) error {
return nil return nil
} }
func lintFile(_ *cli.Context, file string) error { func lintFile(_ context.Context, c *cli.Command, file string) error {
output := term_env.NewOutput(os.Stdout)
fi, err := os.Open(file) fi, err := os.Open(file)
if err != nil { if err != nil {
return err return err
@ -87,7 +97,7 @@ func lintFile(_ *cli.Context, file string) error {
rawConfig := string(buf) rawConfig := string(buf)
c, err := yaml.ParseString(rawConfig) parsedConfig, err := yaml.ParseString(rawConfig)
if err != nil { if err != nil {
return err return err
} }
@ -95,40 +105,23 @@ func lintFile(_ *cli.Context, file string) error {
config := &linter.WorkflowConfig{ config := &linter.WorkflowConfig{
File: path.Base(file), File: path.Base(file),
RawConfig: rawConfig, RawConfig: rawConfig,
Workflow: c, Workflow: parsedConfig,
} }
// TODO: lint multiple files at once to allow checks for sth like "depends_on" to work // 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(true),
linter.PrivilegedPlugins(c.StringSlice("plugins-privileged")),
linter.WithTrustedClonePlugins(c.StringSlice("plugins-trusted-clone")),
).Lint([]*linter.WorkflowConfig{config})
if err != nil { if err != nil {
fmt.Printf("🔥 %s has warnings / errors:\n", output.String(config.File).Underline()) str, err := FormatLintError(config.File, err)
hasErrors := false if str != "" {
for _, err := range pipeline_errors.GetPipelineErrors(err) { fmt.Print(str)
line := " "
if err.IsWarning {
line = fmt.Sprintf("%s ⚠️ ", line)
} else {
line = fmt.Sprintf("%s ❌", line)
hasErrors = true
} }
if data := pipeline_errors.GetLinterData(err); data != nil { return err
line = fmt.Sprintf("%s %s\t%s", line, output.String(data.Field).Bold(), err.Message)
} else {
line = fmt.Sprintf("%s %s", line, err.Message)
}
// TODO: use table output
fmt.Printf("%s\n", line)
}
if hasErrors {
return errors.New("config has errors")
}
return nil
} }
fmt.Println("✅ Config is valid") fmt.Println("✅ Config is valid")

56
cli/lint/utils.go Normal file
View file

@ -0,0 +1,56 @@
package lint
import (
"errors"
"fmt"
"os"
term_env "github.com/muesli/termenv"
pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
)
func FormatLintError(file string, err error) (string, error) {
if err == nil {
return "", nil
}
output := term_env.NewOutput(os.Stdout)
str := ""
amountErrors := 0
amountWarnings := 0
linterErrors := pipeline_errors.GetPipelineErrors(err)
for _, err := range linterErrors {
line := " "
if err.IsWarning {
line = fmt.Sprintf("%s ⚠️ ", line)
amountWarnings++
} else {
line = fmt.Sprintf("%s ❌", line)
amountErrors++
}
if data := pipeline_errors.GetLinterData(err); data != nil {
line = fmt.Sprintf("%s %s\t%s", line, output.String(data.Field).Bold(), err.Message)
} else {
line = fmt.Sprintf("%s %s", line, err.Message)
}
// TODO: use table output
str = fmt.Sprintf("%s%s\n", str, line)
}
if amountErrors > 0 {
if amountWarnings > 0 {
str = fmt.Sprintf("🔥 %s has %d errors and warnings:\n%s", output.String(file).Underline(), len(linterErrors), str)
} else {
str = fmt.Sprintf("🔥 %s has %d errors:\n%s", output.String(file).Underline(), len(linterErrors), str)
}
return str, errors.New("config has errors")
}
str = fmt.Sprintf("⚠️ %s has %d warnings:\n%s", output.String(file).Underline(), len(linterErrors), str)
return str, nil
}

View file

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

View file

@ -15,10 +15,11 @@
package log package log
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
) )
@ -26,44 +27,52 @@ import (
var logPurgeCmd = &cli.Command{ var logPurgeCmd = &cli.Command{
Name: "purge", Name: "purge",
Usage: "purge a log", 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, Action: logPurge,
} }
func logPurge(c *cli.Context) (err error) { func logPurge(ctx context.Context, c *cli.Command) (err error) {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }
repoIDOrFullName := c.Args().First() 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) repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil { 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 { if err != nil {
return err return err
} }
stepArg := c.Args().Get(2) //nolint:mnd stepArg := c.Args().Get(2) //nolint:mnd
// TODO: Add lookup by name: stepID, err := internal.ParseStep(client, repoID, stepIDOrName)
var stepID int64 var stepID int64
if len(stepArg) != 0 { if len(stepArg) != 0 {
stepID, err = strconv.ParseInt(stepArg, 10, 64) stepID, err = internal.ParseStep(client, repoID, number, stepArg)
if err != nil { if err != nil {
return err return err
} }
} }
if stepID > 0 { if stepID > 0 {
fmt.Printf("Purging logs for pipeline %s#%d step %d\n", repoIDOrFullName, number, stepID)
err = client.StepLogsPurge(repoID, number, stepID) err = client.StepLogsPurge(repoID, number, stepID)
} else { } else {
fmt.Printf("Purging logs for pipeline %s#%d\n", repoIDOrFullName, number)
err = client.LogsPurge(repoID, number) err = client.LogsPurge(repoID, number)
} }
if err != nil { if err != nil {
return err return err
} }
fmt.Printf("Purging logs for pipeline %s#%d\n", repoIDOrFullName, number)
return nil return nil
} }

View file

@ -15,9 +15,11 @@
package loglevel package loglevel
import ( import (
"context"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"github.com/rs/zerolog/log" "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/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
@ -31,8 +33,8 @@ var Command = &cli.Command{
Action: logLevel, Action: logLevel,
} }
func logLevel(c *cli.Context) error { func logLevel(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,4 +1,4 @@
// Copyright 2022 Woodpecker Authors // Copyright 2024 Woodpecker Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -12,16 +12,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package migration package org
import ( import (
"src.techknowlogick.com/xormigrate" "github.com/urfave/cli/v3"
"xorm.io/xorm"
"go.woodpecker-ci.org/woodpecker/v2/cli/org/registry"
) )
var removeActiveFromUsers = xormigrate.Migration{ // Command exports the org command set.
ID: "remove-active-from-users", var Command = &cli.Command{
MigrateSession: func(sess *xorm.Session) error { Name: "org",
return dropTableColumns(sess, "users", "user_active") Usage: "manage organizations",
Commands: []*cli.Command{
registry.Command,
}, },
} }

View file

@ -0,0 +1,60 @@
// 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 registry
import (
"strconv"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
// Command exports the registry command set.
var Command = &cli.Command{
Name: "registry",
Usage: "manage organization registries",
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
},
}
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

@ -0,0 +1,84 @@
// 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 registry
import (
"context"
"os"
"strings"
"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"
)
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
ArgsUsage: "[org-id|org-full-name]",
Action: registryCreate,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "hostname",
Usage: "registry hostname",
Value: "docker.io",
},
&cli.StringFlag{
Name: "username",
Usage: "registry username",
},
&cli.StringFlag{
Name: "password",
Usage: "registry password",
},
},
}
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(ctx, c)
if err != nil {
return err
}
registry := &woodpecker.Registry{
Address: hostname,
Username: username,
Password: password,
}
if strings.HasPrefix(registry.Password, "@") {
path := strings.TrimPrefix(registry.Password, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
registry.Password = string(out)
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
_, err = client.OrgRegistryCreate(orgID, registry)
return err
}

View file

@ -0,0 +1,70 @@
// 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 registry
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
ArgsUsage: "[org-id|org-full-name]",
Action: registryInfo,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "hostname",
Usage: "registry hostname",
Value: "docker.io",
},
common.FormatFlag(tmplRegistryList, true),
},
}
func registryInfo(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
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
}
registry, err := client.OrgRegistry(orgID, hostname)
if err != nil {
return err
}
tmpl, err := template.New("_").Parse(format)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, registry)
}

View file

@ -0,0 +1,73 @@
// 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 registry
import (
"context"
"html/template"
"os"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryListCmd = &cli.Command{
Name: "ls",
Usage: "list registries",
ArgsUsage: "[org-id|org-full-name]",
Action: registryList,
Flags: []cli.Flag{
common.OrgFlag,
common.FormatFlag(tmplRegistryList, true),
},
}
func registryList(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
}
list, err := client.OrgRegistryList(orgID)
if err != nil {
return err
}
tmpl, err := template.New("_").Parse(format)
if err != nil {
return err
}
for _, registry := range list {
if err := tmpl.Execute(os.Stdout, registry); err != nil {
return err
}
}
return nil
}
// Template for registry list information.
var tmplRegistryList = "\x1b[33m{{ .Address }} \x1b[0m" + `
Username: {{ .Username }}
Email: {{ .Email }}
`

View file

@ -0,0 +1,55 @@
// 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 registry
import (
"context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryDeleteCmd = &cli.Command{
Name: "rm",
Usage: "remove a registry",
ArgsUsage: "[org-id|org-full-name]",
Action: registryDelete,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "hostname",
Usage: "registry hostname",
Value: "docker.io",
},
},
}
func registryDelete(ctx context.Context, c *cli.Command) error {
hostname := c.String("hostname")
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
return client.OrgRegistryDelete(orgID, hostname)
}

View file

@ -0,0 +1,85 @@
// 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 registry
import (
"context"
"os"
"strings"
"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"
)
var registryUpdateCmd = &cli.Command{
Name: "update",
Usage: "update a registry",
ArgsUsage: "[org-id|org-full-name]",
Action: registryUpdate,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
Name: "hostname",
Usage: "registry hostname",
Value: "docker.io",
},
&cli.StringFlag{
Name: "username",
Usage: "registry username",
},
&cli.StringFlag{
Name: "password",
Usage: "registry password",
},
},
}
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(ctx, c)
if err != nil {
return err
}
registry := &woodpecker.Registry{
Address: hostname,
Username: username,
Password: password,
}
if strings.HasPrefix(registry.Password, "@") {
path := strings.TrimPrefix(registry.Password, "@")
out, err := os.ReadFile(path)
if err != nil {
return err
}
registry.Password = string(out)
}
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
_, err = client.OrgRegistryUpdate(orgID, registry)
return err
}

View file

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

View file

@ -15,9 +15,10 @@
package pipeline package pipeline
import ( import (
"context"
"strings" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -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() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

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

View file

@ -15,9 +15,10 @@
package pipeline package pipeline
import ( import (
"context"
"strconv" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -32,9 +33,9 @@ var pipelineInfoCmd = &cli.Command{
Flags: common.OutputFlags("table"), Flags: common.OutputFlags("table"),
} }
func pipelineInfo(c *cli.Context) error { func pipelineInfo(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,11 @@
package pipeline package pipeline
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
) )
@ -31,14 +32,14 @@ var pipelineKillCmd = &cli.Command{
Hidden: true, 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) number, err := strconv.ParseInt(c.Args().Get(1), 10, 64)
if err != nil { if err != nil {
return err return err
} }
repoIDOrFullName := c.Args().First() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,7 +15,9 @@
package pipeline package pipeline
import ( import (
"github.com/urfave/cli/v2" "context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common" "go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -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() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,7 +15,9 @@
package pipeline package pipeline
import ( import (
"github.com/urfave/cli/v2" "context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common" "go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -49,19 +51,19 @@ var pipelineListCmd = &cli.Command{
}...), }...),
} }
func List(c *cli.Context) error { func List(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }
resources, err := pipelineList(c, client) resources, err := pipelineList(ctx, c, client)
if err != nil { if err != nil {
return err return err
} }
return pipelineOutput(c, resources) return pipelineOutput(c, resources)
} }
func pipelineList(c *cli.Context, client woodpecker.Client) ([]woodpecker.Pipeline, error) { func pipelineList(_ context.Context, c *cli.Command, client woodpecker.Client) ([]woodpecker.Pipeline, error) {
resources := make([]woodpecker.Pipeline, 0) resources := make([]woodpecker.Pipeline, 0)
repoIDOrFullName := c.Args().First() repoIDOrFullName := c.Args().First()
@ -78,7 +80,7 @@ func pipelineList(c *cli.Context, client woodpecker.Client) ([]woodpecker.Pipeli
branch := c.String("branch") branch := c.String("branch")
event := c.String("event") event := c.String("event")
status := c.String("status") status := c.String("status")
limit := c.Int("limit") limit := int(c.Int("limit"))
var count int var count int
for _, pipeline := range pipelines { for _, pipeline := range pipelines {

View file

@ -1,13 +1,14 @@
package pipeline package pipeline
import ( import (
"context"
"errors" "errors"
"io" "io"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "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"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker/mocks" "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker/mocks"
@ -109,12 +110,10 @@ func TestPipelineList(t *testing.T) {
mockClient.On("PipelineList", mock.Anything).Return(tt.pipelines, tt.pipelineErr) mockClient.On("PipelineList", mock.Anything).Return(tt.pipelines, tt.pipelineErr)
mockClient.On("RepoLookup", mock.Anything).Return(&woodpecker.Repo{ID: tt.repoID}, nil) 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 := pipelineListCmd
command.Action = func(c *cli.Context) error { command.Writer = io.Discard
pipelines, err := pipelineList(c, mockClient) command.Action = func(ctx context.Context, c *cli.Command) error {
pipelines, err := pipelineList(ctx, c, mockClient)
if tt.wantErr != nil { if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error()) assert.EqualError(t, err, tt.wantErr.Error())
return nil return nil
@ -126,7 +125,7 @@ func TestPipelineList(t *testing.T) {
return nil return nil
} }
_ = command.Run(c, tt.args...) _ = command.Run(context.Background(), tt.args)
}) })
} }
} }

View file

@ -15,52 +15,98 @@
package pipeline package pipeline
import ( import (
"context"
"fmt" "fmt"
"os"
"strconv" "strconv"
"text/template"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
) )
var pipelineLogsCmd = &cli.Command{ var pipelineLogsCmd = &cli.Command{
Name: "logs", Name: "logs",
Usage: "show pipeline logs", Usage: "show pipeline logs",
ArgsUsage: "<repo-id|repo-full-name> [pipeline] [stepID]", ArgsUsage: "<repo-id|repo-full-name> <pipeline> [step-number|step-name]",
Action: pipelineLogs, Action: pipelineLogs,
} }
func pipelineLogs(c *cli.Context) error { func pipelineLogs(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }
if len(repoIDOrFullName) == 0 {
return fmt.Errorf("missing required argument repo-id / repo-full-name")
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName) repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil { if err != nil {
return err return fmt.Errorf("invalid repo '%s': %w ", repoIDOrFullName, err)
} }
numberArgIndex := 1 pipelineArg := c.Args().Get(1)
number, err := strconv.ParseInt(c.Args().Get(numberArgIndex), 10, 64) 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 showPipelineLog(client, repoID, number)
}
step, err := internal.ParseStep(client, repoID, number, stepArg)
if err != nil {
return fmt.Errorf("invalid step '%s': %w", stepArg, err)
}
return showStepLog(client, repoID, number, step)
}
func showPipelineLog(client woodpecker.Client, repoID, number int64) error {
pipeline, err := client.Pipeline(repoID, number)
if err != nil { if err != nil {
return err return err
} }
stepArgIndex := 2 tmpl, err := template.New("_").Parse(tmplPipelineLogs + "\n")
step, err := strconv.ParseInt(c.Args().Get(stepArgIndex), 10, 64)
if err != nil { if err != nil {
return err 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 := showStepLog(client, repoID, number, step.ID)
if err != nil {
return err
}
}
}
return nil
}
func showStepLog(client woodpecker.Client, repoID, number, step int64) error {
logs, err := client.StepLogEntries(repoID, number, step) logs, err := client.StepLogEntries(repoID, number, step)
if err != nil { if err != nil {
return err return err
} }
for _, log := range logs { for _, log := range logs {
fmt.Print(string(log.Data)) fmt.Println(string(log.Data))
} }
return nil return nil
} }
// template for pipeline ps information.
var tmplPipelineLogs = "\x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m"

View file

@ -20,7 +20,7 @@ import (
"os" "os"
"text/template" "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/cli/output"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
@ -30,7 +30,7 @@ import (
var Command = &cli.Command{ var Command = &cli.Command{
Name: "pipeline", Name: "pipeline",
Usage: "manage pipelines", Usage: "manage pipelines",
Subcommands: []*cli.Command{ Commands: []*cli.Command{
pipelineListCmd, pipelineListCmd,
pipelineLastCmd, pipelineLastCmd,
pipelineLogsCmd, pipelineLogsCmd,
@ -46,7 +46,7 @@ var Command = &cli.Command{
}, },
} }
func pipelineOutput(c *cli.Context, resources []woodpecker.Pipeline, fd ...io.Writer) error { func pipelineOutput(c *cli.Command, resources []woodpecker.Pipeline, fd ...io.Writer) error {
outFmt, outOpt := output.ParseOutputOptions(c.String("output")) outFmt, outOpt := output.ParseOutputOptions(c.String("output"))
noHeader := c.Bool("output-no-headers") noHeader := c.Bool("output-no-headers")
@ -77,7 +77,7 @@ func pipelineOutput(c *cli.Context, resources []woodpecker.Pipeline, fd ...io.Wr
fallthrough fallthrough
default: default:
table := output.NewTable(out) table := output.NewTable(out)
cols := []string{"Number", "Status", "Event", "Branch", "Commit", "Author"} cols := []string{"Number", "Status", "Event", "Branch", "Message", "Author"}
if len(outOpt) > 0 { if len(outOpt) > 0 {
cols = outOpt cols = outOpt

View file

@ -2,11 +2,12 @@ package pipeline
import ( import (
"bytes" "bytes"
"context"
"io" "io"
"testing" "testing"
"github.com/stretchr/testify/assert" "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/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
@ -22,7 +23,7 @@ func TestPipelineOutput(t *testing.T) {
{ {
name: "table output with default columns", name: "table output with default columns",
args: []string{}, args: []string{},
expected: "NUMBER STATUS EVENT BRANCH COMMIT AUTHOR\n1 success push main abcdef John Doe\n", expected: "NUMBER STATUS EVENT BRANCH MESSAGE AUTHOR\n1 success push main message John Doe\n",
}, },
{ {
name: "table output with custom columns", name: "table output with custom columns",
@ -32,7 +33,7 @@ func TestPipelineOutput(t *testing.T) {
{ {
name: "table output with no header", name: "table output with no header",
args: []string{"output", "--output-no-headers"}, args: []string{"output", "--output-no-headers"},
expected: "1 success push main abcdef John Doe\n", expected: "1 success push main message John Doe\n",
}, },
{ {
name: "go-template output", name: "go-template output",
@ -52,20 +53,18 @@ func TestPipelineOutput(t *testing.T) {
Status: "success", Status: "success",
Event: "push", Event: "push",
Branch: "main", Branch: "main",
Commit: "abcdef", Message: "message",
Author: "John Doe", Author: "John Doe",
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
app := &cli.App{Writer: io.Discard} command := &cli.Command{
c := cli.NewContext(app, nil, nil) Writer: io.Discard,
Name: "output",
command := &cli.Command{} Flags: common.OutputFlags("table"),
command.Name = "output" Action: func(_ context.Context, c *cli.Command) error {
command.Flags = common.OutputFlags("table")
command.Action = func(c *cli.Context) error {
var buf bytes.Buffer var buf bytes.Buffer
err := pipelineOutput(c, pipelines, &buf) err := pipelineOutput(c, pipelines, &buf)
@ -78,9 +77,10 @@ func TestPipelineOutput(t *testing.T) {
assert.Equal(t, tt.expected, buf.String()) assert.Equal(t, tt.expected, buf.String())
return nil return nil
},
} }
_ = command.Run(c, tt.args...) _ = command.Run(context.Background(), tt.args)
}) })
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,44 @@
// 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 registry
import (
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
// Command exports the registry command set.
var Command = &cli.Command{
Name: "registry",
Usage: "manage registries",
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
},
}
func parseTargetArgs(client woodpecker.Client, c *cli.Command) (repoID int64, err error) {
repoIDOrFullName := c.String("repository")
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
return internal.ParseRepo(client, repoIDOrFullName)
}

View file

@ -15,10 +15,11 @@
package registry package registry
import ( import (
"context"
"os" "os"
"strings" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -48,21 +49,14 @@ var registryCreateCmd = &cli.Command{
}, },
} }
func registryCreate(c *cli.Context) error { func registryCreate(ctx context.Context, c *cli.Command) error {
var ( var (
hostname = c.String("hostname") hostname = c.String("hostname")
username = c.String("username") username = c.String("username")
password = c.String("password") password = c.String("password")
repoIDOrFullName = c.String("repository")
) )
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First() client, err := internal.NewClient(ctx, c)
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil { if err != nil {
return err return err
} }
@ -79,8 +73,12 @@ func registryCreate(c *cli.Context) error {
} }
registry.Password = string(out) registry.Password = string(out)
} }
if _, err := client.RegistryCreate(repoID, registry); err != nil {
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err return err
} }
return nil
_, err = client.RegistryCreate(repoID, registry)
return err
} }

View file

@ -15,10 +15,11 @@
package registry package registry
import ( import (
"context"
"html/template" "html/template"
"os" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -40,27 +41,27 @@ var registryInfoCmd = &cli.Command{
}, },
} }
func registryInfo(c *cli.Context) error { func registryInfo(ctx context.Context, c *cli.Command) error {
var ( var (
hostname = c.String("hostname") hostname = c.String("hostname")
repoIDOrFullName = c.String("repository")
format = c.String("format") + "\n" format = c.String("format") + "\n"
) )
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First() client, err := internal.NewClient(ctx, c)
}
client, err := internal.NewClient(c)
if err != nil { if err != nil {
return err return err
} }
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
repoID, err := parseTargetArgs(client, c)
if err != nil { if err != nil {
return err return err
} }
registry, err := client.Registry(repoID, hostname) registry, err := client.Registry(repoID, hostname)
if err != nil { if err != nil {
return err return err
} }
tmpl, err := template.New("_").Parse(format) tmpl, err := template.New("_").Parse(format)
if err != nil { if err != nil {
return err return err

View file

@ -15,10 +15,11 @@
package registry package registry
import ( import (
"context"
"html/template" "html/template"
"os" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -35,26 +36,24 @@ var registryListCmd = &cli.Command{
}, },
} }
func registryList(c *cli.Context) error { func registryList(ctx context.Context, c *cli.Command) error {
var ( format := c.String("format") + "\n"
format = c.String("format") + "\n"
repoIDOrFullName = c.String("repository") client, err := internal.NewClient(ctx, c)
)
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
if err != nil { if err != nil {
return err return err
} }
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
repoID, err := parseTargetArgs(client, c)
if err != nil { if err != nil {
return err return err
} }
list, err := client.RegistryList(repoID) list, err := client.RegistryList(repoID)
if err != nil { if err != nil {
return err return err
} }
tmpl, err := template.New("_").Parse(format) tmpl, err := template.New("_").Parse(format)
if err != nil { if err != nil {
return err return err

View file

@ -15,7 +15,9 @@
package registry package registry
import ( import (
"github.com/urfave/cli/v2" "context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common" "go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -36,21 +38,18 @@ var registryDeleteCmd = &cli.Command{
}, },
} }
func registryDelete(c *cli.Context) error { func registryDelete(ctx context.Context, c *cli.Command) error {
var ( hostname := c.String("hostname")
hostname = c.String("hostname")
repoIDOrFullName = c.String("repository") client, err := internal.NewClient(ctx, c)
)
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
if err != nil { if err != nil {
return err return err
} }
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
repoID, err := parseTargetArgs(client, c)
if err != nil { if err != nil {
return err return err
} }
return client.RegistryDelete(repoID, hostname) return client.RegistryDelete(repoID, hostname)
} }

View file

@ -15,10 +15,11 @@
package registry package registry
import ( import (
"context"
"os" "os"
"strings" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -48,24 +49,18 @@ var registryUpdateCmd = &cli.Command{
}, },
} }
func registryUpdate(c *cli.Context) error { func registryUpdate(ctx context.Context, c *cli.Command) error {
var ( var (
hostname = c.String("hostname") hostname = c.String("hostname")
username = c.String("username") username = c.String("username")
password = c.String("password") password = c.String("password")
repoIDOrFullName = c.String("repository")
) )
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First() client, err := internal.NewClient(ctx, c)
}
client, err := internal.NewClient(c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
if err != nil { if err != nil {
return err return err
} }
registry := &woodpecker.Registry{ registry := &woodpecker.Registry{
Address: hostname, Address: hostname,
Username: username, Username: username,
@ -79,6 +74,12 @@ func registryUpdate(c *cli.Context) error {
} }
registry.Password = string(out) registry.Password = string(out)
} }
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
_, err = client.RegistryUpdate(repoID, registry) _, err = client.RegistryUpdate(repoID, registry)
return err return err
} }

View file

@ -15,14 +15,16 @@
package repo package repo
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/repo/registry"
) )
// Command exports the repository command. // Command exports the repository command.
var Command = &cli.Command{ var Command = &cli.Command{
Name: "repo", Name: "repo",
Usage: "manage repositories", Usage: "manage repositories",
Subcommands: []*cli.Command{ Commands: []*cli.Command{
repoListCmd, repoListCmd,
repoInfoCmd, repoInfoCmd,
repoAddCmd, repoAddCmd,
@ -31,5 +33,6 @@ var Command = &cli.Command{
repoRepairCmd, repoRepairCmd,
repoChownCmd, repoChownCmd,
repoSyncCmd, repoSyncCmd,
registry.Command,
}, },
} }

View file

@ -15,10 +15,11 @@
package repo package repo
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
) )
@ -30,14 +31,14 @@ var repoAddCmd = &cli.Command{
Action: repoAdd, Action: repoAdd,
} }
func repoAdd(c *cli.Context) error { func repoAdd(ctx context.Context, c *cli.Command) error {
_forgeRemoteID := c.Args().First() _forgeRemoteID := c.Args().First()
forgeRemoteID, err := strconv.Atoi(_forgeRemoteID) forgeRemoteID, err := strconv.Atoi(_forgeRemoteID)
if err != nil { if err != nil {
return fmt.Errorf("invalid forge remote id: %s", _forgeRemoteID) return fmt.Errorf("invalid forge remote id: %s", _forgeRemoteID)
} }
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,9 +15,10 @@
package repo package repo
import ( import (
"context"
"fmt" "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/cli/internal"
) )
@ -29,9 +30,9 @@ var repoChownCmd = &cli.Command{
Action: repoChown, Action: repoChown,
} }
func repoChown(c *cli.Context) error { func repoChown(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,11 @@
package repo package repo
import ( import (
"context"
"os" "os"
"text/template" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -32,9 +33,9 @@ var repoInfoCmd = &cli.Command{
Flags: []cli.Flag{common.FormatFlag(tmplRepoInfo)}, Flags: []cli.Flag{common.FormatFlag(tmplRepoInfo)},
} }
func repoInfo(c *cli.Context) error { func repoInfo(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,11 @@
package repo package repo
import ( import (
"context"
"os" "os"
"text/template" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -38,8 +39,8 @@ var repoListCmd = &cli.Command{
}, },
} }
func repoList(c *cli.Context) error { func repoList(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,9 +15,10 @@
package repo package repo
import ( import (
"context"
"fmt" "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/cli/internal"
) )
@ -29,9 +30,9 @@ var repoRepairCmd = &cli.Command{
Action: repoRepair, Action: repoRepair,
} }
func repoRepair(c *cli.Context) error { func repoRepair(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,9 +15,10 @@
package repo package repo
import ( import (
"context"
"fmt" "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/cli/internal"
) )
@ -29,9 +30,9 @@ var repoRemoveCmd = &cli.Command{
Action: repoRemove, Action: repoRemove,
} }
func repoRemove(c *cli.Context) error { func repoRemove(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,11 @@
package repo package repo
import ( import (
"context"
"os" "os"
"text/template" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -33,8 +34,8 @@ var repoSyncCmd = &cli.Command{
} }
// TODO: remove this and add an option to the list cmd as we do not store the remote repo list anymore // TODO: remove this and add an option to the list cmd as we do not store the remote repo list anymore
func repoSync(c *cli.Context) error { func repoSync(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,11 @@
package repo package repo
import ( import (
"context"
"fmt" "fmt"
"time" "time"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
@ -61,9 +62,9 @@ var repoUpdateCmd = &cli.Command{
}, },
} }
func repoUpdate(c *cli.Context) error { func repoUpdate(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First() repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }
@ -78,7 +79,7 @@ func repoUpdate(c *cli.Context) error {
timeout = c.Duration("timeout") timeout = c.Duration("timeout")
trusted = c.Bool("trusted") trusted = c.Bool("trusted")
gated = c.Bool("gated") gated = c.Bool("gated")
pipelineCounter = c.Int("pipeline-counter") pipelineCounter = int(c.Int("pipeline-counter"))
unsafe = c.Bool("unsafe") unsafe = c.Bool("unsafe")
) )

View file

@ -19,7 +19,7 @@ import (
"strconv" "strconv"
"strings" "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/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
@ -29,7 +29,7 @@ import (
var Command = &cli.Command{ var Command = &cli.Command{
Name: "secret", Name: "secret",
Usage: "manage secrets", Usage: "manage secrets",
Subcommands: []*cli.Command{ Commands: []*cli.Command{
secretCreateCmd, secretCreateCmd,
secretDeleteCmd, secretDeleteCmd,
secretUpdateCmd, secretUpdateCmd,
@ -38,7 +38,7 @@ var Command = &cli.Command{
}, },
} }
func parseTargetArgs(client woodpecker.Client, c *cli.Context) (global bool, orgID, repoID int64, err error) { func parseTargetArgs(client woodpecker.Client, c *cli.Command) (global bool, orgID, repoID int64, err error) {
if c.Bool("global") { if c.Bool("global") {
return true, -1, -1, nil return true, -1, -1, nil
} }

View file

@ -15,10 +15,11 @@
package secret package secret
import ( import (
"context"
"os" "os"
"strings" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -56,8 +57,8 @@ var secretCreateCmd = &cli.Command{
}, },
} }
func secretCreate(c *cli.Context) error { func secretCreate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,12 @@
package secret package secret
import ( import (
"context"
"fmt"
"html/template" "html/template"
"os" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -45,12 +47,17 @@ var secretInfoCmd = &cli.Command{
}, },
} }
func secretInfo(c *cli.Context) error { func secretInfo(ctx context.Context, c *cli.Command) error {
var ( var (
secretName = c.String("name") secretName = c.String("name")
format = c.String("format") + "\n" format = c.String("format") + "\n"
) )
client, err := internal.NewClient(c)
if secretName == "" {
return fmt.Errorf("secret name is missing")
}
client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,11 +15,12 @@
package secret package secret
import ( import (
"context"
"html/template" "html/template"
"os" "os"
"strings" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -42,10 +43,10 @@ var secretListCmd = &cli.Command{
}, },
} }
func secretList(c *cli.Context) error { func secretList(ctx context.Context, c *cli.Command) error {
format := c.String("format") + "\n" format := c.String("format") + "\n"
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,7 +15,9 @@
package secret package secret
import ( import (
"github.com/urfave/cli/v2" "context"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common" "go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -40,10 +42,10 @@ var secretDeleteCmd = &cli.Command{
}, },
} }
func secretDelete(c *cli.Context) error { func secretDelete(ctx context.Context, c *cli.Command) error {
secretName := c.String("name") secretName := c.String("name")
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,11 @@
package secret package secret
import ( import (
"context"
"os" "os"
"strings" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -56,8 +57,8 @@ var secretUpdateCmd = &cli.Command{
}, },
} }
func secretUpdate(c *cli.Context) error { func secretUpdate(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,11 +1,12 @@
package setup package setup
import ( import (
"context"
"errors" "errors"
"strings" "strings"
"github.com/rs/zerolog/log" "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/internal/config"
"go.woodpecker-ci.org/woodpecker/v2/cli/setup/ui" "go.woodpecker-ci.org/woodpecker/v2/cli/setup/ui"
@ -15,7 +16,6 @@ import (
var Command = &cli.Command{ var Command = &cli.Command{
Name: "setup", Name: "setup",
Usage: "setup the woodpecker-cli for the first time", Usage: "setup the woodpecker-cli for the first time",
Args: true,
ArgsUsage: "[server]", ArgsUsage: "[server]",
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
@ -30,8 +30,8 @@ var Command = &cli.Command{
Action: setup, Action: setup,
} }
func setup(c *cli.Context) error { func setup(ctx context.Context, c *cli.Command) error {
_config, err := config.Get(c, c.String("config")) _config, err := config.Get(ctx, c, c.String("config"))
if err != nil { if err != nil {
return err return err
} else if _config != nil { } else if _config != nil {
@ -68,7 +68,7 @@ func setup(c *cli.Context) error {
token := c.String("token") token := c.String("token")
if token == "" { if token == "" {
token, err = receiveTokenFromUI(c.Context, serverURL) token, err = receiveTokenFromUI(ctx, serverURL)
if err != nil { if err != nil {
return err return err
} }
@ -78,7 +78,7 @@ func setup(c *cli.Context) error {
} }
} }
err = config.Save(c, c.String("config"), &config.Config{ err = config.Save(ctx, c, c.String("config"), &config.Config{
ServerURL: serverURL, ServerURL: serverURL,
Token: token, Token: token,
LogLevel: "info", LogLevel: "info",

View file

@ -1,12 +1,13 @@
package update package update
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// Command exports the update command. // Command exports the update command.
@ -22,10 +23,10 @@ var Command = &cli.Command{
Action: update, Action: update,
} }
func update(c *cli.Context) error { func update(ctx context.Context, c *cli.Command) error {
log.Info().Msg("Checking for updates ...") log.Info().Msg("Checking for updates ...")
newVersion, err := CheckForUpdate(c.Context, c.Bool("force")) newVersion, err := CheckForUpdate(ctx, c.Bool("force"))
if err != nil { if err != nil {
return err return err
} }
@ -38,7 +39,7 @@ func update(c *cli.Context) error {
log.Info().Msgf("New version %s is available! Updating ...", newVersion.Version) log.Info().Msgf("New version %s is available! Updating ...", newVersion.Version)
var tarFilePath string var tarFilePath string
tarFilePath, err = downloadNewVersion(c.Context, newVersion.AssetURL) tarFilePath, err = downloadNewVersion(ctx, newVersion.AssetURL)
if err != nil { if err != nil {
return err return err
} }

View file

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

View file

@ -15,9 +15,10 @@
package user package user
import ( import (
"context"
"fmt" "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/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
@ -30,10 +31,10 @@ var userAddCmd = &cli.Command{
Action: userAdd, Action: userAdd,
} }
func userAdd(c *cli.Context) error { func userAdd(ctx context.Context, c *cli.Command) error {
login := c.Args().First() login := c.Args().First()
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,11 +15,12 @@
package user package user
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"text/template" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -33,8 +34,8 @@ var userInfoCmd = &cli.Command{
Flags: []cli.Flag{common.FormatFlag(tmplUserInfo)}, Flags: []cli.Flag{common.FormatFlag(tmplUserInfo)},
} }
func userInfo(c *cli.Context) error { func userInfo(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

View file

@ -15,10 +15,11 @@
package user package user
import ( import (
"context"
"os" "os"
"text/template" "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/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal" "go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -32,8 +33,8 @@ var userListCmd = &cli.Command{
Flags: []cli.Flag{common.FormatFlag(tmplUserList)}, Flags: []cli.Flag{common.FormatFlag(tmplUserList)},
} }
func userList(c *cli.Context) error { func userList(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(c) client, err := internal.NewClient(ctx, c)
if err != nil { if err != nil {
return err return err
} }

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