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

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",
"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": [
{
"matchCurrentVersion": "<1.0.0",

View file

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

View file

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

View file

@ -8,11 +8,9 @@
},
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"go.buildTags": "test",
"eslint.workingDirectories": ["./web"],
"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
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ when:
steps:
- name: lint-editorconfig
image: docker.io/mstruebing/editorconfig-checker:v3.0.1
image: docker.io/mstruebing/editorconfig-checker:v3.0.3
depends_on: []
when:
- 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
- name: prettier
image: docker.io/woodpeckerci/plugin-prettier:0.1.0
image: docker.io/woodpeckerci/plugin-prettier:0.2.0
depends_on: []
settings:
version: 3.2.5
version: 3.3.3
- name: links
image: docker.io/lycheeverse/lychee:0.15.1
depends_on: []
commands:
- lychee pipeline/frontend/yaml/linter/schema/schema.json
- lychee --exclude localhost docs/docs/
- lychee --exclude localhost docs/src/pages/
- lychee --user-agent "curl/8.4.0" --exclude localhost docs/docs/
- 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
environment:
WOODPECKER_DISABLE_UPDATE_CHECK: true
WOODPECKER_PLUGINS_PRIVILEGED: 'docker.io/woodpeckerci/plugin-docker-buildx:4.2.0'
when:
- event: pull_request
path:

View file

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

View file

@ -1,5 +1,133 @@
# 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
### ❤️ Thanks to all contributors! ❤️

134
Makefile
View file

@ -8,6 +8,8 @@ ifeq ($(TARGETOS),windows)
BIN_SUFFIX := .exe
endif
DIST_DIR ?= dist
VERSION ?= next
VERSION_NUMBER ?= 0.0.0
CI_COMMIT_SHA ?= $(shell git rev-parse HEAD)
@ -27,6 +29,7 @@ else
endif
endif
TAGS ?=
LDFLAGS := -X go.woodpecker-ci.org/woodpecker/v2/version.Version=${VERSION}
STATIC_BUILD ?= true
ifeq ($(STATIC_BUILD),true)
@ -103,7 +106,7 @@ clean: ## Clean build artifacts
.PHONY: clean-all
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
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)
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
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
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
go test -timeout 120s -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 -timeout 300s -tags 'test $(TAGS)' -run TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/...
go test -race -timeout 100s -tags 'test $(TAGS)' -skip TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/...
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
(cd web/; pnpm run lint)
@ -182,7 +185,7 @@ test-ui: ui-dependencies ## Test UI code
(cd web/; pnpm run test)
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
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)
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
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
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
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="./.pnpm-store" \
--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))))) \
make release-server-xgo || exit 1; \
)
tree dist
tree ${DIST_DIR}
release-server-xgo: check-xgo ## Create server binaries for release using xgo
@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 (xgo):$(TARGETARCH_XGO)"
@echo "arch (buildx):$(TARGETARCH_BUILDX)"
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 .
@if [ "$${XGO_IN_XGO:-0}" -eq "1" ]; then echo "inside xgo image"; \
mkdir -p ./dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX); \
mv -vf /build/woodpecker-server* ./dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \
else echo "outside xgo image"; \
[ -f "./dist/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX)" ] && rm -v ./dist/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); \
# build via xgo
CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX) -tags 'netgo osusergo grpcnotrace $(TAGS)' -ldflags '-linkmode external $(LDFLAGS)' -targets '$(TARGETOS)/$(TARGETARCH_XGO)' -out woodpecker-server -pkg cmd/server .
# move binary into subfolder depending on target os and arch
@if [ "$${XGO_IN_XGO:-0}" -eq "1" ]; then \
echo "inside xgo image"; \
mkdir -p ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX); \
mv -vf /build/woodpecker-server* ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \
else \
echo "outside xgo image"; \
[ -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
@[ "$${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
# 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 -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
# 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=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=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=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=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=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=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/windows_amd64/woodpecker-agent.exe go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent
# tar binary files
tar -cvzf dist/woodpecker-agent_linux_amd64.tar.gz -C dist/agent/linux_amd64 woodpecker-agent
tar -cvzf dist/woodpecker-agent_linux_arm64.tar.gz -C dist/agent/linux_arm64 woodpecker-agent
tar -cvzf dist/woodpecker-agent_linux_arm.tar.gz -C dist/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/woodpecker-agent_darwin_amd64.tar.gz -C dist/agent/darwin_amd64 woodpecker-agent
tar -cvzf dist/woodpecker-agent_darwin_arm64.tar.gz -C dist/agent/darwin_arm64 woodpecker-agent
tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_amd64.tar.gz -C ${DIST_DIR}/agent/linux_amd64 woodpecker-agent
tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_arm64.tar.gz -C ${DIST_DIR}/agent/linux_arm64 woodpecker-agent
tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_arm.tar.gz -C ${DIST_DIR}/agent/linux_arm woodpecker-agent
tar -cvzf ${DIST_DIR}/woodpecker-agent_darwin_amd64.tar.gz -C ${DIST_DIR}/agent/darwin_amd64 woodpecker-agent
tar -cvzf ${DIST_DIR}/woodpecker-agent_darwin_arm64.tar.gz -C ${DIST_DIR}/agent/darwin_arm64 woodpecker-agent
# zip binary files
rm -f ${DIST_DIR}/woodpecker-agent_windows_amd64.zip
zip -j ${DIST_DIR}/woodpecker-agent_windows_amd64.zip ${DIST_DIR}/agent/windows_amd64/woodpecker-agent.exe
release-cli: ## Create cli binaries for release
# compile
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o dist/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=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o dist/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=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=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o dist/cli/darwin_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/windows_amd64/woodpecker-cli.exe go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli
# tar binary files
tar -cvzf dist/woodpecker-cli_linux_amd64.tar.gz -C dist/cli/linux_amd64 woodpecker-cli
tar -cvzf dist/woodpecker-cli_linux_arm64.tar.gz -C dist/cli/linux_arm64 woodpecker-cli
tar -cvzf dist/woodpecker-cli_linux_arm.tar.gz -C dist/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/woodpecker-cli_darwin_amd64.tar.gz -C dist/cli/darwin_amd64 woodpecker-cli
tar -cvzf dist/woodpecker-cli_darwin_arm64.tar.gz -C dist/cli/darwin_arm64 woodpecker-cli
tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_amd64.tar.gz -C ${DIST_DIR}/cli/linux_amd64 woodpecker-cli
tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_arm64.tar.gz -C ${DIST_DIR}/cli/linux_arm64 woodpecker-cli
tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_arm.tar.gz -C ${DIST_DIR}/cli/linux_arm woodpecker-cli
tar -cvzf ${DIST_DIR}/woodpecker-cli_darwin_amd64.tar.gz -C ${DIST_DIR}/cli/darwin_amd64 woodpecker-cli
tar -cvzf ${DIST_DIR}/woodpecker-cli_darwin_arm64.tar.gz -C ${DIST_DIR}/cli/darwin_arm64 woodpecker-cli
# zip binary files
rm -f ${DIST_DIR}/woodpecker-cli_windows_amd64.zip
zip -j ${DIST_DIR}/woodpecker-cli_windows_amd64.zip ${DIST_DIR}/cli/windows_amd64/woodpecker-cli.exe
release-checksums: ## Create checksums for all release files
# generate shas for tar files
(cd dist/; sha256sum *.* > checksums.txt)
(cd ${DIST_DIR}/; sha256sum *.* > checksums.txt)
.PHONY: release
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
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 --packager rpm
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_DIR} --packager rpm
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 --packager rpm
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_DIR} --packager rpm
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 --packager rpm
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_DIR} --packager rpm
.PHONY: bundle
bundle: bundle-agent bundle-server bundle-cli ## Create all bundles

View file

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

View file

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

View file

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

View file

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

View file

@ -25,29 +25,44 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
grpcproto "google.golang.org/protobuf/proto"
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto"
)
// Set grpc version on compile time to compare against server version response.
const ClientGrpcVersion int32 = proto.Version
const (
// Set grpc version on compile time to compare against server version response.
ClientGrpcVersion int32 = proto.Version
// Maximum size of an outgoing log message.
// Picked to prevent it from going over GRPC size limit (4 MiB) with a large safety margin.
maxLogBatchSize int = 1 * 1024 * 1024
// Maximum amount of time between sending consecutive batched log messages.
// Controls the delay between the CI job generating a log record, and web users receiving it.
maxLogFlushPeriod time.Duration = time.Second
)
type client struct {
client proto.WoodpeckerClient
conn *grpc.ClientConn
logs chan *proto.LogEntry
}
// NewGrpcClient returns a new grpc Client.
func NewGrpcClient(conn *grpc.ClientConn) rpc.Peer {
func NewGrpcClient(ctx context.Context, conn *grpc.ClientConn) rpc.Peer {
client := new(client)
client.client = proto.NewWoodpeckerClient(conn)
client.conn = conn
client.logs = make(chan *proto.LogEntry, 10) // max memory use: 10 lines * 1 MiB
go client.processLogs(ctx)
return client
}
func (c *client) Close() error {
close(c.logs)
return c.conn.Close()
}
@ -72,28 +87,28 @@ func (c *client) Version(ctx context.Context) (*rpc.Version, error) {
}
// Next returns the next workflow in the queue.
func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error) {
func (c *client) Next(ctx context.Context, filter rpc.Filter) (*rpc.Workflow, error) {
var res *proto.NextResponse
var err error
retry := c.newBackOff()
req := new(proto.NextRequest)
req.Filter = new(proto.Filter)
req.Filter.Labels = f.Labels
req.Filter.Labels = filter.Labels
for {
res, err = c.client.Next(ctx, req)
if err == nil {
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) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: next(): context canceled")
return nil, nil
}
log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err))
return nil, err
case
codes.Aborted,
codes.DataLoss,
@ -101,14 +116,22 @@ func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error)
codes.Internal,
codes.Unavailable:
// 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:
log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err))
return nil, err
}
select {
case <-time.After(retry.NextBackOff()):
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.
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()
req := new(proto.WaitRequest)
req.Id = id
req.Id = workflowID
for {
_, err = c.client.Wait(ctx, req)
if err == nil {
break
}
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: wait(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -147,7 +176,9 @@ func (c *client) Wait(ctx context.Context, id string) (err error) {
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err))
return err
}
@ -161,17 +192,14 @@ func (c *client) Wait(ctx context.Context, id string) (err error) {
}
// 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()
req := new(proto.InitRequest)
req.Id = id
req.State = new(proto.State)
req.State.Error = state.Error
req.State.ExitCode = int32(state.ExitCode)
req.State.Exited = state.Exited
req.State.Finished = state.Finished
req.Id = workflowID
req.State = new(proto.WorkflowState)
req.State.Started = state.Started
req.State.StepUuid = state.StepUUID
req.State.Finished = state.Finished
req.State.Error = state.Error
for {
_, err = c.client.Init(ctx, req)
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))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: init(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -188,7 +224,9 @@ func (c *client) Init(ctx context.Context, id string, state rpc.State) (err erro
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err))
return err
}
@ -201,18 +239,15 @@ func (c *client) Init(ctx context.Context, id string, state rpc.State) (err erro
return nil
}
// Done signals the work is complete.
func (c *client) Done(ctx context.Context, id string, state rpc.State) (err error) {
// Done signals the workflow is complete.
func (c *client) Done(ctx context.Context, workflowID string, state rpc.WorkflowState) (err error) {
retry := c.newBackOff()
req := new(proto.DoneRequest)
req.Id = id
req.State = new(proto.State)
req.State.Error = state.Error
req.State.ExitCode = int32(state.ExitCode)
req.State.Exited = state.Exited
req.State.Finished = state.Finished
req.Id = workflowID
req.State = new(proto.WorkflowState)
req.State.Started = state.Started
req.State.StepUuid = state.StepUUID
req.State.Finished = state.Finished
req.State.Error = state.Error
for {
_, err = c.client.Done(ctx, req)
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))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: done(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -229,7 +272,9 @@ func (c *client) Done(ctx context.Context, id string, state rpc.State) (err erro
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err))
return err
}
@ -243,10 +288,10 @@ func (c *client) Done(ctx context.Context, id string, state rpc.State) (err erro
}
// 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()
req := new(proto.ExtendRequest)
req.Id = id
req.Id = workflowID
for {
_, err = c.client.Extend(ctx, req)
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))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: extend(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -263,7 +316,9 @@ func (c *client) Extend(ctx context.Context, id string) (err error) {
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err))
return err
}
@ -277,17 +332,17 @@ func (c *client) Extend(ctx context.Context, id string) (err error) {
}
// 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()
req := new(proto.UpdateRequest)
req.Id = id
req.State = new(proto.State)
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.Id = workflowID
req.State = new(proto.StepState)
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 {
_, err = c.client.Update(ctx, req)
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))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: update(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -304,7 +367,9 @@ func (c *client) Update(ctx context.Context, id string, state rpc.State) (err er
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err))
return err
}
@ -317,25 +382,82 @@ func (c *client) Update(ctx context.Context, id string, state rpc.State) (err er
return nil
}
// Log writes the workflow log entry.
func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) {
retry := c.newBackOff()
req := new(proto.LogRequest)
req.LogEntry = new(proto.LogEntry)
req.LogEntry.StepUuid = logEntry.StepUUID
req.LogEntry.Data = logEntry.Data
req.LogEntry.Line = int32(logEntry.Line)
req.LogEntry.Time = logEntry.Time
req.LogEntry.Type = int32(logEntry.Type)
// EnqueueLog queues the log entry to be written in a batch later.
func (c *client) EnqueueLog(logEntry *rpc.LogEntry) {
c.logs <- &proto.LogEntry{
StepUuid: logEntry.StepUUID,
Data: logEntry.Data,
Line: int32(logEntry.Line),
Time: logEntry.Time,
Type: int32(logEntry.Type),
}
}
func (c *client) processLogs(ctx context.Context) {
var entries []*proto.LogEntry
var bytes int
send := func() {
if len(entries) == 0 {
return
}
log.Debug().
Int("entries", len(entries)).
Int("bytes", bytes).
Msg("log drain: sending queued logs")
if err := c.sendLogs(ctx, entries); err != nil {
log.Error().Err(err).Msg("log drain: could not send logs to server")
}
// even if send failed, we don't have infinite memory; retry has already been used
entries = entries[:0]
bytes = 0
}
// ctx.Done() is covered by the log channel being closed
for {
_, err = c.client.Log(ctx, req)
select {
case entry, ok := <-c.logs:
if !ok {
log.Info().Msg("log drain: channel closed")
send()
return
}
entries = append(entries, entry)
bytes += grpcproto.Size(entry) // cspell:words grpcproto
if bytes >= maxLogBatchSize {
send()
}
case <-time.After(maxLogFlushPeriod):
send()
}
}
}
func (c *client) sendLogs(ctx context.Context, entries []*proto.LogEntry) error {
req := &proto.LogRequest{LogEntries: entries}
retry := c.newBackOff()
for {
_, err := c.client.Log(ctx, req)
if err == nil {
break
}
log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: log(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -343,7 +465,9 @@ func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) {
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err))
return err
}
@ -383,6 +507,14 @@ func (c *client) ReportHealth(ctx context.Context) (err error) {
return nil
}
switch status.Code(err) {
case codes.Canceled:
if ctx.Err() != nil {
// expected as context was canceled
log.Debug().Err(err).Msgf("grpc error: report_health(): context canceled")
return nil
}
log.Error().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err))
return err
case
codes.Aborted,
codes.DataLoss,
@ -390,7 +522,9 @@ func (c *client) ReportHealth(ctx context.Context) (err error) {
codes.Internal,
codes.Unavailable:
// non-fatal errors
log.Warn().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err))
default:
log.Error().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err))
return err
}

View file

@ -23,12 +23,12 @@ import (
"time"
"github.com/rs/zerolog/log"
"github.com/tevino/abool/v2"
"google.golang.org/grpc/metadata"
"go.woodpecker-ci.org/woodpecker/v2/pipeline"
backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/v2/shared/constant"
"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")
meta, _ := metadata.FromOutgoingContext(runnerCtx)
ctxMeta := metadata.NewOutgoingContext(context.Background(), meta)
// 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 {
return err
}
if work == nil {
if workflow == nil {
return nil
}
timeout := time.Hour
if minutes := work.Timeout; minutes != 0 {
if minutes := workflow.Timeout; minutes != 0 {
timeout = time.Duration(minutes) * time.Minute
}
repoName := extractRepositoryName(work.Config) // hack
pipelineNumber := extractPipelineNumber(work.Config) // hack
repoName := extractRepositoryName(workflow.Config) // hack
pipelineNumber := extractPipelineNumber(workflow.Config) // hack
r.counter.Add(
work.ID,
workflow.ID,
timeout,
repoName,
pipelineNumber,
)
defer r.counter.Done(work.ID)
defer r.counter.Done(workflow.ID)
logger := log.With().
Str("repo", repoName).
Str("pipeline", pipelineNumber).
Str("id", work.ID).
Str("workflow_id", workflow.ID).
Logger()
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")
})
canceled := abool.New()
canceled := false
go func() {
logger.Debug().Msg("listen for cancel signal")
if err := r.client.Wait(workflowCtx, work.ID); err != nil {
canceled.SetTo(true)
if err := r.client.Wait(workflowCtx, workflow.ID); err != nil {
canceled = true
logger.Warn().Err(err).Msg("cancel signal received")
cancel()
} 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 {
case <-workflowCtx.Done():
logger.Debug().Msg("pipeline done")
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")
}
}
}
}()
state := rpc.State{}
state := rpc.WorkflowState{}
state.Started = time.Now().Unix()
err = r.client.Init(runnerCtx, work.ID, state)
err = r.client.Init(runnerCtx, workflow.ID, state)
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
//nolint:contextcheck
err = pipeline.New(work.Config,
err = pipeline.New(workflow.Config,
pipeline.WithContext(workflowCtx),
pipeline.WithTaskUUID(fmt.Sprint(work.ID)),
pipeline.WithLogger(r.createLogger(logger, &uploads, work)),
pipeline.WithTracer(r.createTracer(ctxMeta, logger, work)),
pipeline.WithTaskUUID(fmt.Sprint(workflow.ID)),
pipeline.WithLogger(r.createLogger(logger, &uploads, workflow)),
pipeline.WithTracer(r.createTracer(ctxMeta, &uploads, logger, workflow)),
pipeline.WithBackend(*r.backend),
pipeline.WithDescription(map[string]string{
"ID": work.ID,
"Repo": repoName,
"Pipeline": pipelineNumber,
"workflow_id": workflow.ID,
"repo": repoName,
"pipeline_number": pipelineNumber,
}),
).Run(runnerCtx)
state.Finished = time.Now().Unix()
state.Exited = true
if canceled.IsSet() {
state.Error = ""
state.ExitCode = pipeline.ExitCodeKilled
} else if err != nil {
pExitError := &pipeline.ExitError{}
switch {
case errors.As(err, &pExitError):
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()
}
if errors.Is(err, pipeline.ErrCancel) {
canceled = true
} else if canceled {
err = errors.Join(err, pipeline.ErrCancel)
}
if err != nil {
state.Error = err.Error()
}
logger.Debug().
Str("error", state.Error).
Int("exit_code", state.ExitCode).
Bool("canceled", canceled.IsSet()).
Msg("pipeline complete")
Bool("canceled", canceled).
Msg("workflow finished")
logger.Debug().Msg("uploading logs")
logger.Debug().Msg("uploading logs and traces / states ...")
uploads.Wait()
logger.Debug().Msg("uploading logs complete")
logger.Debug().Msg("uploaded logs and traces / states")
logger.Debug().
Str("error", state.Error).
Int("exit_code", state.ExitCode).
Msg("updating pipeline status")
Msg("updating workflow status")
if err := r.client.Done(runnerCtx, work.ID, state); err != nil {
logger.Error().Err(err).Msg("updating pipeline status failed")
doneCtx := runnerCtx
if doneCtx.Err() != nil {
doneCtx = shutdownCtx
}
if err := r.client.Done(doneCtx, workflow.ID, state); err != nil {
logger.Error().Err(err).Msg("updating workflow status failed")
} else {
logger.Debug().Msg("updating pipeline status complete")
logger.Debug().Msg("updating workflow status complete")
}
return nil

View file

@ -18,6 +18,7 @@ import (
"context"
"runtime"
"strconv"
"sync"
"time"
"github.com/rs/zerolog"
@ -26,17 +27,19 @@ import (
"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 {
uploads.Add(1)
stepLogger := logger.With().
Str("image", state.Pipeline.Step.Image).
Str("workflowID", workflow.ID).
Str("workflow_id", workflow.ID).
Err(state.Process.Error).
Int("exit_code", state.Process.ExitCode).
Bool("exited", state.Process.Exited).
Logger()
stepState := rpc.State{
stepState := rpc.StepState{
StepUUID: state.Pipeline.Step.UUID,
Exited: state.Process.Exited,
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")
uploads.Done()
}()
if state.Process.Exited {
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
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.Time, 10)
state.Pipeline.Step.Environment["CI_PIPELINE_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10)
state.Pipeline.Step.Environment["CI_PIPELINE_STARTED"] = strconv.FormatInt(state.Pipeline.Started, 10)
state.Pipeline.Step.Environment["CI_STEP_STATUS"] = "success"
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_STEP_STARTED"] = strconv.FormatInt(state.Pipeline.Started, 10)
state.Pipeline.Step.Environment["CI_SYSTEM_PLATFORM"] = runtime.GOOS + "/" + runtime.GOARCH
if state.Pipeline.Error != nil {
state.Pipeline.Step.Environment["CI_PIPELINE_STATUS"] = "failure"
state.Pipeline.Step.Environment["CI_STEP_STATUS"] = "failure"
}
return nil
}
}

View file

@ -1,10 +1,10 @@
// Copyright 2021 Woodpecker Authors
// 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
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@ -12,16 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package migration
package admin
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/admin/registry"
)
var alterTableReposDropCounter = xormigrate.Migration{
ID: "alter-table-drop-counter",
MigrateSession: func(sess *xorm.Session) error {
return dropTableColumns(sess, "repos", "repo_counter")
// Command exports the admin command set.
var Command = &cli.Command{
Name: "admin",
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");
// you may not use this file except in compliance with the License.
@ -15,14 +15,14 @@
package registry
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Command exports the registry command set.
var Command = &cli.Command{
Name: "registry",
Usage: "manage registries",
Subcommands: []*cli.Command{
Usage: "manage global registries",
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
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
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/shared/logger"
)
var GlobalFlags = append([]cli.Flag{
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_CONFIG"},
Sources: cli.EnvVars("WOODPECKER_CONFIG"),
Name: "config",
Aliases: []string{"c"},
Usage: "path to config file",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_SERVER"},
Sources: cli.EnvVars("WOODPECKER_SERVER"),
Name: "server",
Aliases: []string{"s"},
Usage: "server address",
},
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_TOKEN"},
Sources: cli.EnvVars("WOODPECKER_TOKEN"),
Name: "token",
Aliases: []string{"t"},
Usage: "server auth token",
},
&cli.BoolFlag{
EnvVars: []string{"WOODPECKER_DISABLE_UPDATE_CHECK"},
Sources: cli.EnvVars("WOODPECKER_DISABLE_UPDATE_CHECK"),
Name: "disable-update-check",
Usage: "disable update check",
},
&cli.BoolFlag{
EnvVars: []string{"WOODPECKER_SKIP_VERIFY"},
Sources: cli.EnvVars("WOODPECKER_SKIP_VERIFY"),
Name: "skip-verify",
Usage: "skip ssl verification",
Hidden: true,
},
&cli.StringFlag{
EnvVars: []string{"SOCKS_PROXY"},
Sources: cli.EnvVars("SOCKS_PROXY"),
Name: "socks-proxy",
Usage: "socks proxy address",
Hidden: true,
},
&cli.BoolFlag{
EnvVars: []string{"SOCKS_PROXY_OFF"},
Sources: cli.EnvVars("SOCKS_PROXY_OFF"),
Name: "socks-proxy-off",
Usage: "socks proxy ignored",
Hidden: true,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,12 +15,13 @@
package deploy
import (
"context"
"fmt"
"html/template"
"os"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -30,7 +31,7 @@ import (
// Command exports the deploy command.
var Command = &cli.Command{
Name: "deploy",
Usage: "deploy code",
Usage: "trigger a pipeline with the 'deployment' event",
ArgsUsage: "<repo-id|repo-full-name> <pipeline> <environment>",
Action: deploy,
Flags: []cli.Flag{
@ -38,7 +39,6 @@ var Command = &cli.Command{
&cli.StringFlag{
Name: "branch",
Usage: "branch filter",
Value: "main",
},
&cli.StringFlag{
Name: "event",
@ -58,8 +58,8 @@ var Command = &cli.Command{
},
}
func deploy(c *cli.Context) error {
client, err := internal.NewClient(c)
func deploy(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -74,6 +74,15 @@ func deploy(c *cli.Context) error {
event := c.String("event")
status := c.String("status")
if branch == "" {
repo, err := client.Repo(repoID)
if err != nil {
return err
}
branch = repo.DefaultBranch
}
pipelineArg := c.Args().Get(1)
var number int64
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");
// 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
// limitations under the License.
package migration
//go:build test
// +build test
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
package exec
var dropFiles = xormigrate.Migration{
ID: "drop-files",
MigrateSession: func(sess *xorm.Session) error {
return sess.DropTable("files")
},
import "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy"
func init() { //nolint:gochecknoinits
backends = append(backends, dummy.New())
}

View file

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

View file

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

View file

@ -15,10 +15,13 @@
package exec
import (
"context"
"encoding/json"
"fmt"
"runtime"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix"
@ -26,7 +29,7 @@ import (
)
// 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")
if platform == "" {
platform = runtime.GOOS + "/" + runtime.GOARCH
@ -40,28 +43,42 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata {
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{
Repo: metadata.Repo{
Name: repoName,
Owner: repoOwner,
RemoteID: c.String("repo-remote-id"),
ForgeURL: c.String("repo-url"),
SCM: c.String("repo-scm"),
Branch: c.String("repo-default-branch"),
CloneURL: c.String("repo-clone-url"),
CloneSSHURL: c.String("repo-clone-ssh-url"),
Private: c.Bool("repo-private"),
Trusted: c.Bool("repo-trusted"),
},
Curr: metadata.Pipeline{
Number: c.Int64("pipeline-number"),
Parent: c.Int64("pipeline-parent"),
Created: c.Int64("pipeline-created"),
Started: c.Int64("pipeline-started"),
Finished: c.Int64("pipeline-finished"),
Status: c.String("pipeline-status"),
Event: c.String("pipeline-event"),
ForgeURL: c.String("pipeline-url"),
Target: c.String("pipeline-target"),
Task: c.String("pipeline-task"),
Number: c.Int("pipeline-number"),
Parent: c.Int("pipeline-parent"),
Created: c.Int("pipeline-created"),
Started: c.Int("pipeline-started"),
Finished: c.Int("pipeline-finished"),
Status: c.String("pipeline-status"),
Event: c.String("pipeline-event"),
ForgeURL: c.String("pipeline-url"),
DeployTo: c.String("pipeline-deploy-to"),
DeployTask: c.String("pipeline-deploy-task"),
Commit: metadata.Commit{
Sha: c.String("commit-sha"),
Ref: c.String("commit-ref"),
@ -73,13 +90,16 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata {
Email: c.String("commit-author-email"),
Avatar: c.String("commit-author-avatar"),
},
PullRequestLabels: c.StringSlice("commit-pull-labels"),
IsPrerelease: c.Bool("commit-release-is-pre"),
ChangedFiles: changedFiles,
},
},
Prev: metadata.Pipeline{
Number: c.Int64("prev-pipeline-number"),
Created: c.Int64("prev-pipeline-created"),
Started: c.Int64("prev-pipeline-started"),
Finished: c.Int64("prev-pipeline-finished"),
Number: c.Int("prev-pipeline-number"),
Created: c.Int("prev-pipeline-created"),
Started: c.Int("prev-pipeline-started"),
Finished: c.Int("prev-pipeline-finished"),
Status: c.String("prev-pipeline-status"),
Event: c.String("prev-pipeline-event"),
ForgeURL: c.String("prev-pipeline-url"),
@ -98,16 +118,17 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata {
},
Workflow: metadata.Workflow{
Name: c.String("workflow-name"),
Number: c.Int("workflow-number"),
Number: int(c.Int("workflow-number")),
Matrix: axis,
},
Step: metadata.Step{
Name: c.String("step-name"),
Number: c.Int("step-number"),
Number: int(c.Int("step-number")),
},
Sys: metadata.System{
Name: c.String("system-name"),
URL: c.String("system-url"),
Host: c.String("system-host"),
Platform: platform,
Version: version.Version,
},
@ -115,5 +136,5 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata {
Type: c.String("forge-type"),
URL: c.String("forge-url"),
},
}
}, nil
}

View file

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

View file

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

View file

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

View file

@ -15,20 +15,19 @@
package lint
import (
"errors"
"context"
"fmt"
"os"
"path"
"path/filepath"
"strings"
term_env "github.com/muesli/termenv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"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/linter"
"go.woodpecker-ci.org/woodpecker/v2/shared/constant"
)
// Command exports the info command.
@ -37,13 +36,26 @@ var Command = &cli.Command{
Usage: "lint a pipeline configuration file",
ArgsUsage: "[path/to/.woodpecker.yaml]",
Action: lint,
Flags: []cli.Flag{
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_PLUGINS_PRIVILEGED"),
Name: "plugins-privileged",
Usage: "Allow plugins to run in privileged mode, if 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 {
return common.RunPipelineFunc(c, lintFile, lintDir)
func lint(ctx context.Context, c *cli.Command) error {
return common.RunPipelineFunc(ctx, c, lintFile, lintDir)
}
func lintDir(c *cli.Context, dir string) error {
func lintDir(ctx context.Context, c *cli.Command, dir string) error {
var errorStrings []string
if err := filepath.Walk(dir, func(path string, info os.FileInfo, e error) error {
if e != nil {
@ -53,7 +65,7 @@ func lintDir(c *cli.Context, dir string) error {
// check if it is a regular file (not dir)
if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
fmt.Println("#", info.Name())
if err := lintFile(c, path); err != nil {
if err := lintFile(ctx, c, path); err != nil {
errorStrings = append(errorStrings, err.Error())
}
fmt.Println("")
@ -71,9 +83,7 @@ func lintDir(c *cli.Context, dir string) error {
return nil
}
func lintFile(_ *cli.Context, file string) error {
output := term_env.NewOutput(os.Stdout)
func lintFile(_ context.Context, c *cli.Command, file string) error {
fi, err := os.Open(file)
if err != nil {
return err
@ -87,7 +97,7 @@ func lintFile(_ *cli.Context, file string) error {
rawConfig := string(buf)
c, err := yaml.ParseString(rawConfig)
parsedConfig, err := yaml.ParseString(rawConfig)
if err != nil {
return err
}
@ -95,40 +105,23 @@ func lintFile(_ *cli.Context, file string) error {
config := &linter.WorkflowConfig{
File: path.Base(file),
RawConfig: rawConfig,
Workflow: c,
Workflow: parsedConfig,
}
// TODO: lint multiple files at once to allow checks for sth like "depends_on" to work
err = linter.New(linter.WithTrusted(true)).Lint([]*linter.WorkflowConfig{config})
err = linter.New(
linter.WithTrusted(true),
linter.PrivilegedPlugins(c.StringSlice("plugins-privileged")),
linter.WithTrustedClonePlugins(c.StringSlice("plugins-trusted-clone")),
).Lint([]*linter.WorkflowConfig{config})
if err != nil {
fmt.Printf("🔥 %s has warnings / errors:\n", output.String(config.File).Underline())
str, err := FormatLintError(config.File, err)
hasErrors := false
for _, err := range pipeline_errors.GetPipelineErrors(err) {
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 {
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 str != "" {
fmt.Print(str)
}
if hasErrors {
return errors.New("config has errors")
}
return nil
return err
}
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
import (
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
)
// Command exports the log command set.
var Command = &cli.Command{
Name: "log",
Usage: "manage logs",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
logPurgeCmd,
},
}

View file

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

View file

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

View file

@ -1,10 +1,10 @@
// Copyright 2022 Woodpecker Authors
// 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
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@ -12,16 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package migration
package org
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/org/registry"
)
var removeActiveFromUsers = xormigrate.Migration{
ID: "remove-active-from-users",
MigrateSession: func(sess *xorm.Session) error {
return dropTableColumns(sess, "users", "user_active")
// Command exports the org command set.
var Command = &cli.Command{
Name: "org",
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
import (
"context"
"fmt"
"strconv"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
@ -30,9 +31,9 @@ var pipelineApproveCmd = &cli.Command{
Action: pipelineApprove,
}
func pipelineApprove(c *cli.Context) (err error) {
func pipelineApprove(ctx context.Context, c *cli.Command) (err error) {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

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

View file

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

View file

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

View file

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

View file

@ -15,7 +15,9 @@
package pipeline
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/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()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,7 +15,9 @@
package pipeline
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/internal"
@ -49,19 +51,19 @@ var pipelineListCmd = &cli.Command{
}...),
}
func List(c *cli.Context) error {
client, err := internal.NewClient(c)
func List(ctx context.Context, c *cli.Command) error {
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
resources, err := pipelineList(c, client)
resources, err := pipelineList(ctx, c, client)
if err != nil {
return err
}
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)
repoIDOrFullName := c.Args().First()
@ -78,7 +80,7 @@ func pipelineList(c *cli.Context, client woodpecker.Client) ([]woodpecker.Pipeli
branch := c.String("branch")
event := c.String("event")
status := c.String("status")
limit := c.Int("limit")
limit := int(c.Int("limit"))
var count int
for _, pipeline := range pipelines {

View file

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

View file

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

View file

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

View file

@ -2,11 +2,12 @@ package pipeline
import (
"bytes"
"context"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
@ -22,7 +23,7 @@ func TestPipelineOutput(t *testing.T) {
{
name: "table output with default columns",
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",
@ -32,7 +33,7 @@ func TestPipelineOutput(t *testing.T) {
{
name: "table output with no header",
args: []string{"output", "--output-no-headers"},
expected: "1 success push main abcdef John Doe\n",
expected: "1 success push main message John Doe\n",
},
{
name: "go-template output",
@ -48,39 +49,38 @@ func TestPipelineOutput(t *testing.T) {
pipelines := []woodpecker.Pipeline{
{
Number: 1,
Status: "success",
Event: "push",
Branch: "main",
Commit: "abcdef",
Author: "John Doe",
Number: 1,
Status: "success",
Event: "push",
Branch: "main",
Message: "message",
Author: "John Doe",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := &cli.App{Writer: io.Discard}
c := cli.NewContext(app, nil, nil)
command := &cli.Command{
Writer: io.Discard,
Name: "output",
Flags: common.OutputFlags("table"),
Action: func(_ context.Context, c *cli.Command) error {
var buf bytes.Buffer
err := pipelineOutput(c, pipelines, &buf)
command := &cli.Command{}
command.Name = "output"
command.Flags = common.OutputFlags("table")
command.Action = func(c *cli.Context) error {
var buf bytes.Buffer
err := pipelineOutput(c, pipelines, &buf)
if tt.wantErr {
assert.Error(t, err)
return nil
}
assert.NoError(t, err)
assert.Equal(t, tt.expected, buf.String())
if tt.wantErr {
assert.Error(t, err)
return nil
}
assert.NoError(t, err)
assert.Equal(t, tt.expected, buf.String())
return nil
},
}
_ = command.Run(c, tt.args...)
_ = command.Run(context.Background(), tt.args)
})
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -15,7 +15,9 @@
package registry
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/internal"
@ -36,21 +38,18 @@ var registryDeleteCmd = &cli.Command{
},
}
func registryDelete(c *cli.Context) error {
var (
hostname = c.String("hostname")
repoIDOrFullName = c.String("repository")
)
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
client, err := internal.NewClient(c)
func registryDelete(ctx context.Context, c *cli.Command) error {
hostname := c.String("hostname")
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
return client.RegistryDelete(repoID, hostname)
}

View file

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

View file

@ -15,14 +15,16 @@
package repo
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.
var Command = &cli.Command{
Name: "repo",
Usage: "manage repositories",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
repoListCmd,
repoInfoCmd,
repoAddCmd,
@ -31,5 +33,6 @@ var Command = &cli.Command{
repoRepairCmd,
repoChownCmd,
repoSyncCmd,
registry.Command,
},
}

View file

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

View file

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

View file

@ -15,10 +15,11 @@
package repo
import (
"context"
"os"
"text/template"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -32,9 +33,9 @@ var repoInfoCmd = &cli.Command{
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()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

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

View file

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

View file

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

View file

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

View file

@ -15,10 +15,11 @@
package repo
import (
"context"
"fmt"
"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/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()
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}
@ -78,7 +79,7 @@ func repoUpdate(c *cli.Context) error {
timeout = c.Duration("timeout")
trusted = c.Bool("trusted")
gated = c.Bool("gated")
pipelineCounter = c.Int("pipeline-counter")
pipelineCounter = int(c.Int("pipeline-counter"))
unsafe = c.Bool("unsafe")
)

View file

@ -19,7 +19,7 @@ import (
"strconv"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
@ -29,7 +29,7 @@ import (
var Command = &cli.Command{
Name: "secret",
Usage: "manage secrets",
Subcommands: []*cli.Command{
Commands: []*cli.Command{
secretCreateCmd,
secretDeleteCmd,
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") {
return true, -1, -1, nil
}

View file

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

View file

@ -15,10 +15,12 @@
package secret
import (
"context"
"fmt"
"html/template"
"os"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -45,12 +47,17 @@ var secretInfoCmd = &cli.Command{
},
}
func secretInfo(c *cli.Context) error {
func secretInfo(ctx context.Context, c *cli.Command) error {
var (
secretName = c.String("name")
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 {
return err
}

View file

@ -15,11 +15,12 @@
package secret
import (
"context"
"html/template"
"os"
"strings"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -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"
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

@ -15,7 +15,9 @@
package secret
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/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")
client, err := internal.NewClient(c)
client, err := internal.NewClient(ctx, c)
if err != nil {
return err
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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