1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2024-12-24 10:07:21 +02:00

Merge branch 'main' into make-cli-exec-work-with-local-backend

This commit is contained in:
6543 2024-12-12 05:23:19 +01:00 committed by GitHub
commit bc0fcdd58e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
449 changed files with 14240 additions and 8761 deletions

View File

@ -129,6 +129,7 @@
"nosniff",
"ntfy",
"octocat",
"openapi",
"opensource",
"Pacman",
"picus",
@ -250,7 +251,7 @@
"flake.lock",
"pnpm-lock.yaml",
"**/node_modules/**/*",
"cmd/server/docs/docs.go",
"cmd/server/openapi/docs.go",
"renovate.json",
// TODO: remove the following
"docs/**/*.js",

5
.ecrc
View File

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

View File

@ -2,7 +2,6 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>woodpecker-ci/renovate-config"],
"automergeType": "pr",
"enabledManagers": ["woodpecker"],
"customManagers": [
{
"customType": "regex",

3
.gitignore vendored
View File

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

View File

@ -10,11 +10,11 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/golangci/golangci-lint
rev: v1.61.0
rev: v1.62.2
hooks:
- id: golangci-lint
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.42.0
rev: v0.43.0
hooks:
- id: markdownlint
exclude: '^(docs/versioned_docs/.*|CHANGELOG.md)$'

View File

@ -2,7 +2,9 @@ when:
- event: tag
- event: pull_request
branch: ${CI_REPO_DEFAULT_BRANCH}
path: Makefile
path:
- Makefile
- .woodpecker/binaries.yaml
variables:
- &golang_image 'docker.io/golang:1.23'
@ -97,7 +99,7 @@ steps:
release:
depends_on:
- checksums
image: woodpeckerci/plugin-release:0.2.1
image: woodpeckerci/plugin-release:0.2.2
settings:
api_key:
from_secret: github_token

View File

@ -2,7 +2,7 @@ variables:
- &golang_image 'docker.io/golang:1.23'
- &node_image 'docker.io/node:23-alpine'
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.23.x'
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:5.0.0'
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:5.1.0'
- &platforms_release 'linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/386,linux/amd64,linux/ppc64le,linux/riscv64,linux/s390x,freebsd/arm64,freebsd/amd64,openbsd/arm64,openbsd/amd64'
- &platforms_server 'linux/arm/v7,linux/arm64/v8,linux/amd64,linux/ppc64le,linux/riscv64'
- &platforms_preview 'linux/amd64'
@ -41,9 +41,6 @@ variables:
when:
- event: [pull_request, tag]
- event: push
branch:
- renovate/*
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
path: *when_path
@ -61,7 +58,6 @@ steps:
path: *when_path
- branch:
- ${CI_REPO_DEFAULT_BRANCH}
- renovate/*
event: [push, tag]
path: *when_path
@ -82,7 +78,6 @@ steps:
path: *when_path
- branch:
- ${CI_REPO_DEFAULT_BRANCH}
- renovate/*
event: [push, tag]
path: *when_path
@ -104,9 +99,6 @@ steps:
evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images"'
- event: pull_request
path: *when_path
- event: push
path: *when_path
branch: renovate/*
cross-compile-server:
depends_on:
@ -169,9 +161,6 @@ steps:
- evaluate: 'not (CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images")'
event: pull_request
path: *when_path
- event: push
path: *when_path
branch: renovate/*
publish-next-server:
depends_on:

View File

@ -1,7 +1,7 @@
variables:
- &golang_image 'docker.io/golang:1.23'
- &node_image 'docker.io/node:23-alpine'
- &alpine_image 'docker.io/alpine:3.20'
- &alpine_image 'docker.io/alpine:3.21'
- path: &when_path
- 'docs/**'
- '.woodpecker/docs.yaml'
@ -31,7 +31,6 @@ when:
- <<: *docker_path
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- renovate/*
- event: pull_request_closed
path: *when_path
- event: manual
@ -60,7 +59,7 @@ steps:
- event: manual
deploy-preview:
image: docker.io/woodpeckerci/plugin-surge-preview:1.3.2
image: docker.io/woodpeckerci/plugin-surge-preview:1.3.3
settings:
path: 'docs/build/'
surge_token:
@ -74,8 +73,9 @@ steps:
deploy-prepare:
image: *alpine_image
secrets:
- BOT_PRIVATE_KEY
environment:
BOT_PRIVATE_KEY:
from_secret: BOT_PRIVATE_KEY
commands:
- apk add openssh-client git
- mkdir -p $HOME/.ssh
@ -127,8 +127,9 @@ steps:
deploy:
image: *alpine_image
secrets:
- BOT_PRIVATE_KEY
environment:
BOT_PRIVATE_KEY:
from_secret: BOT_PRIVATE_KEY
commands:
- apk add openssh-client rsync git
- mkdir -p $HOME/.ssh

31
.woodpecker/links.yaml Normal file
View File

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

View File

@ -1,6 +1,6 @@
steps:
- name: release-helper
image: woodpeckerci/plugin-ready-release-go:2.0.0
image: docker.io/woodpeckerci/plugin-ready-release-go:3.1.0
settings:
release_branch: ${CI_COMMIT_BRANCH}
forge_type: github
@ -13,5 +13,3 @@ when:
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- release/*
- event: manual
evaluate: 'TASK == "release-helper"'

View File

@ -1,12 +1,11 @@
when:
- event: [pull_request, cron]
- event: [pull_request]
- event: push
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- renovate/*
variables:
- &trivy_plugin docker.io/woodpeckerci/plugin-trivy:1.2.0
- &trivy_plugin docker.io/woodpeckerci/plugin-trivy:1.3.0
steps:
backend:
@ -35,11 +34,8 @@ steps:
services:
server:
image: *trivy_plugin
# settings:
# service: true
# db-repository: docker.io/aquasec/trivy-db:2
environment:
PLUGIN_SERVICE: 'true'
PLUGIN_DB_REPOSITORY: 'docker.io/aquasec/trivy-db:2'
settings:
service: true
db-repository: docker.io/aquasec/trivy-db:2
ports:
- 10000

View File

@ -1,16 +1,12 @@
when:
- event: pull_request
- event: push
branch: renovate/*
steps:
- name: lint-editorconfig
image: docker.io/mstruebing/editorconfig-checker:v3.0.3
image: docker.io/woodpeckerci/plugin-editorconfig-checker:0.2.0
depends_on: []
when:
- event: pull_request
- event: push
branch: renovate/*
- name: spellcheck
image: docker.io/node:23-alpine
@ -23,15 +19,7 @@ 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.2.0
image: docker.io/woodpeckerci/plugin-prettier:1.0.0
depends_on: []
settings:
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 --user-agent "curl/8.4.0" --exclude localhost docs/docs/
- lychee --user-agent "curl/8.4.0" --exclude localhost docs/src/pages/

View File

@ -10,14 +10,9 @@ variables:
# schema changes
- 'pipeline/schema/**'
event: pull_request
- event: push
branch: renovate/*
path: *when_path
when:
- event: pull_request
- event: push
branch: renovate/*
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
path: *when_path
@ -40,7 +35,8 @@ 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:5.0.0'
WOODPECKER_LINT_STRICT: true
WOODPECKER_PLUGINS_PRIVILEGED: 'docker.io/woodpeckerci/plugin-docker-buildx'
when:
- event: pull_request
path:
@ -62,14 +58,14 @@ steps:
- make lint
when: *when
check-swagger:
check-openapi:
depends_on:
- vendor
image: *golang_image
commands:
- 'make generate-swagger'
- 'make generate-openapi'
- 'DIFF=$(git diff | head)'
- '[ -n "$DIFF" ] && { echo "swagger not up to date, exec `make generate-swagger` and commit"; exit 1; } || true'
- '[ -n "$DIFF" ] && { echo "openapi not up to date, exec `make generate-openapi` and commit"; exit 1; } || true'
when: *when
lint-license-header:

View File

@ -3,7 +3,6 @@ when:
- event: push
branch:
- release/*
- renovate/*
variables:
- &node_image 'docker.io/node:23-alpine'

View File

@ -40,7 +40,7 @@ CGO_ENABLED ?= 1 # only used to compile server
HAS_GO = $(shell hash go > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
ifeq ($(HAS_GO),GO)
# renovate: datasource=docker depName=docker.io/techknowlogick/xgo
XGO_VERSION ?= go-1.22.x
XGO_VERSION ?= go-1.23.x
CGO_CFLAGS ?= $(shell go env CGO_CFLAGS)
endif
CGO_CFLAGS ?=
@ -109,15 +109,15 @@ clean: ## Clean build artifacts
clean-all: clean ## Clean all artifacts
rm -rf ${DIST_DIR} web/dist docs/build docs/node_modules web/node_modules
# delete generated
rm -rf docs/docs/40-cli.md docs/swagger.json
rm -rf docs/docs/40-cli.md docs/openapi.json
.PHONY: generate
generate: install-tools generate-swagger ## Run all code generations
generate: install-tools generate-openapi ## Run all code generations
CGO_ENABLED=0 go generate ./...
generate-swagger: install-tools ## Run swagger code generation
swag init -g server/api/ -g cmd/server/swagger.go --outputTypes go -output cmd/server/docs
CGO_ENABLED=0 go generate cmd/server/swagger.go
generate-openapi: install-tools ## Run openapi code generation and format it
go run github.com/swaggo/swag/cmd/swag fmt
CGO_ENABLED=0 go generate cmd/server/openapi.go
generate-license-header: install-tools
addlicense -c "Woodpecker Authors" -ignore "vendor/**" **/*.go
@ -134,9 +134,6 @@ install-tools: ## Install development tools
hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install mvdan.cc/gofumpt@latest; \
fi ; \
hash swag > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install github.com/swaggo/swag/cmd/swag@latest; \
fi ; \
hash addlicense > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install github.com/google/addlicense@latest; \
fi ; \
@ -196,7 +193,7 @@ test: test-agent test-server test-server-datastore test-cli test-lib ## Run all
build-ui: ## Build UI
(cd web/; pnpm install --frozen-lockfile; pnpm build)
build-server: build-ui generate-swagger ## Build server
build-server: build-ui generate-openapi ## Build server
CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-server${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/server
build-agent: ## Build agent
@ -343,6 +340,6 @@ spellcheck:
.PHONY: docs
docs: ## Generate docs (currently only for the cli)
CGO_ENABLED=0 go generate cmd/cli/app.go
CGO_ENABLED=0 go generate cmd/server/swagger.go
CGO_ENABLED=0 go generate cmd/server/openapi.go
endif

View File

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

View File

@ -29,7 +29,7 @@ import (
var Command = &cli.Command{
Name: "log-level",
ArgsUsage: "[level]",
Usage: "get the logging level of the server, or set it with [level]",
Usage: "retrieve log level from server, or set it with [level]",
Action: logLevel,
}
@ -59,6 +59,6 @@ func logLevel(ctx context.Context, c *cli.Command) error {
}
}
log.Info().Msgf("logging level: %s", ll.Level)
log.Info().Msgf("log level: %s", ll.Level)
return nil
}

View File

@ -25,8 +25,8 @@ var Command = &cli.Command{
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
registryShowCmd,
registryUpdateCmd,
},
}

View File

@ -27,7 +27,7 @@ import (
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
Usage: "add a registry",
Action: registryCreate,
Flags: []cli.Flag{
&cli.StringFlag{

View File

@ -23,6 +23,7 @@ import (
"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 registryListCmd = &cli.Command{
@ -42,7 +43,9 @@ func registryList(ctx context.Context, c *cli.Command) error {
return err
}
list, err := client.GlobalRegistryList()
opt := woodpecker.RegistryListOptions{}
list, err := client.GlobalRegistryList(opt)
if err != nil {
return err
}

View File

@ -25,10 +25,10 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
Action: registryInfo,
var registryShowCmd = &cli.Command{
Name: "show",
Usage: "show registry information",
Action: registryShow,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "hostname",
@ -39,7 +39,7 @@ var registryInfoCmd = &cli.Command{
},
}
func registryInfo(ctx context.Context, c *cli.Command) error {
func registryShow(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
format = c.String("format") + "\n"

View File

@ -0,0 +1,32 @@
// Copyright 2023 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package secret
import (
"github.com/urfave/cli/v3"
)
// Command exports the secret command.
var Command = &cli.Command{
Name: "secret",
Usage: "manage global secrets",
Commands: []*cli.Command{
secretCreateCmd,
secretDeleteCmd,
secretListCmd,
secretShowCmd,
secretUpdateCmd,
},
}

View File

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

View File

@ -33,12 +33,6 @@ var secretListCmd = &cli.Command{
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretList,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
common.FormatFlag(tmplSecretList, true),
},
}
@ -51,30 +45,13 @@ func secretList(ctx context.Context, c *cli.Command) error {
return err
}
global, orgID, repoID, err := parseTargetArgs(client, c)
opt := woodpecker.SecretListOptions{}
list, err := client.GlobalSecretList(opt)
if err != nil {
return err
}
var list []*woodpecker.Secret
switch {
case global:
list, err = client.GlobalSecretList()
if err != nil {
return err
}
case orgID != -1:
list, err = client.OrgSecretList(orgID)
if err != nil {
return err
}
default:
list, err = client.SecretList(repoID)
if err != nil {
return err
}
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)
if err != nil {
return err

View File

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

View File

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

View File

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

View File

@ -23,9 +23,9 @@ var Command = &cli.Command{
Name: "user",
Usage: "manage users",
Commands: []*cli.Command{
userListCmd,
userInfoCmd,
userAddCmd,
userListCmd,
userRemoveCmd,
userShowCmd,
},
}

View File

@ -26,7 +26,7 @@ import (
var userAddCmd = &cli.Command{
Name: "add",
Usage: "adds a user",
Usage: "add a user",
ArgsUsage: "<username>",
Action: userAdd,
}

View File

@ -23,6 +23,7 @@ import (
"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 userListCmd = &cli.Command{
@ -39,7 +40,9 @@ func userList(ctx context.Context, c *cli.Command) error {
return err
}
users, err := client.UserList()
opt := woodpecker.UserListOptions{}
users, err := client.UserList(opt)
if err != nil || len(users) == 0 {
return err
}

View File

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

View File

@ -48,19 +48,16 @@ var GlobalFlags = append([]cli.Flag{
Sources: cli.EnvVars("WOODPECKER_SKIP_VERIFY"),
Name: "skip-verify",
Usage: "skip ssl verification",
Hidden: true,
},
&cli.StringFlag{
Sources: cli.EnvVars("SOCKS_PROXY"),
Name: "socks-proxy",
Usage: "socks proxy address",
Hidden: true,
},
&cli.BoolFlag{
Sources: cli.EnvVars("SOCKS_PROXY_OFF"),
Name: "socks-proxy-off",
Usage: "socks proxy ignored",
Hidden: true,
},
}, logger.GlobalLoggerFlags...)

View File

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

View File

@ -227,7 +227,7 @@ func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, ax
Workflow: conf,
}})
if err != nil {
str, err := lint.FormatLintError(file, err)
str, err := lint.FormatLintError(file, err, false)
fmt.Print(str)
if err != nil {
return err

View File

@ -44,7 +44,7 @@ func Load(ctx context.Context, c *cli.Command) error {
}
if config.ServerURL == "" || config.Token == "" {
log.Info().Msg("The woodpecker-cli is not yet set up. Please run `woodpecker-cli setup` or provide the required environment variables / flags.")
log.Info().Msg("woodpecker-cli is not set up, run `woodpecker-cli setup` or provide required environment variables/flags")
return errors.New("woodpecker-cli is not configured")
}
@ -63,7 +63,7 @@ func Load(ctx context.Context, c *cli.Command) error {
return err
}
log.Debug().Any("config", config).Msg("Loaded config")
log.Debug().Any("config", config).Msg("loaded config")
return nil
}
@ -93,16 +93,16 @@ func Get(_ context.Context, c *cli.Command, _configPath string) (*Config, error)
return nil, err
}
log.Debug().Str("configPath", configPath).Msg("Checking for config file")
log.Debug().Str("configPath", configPath).Msg("checking for config file")
content, err := os.ReadFile(configPath)
switch {
case err != nil && !os.IsNotExist(err):
log.Debug().Err(err).Msg("Failed to read the config file")
log.Debug().Err(err).Msg("failed to read the config file")
return nil, err
case err != nil && os.IsNotExist(err):
log.Debug().Msg("The config file does not exist")
log.Debug().Msg("config file does not exist")
default:
configFromFile := &Config{}
@ -111,7 +111,7 @@ func Get(_ context.Context, c *cli.Command, _configPath string) (*Config, error)
return nil, err
}
conf.MergeIfNotSet(configFromFile)
log.Debug().Msg("Loaded config from file")
log.Debug().Msg("loaded config from file")
}
// if server or token are explicitly set, use them
@ -123,11 +123,11 @@ func Get(_ context.Context, c *cli.Command, _configPath string) (*Config, error)
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")
log.Warn().Msg("keyring is not supported on this platform")
return conf, nil
}
if errors.Is(err, keyring.ErrNotFound) {
log.Warn().Msg("Token not found in keyring")
log.Warn().Msg("token not found in keyring")
return conf, nil
}
conf.Token = secret

View File

@ -40,14 +40,19 @@ var Command = &cli.Command{
&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",
Usage: "allow plugins to run in privileged mode, if set empty, there is no",
},
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_PLUGINS_TRUSTED_CLONE"),
Name: "plugins-trusted-clone",
Usage: "Plugins which are trusted to handle the netrc info in clone steps",
Usage: "plugins that are trusted to handle Git credentials in cloning steps",
Value: constant.TrustedClonePlugins,
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_LINT_STRICT"),
Name: "strict",
Usage: "treat warnings as errors",
},
},
}
@ -119,7 +124,7 @@ func lintFile(_ context.Context, c *cli.Command, file string) error {
linter.WithTrustedClonePlugins(c.StringSlice("plugins-trusted-clone")),
).Lint([]*linter.WorkflowConfig{config})
if err != nil {
str, err := FormatLintError(config.File, err)
str, err := FormatLintError(config.File, err, c.Bool("strict"))
if str != "" {
fmt.Print(str)

View File

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

View File

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

View File

@ -29,9 +29,9 @@ var Command = &cli.Command{
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
registryShowCmd,
registryUpdateCmd,
},
}

View File

@ -28,7 +28,7 @@ import (
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
Usage: "add a registry",
ArgsUsage: "[org-id|org-full-name]",
Action: registryCreate,
Flags: []cli.Flag{

View File

@ -23,6 +23,7 @@ import (
"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 registryListCmd = &cli.Command{
@ -49,7 +50,9 @@ func registryList(ctx context.Context, c *cli.Command) error {
return err
}
list, err := client.OrgRegistryList(orgID)
opt := woodpecker.RegistryListOptions{}
list, err := client.OrgRegistryList(orgID, opt)
if err != nil {
return err
}

View File

@ -25,11 +25,11 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
var registryShowCmd = &cli.Command{
Name: "show",
Usage: "show registry information",
ArgsUsage: "[org-id|org-full-name]",
Action: registryInfo,
Action: registryShow,
Flags: []cli.Flag{
common.OrgFlag,
&cli.StringFlag{
@ -41,7 +41,7 @@ var registryInfoCmd = &cli.Command{
},
}
func registryInfo(ctx context.Context, c *cli.Command) error {
func registryShow(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
format = c.String("format") + "\n"

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

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

View File

@ -28,16 +28,11 @@ import (
var secretCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a secret",
Usage: "add a secret",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretCreate,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
&cli.StringFlag{
Name: "name",
Usage: "secret name",
@ -81,22 +76,12 @@ func secretCreate(ctx context.Context, c *cli.Command) error {
secret.Value = string(out)
}
global, orgID, repoID, err := parseTargetArgs(client, c)
orgID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
if global {
_, err = client.GlobalSecretCreate(secret)
return err
}
if orgID != -1 {
_, err = client.OrgSecretCreate(orgID, secret)
return err
}
_, err = client.SecretCreate(repoID, secret)
_, err = client.OrgSecretCreate(orgID, secret)
return err
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -74,5 +74,5 @@ func pipelineCreate(ctx context.Context, c *cli.Command) error {
return err
}
return pipelineOutput(c, []woodpecker.Pipeline{*pipeline})
return pipelineOutput(c, []*woodpecker.Pipeline{pipeline})
}

View File

@ -53,7 +53,7 @@ var Command = &cli.Command{
&cli.StringSliceFlag{
Name: "param",
Aliases: []string{"p"},
Usage: "custom parameters to be injected into the step environment. Format: KEY=value",
Usage: "custom parameters to inject into the step environment. Format: KEY=value",
},
},
}
@ -80,14 +80,14 @@ func deploy(ctx context.Context, c *cli.Command) error {
return err
}
branch = repo.DefaultBranch
branch = repo.Branch
}
pipelineArg := c.Args().Get(1)
var number int64
if pipelineArg == "last" {
// Fetch the pipeline number from the last pipeline
pipelines, err := client.PipelineList(repoID)
pipelines, err := client.PipelineList(repoID, woodpecker.PipelineListOptions{})
if err != nil {
return err
}
@ -121,9 +121,12 @@ func deploy(ctx context.Context, c *cli.Command) error {
return fmt.Errorf("please specify the target environment (i.e. production)")
}
params := internal.ParseKeyPair(c.StringSlice("param"))
opt := woodpecker.DeployOptions{
DeployTo: env,
Params: internal.ParseKeyPair(c.StringSlice("param")),
}
deploy, err := client.Deploy(repoID, number, env, params)
deploy, err := client.Deploy(repoID, number, opt)
if err != nil {
return err
}

View File

@ -48,7 +48,7 @@ func pipelineKill(ctx context.Context, c *cli.Command) (err error) {
return err
}
err = client.PipelineKill(repoID, number)
err = client.PipelineDelete(repoID, number)
if err != nil {
return err
}

View File

@ -26,7 +26,7 @@ import (
var pipelineLastCmd = &cli.Command{
Name: "last",
Usage: "show latest pipeline details",
Usage: "show latest pipeline information",
ArgsUsage: "<repo-id|repo-full-name>",
Action: pipelineLast,
Flags: append(common.OutputFlags("table"), []cli.Flag{
@ -49,10 +49,14 @@ func pipelineLast(ctx context.Context, c *cli.Command) error {
return err
}
pipeline, err := client.PipelineLast(repoID, c.String("branch"))
opt := woodpecker.PipelineLastOptions{
Branch: c.String("branch"),
}
pipeline, err := client.PipelineLast(repoID, opt)
if err != nil {
return err
}
return pipelineOutput(c, []woodpecker.Pipeline{*pipeline})
return pipelineOutput(c, []*woodpecker.Pipeline{pipeline})
}

View File

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

View File

@ -22,7 +22,7 @@ func TestPipelineList(t *testing.T) {
pipelines []*woodpecker.Pipeline
pipelineErr error
args []string
expected []woodpecker.Pipeline
expected []*woodpecker.Pipeline
wantErr error
}{
{
@ -34,53 +34,12 @@ func TestPipelineList(t *testing.T) {
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "repo/name"},
expected: []woodpecker.Pipeline{
expected: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
},
{
name: "filter by branch",
repoID: 1,
pipelines: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "--branch", "main", "repo/name"},
expected: []woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
},
{
name: "filter by event",
repoID: 1,
pipelines: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "--event", "push", "repo/name"},
expected: []woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
},
{
name: "filter by status",
repoID: 1,
pipelines: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "--status", "success", "repo/name"},
expected: []woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
},
},
{
name: "limit results",
repoID: 1,
@ -90,7 +49,7 @@ func TestPipelineList(t *testing.T) {
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
},
args: []string{"ls", "--limit", "2", "repo/name"},
expected: []woodpecker.Pipeline{
expected: []*woodpecker.Pipeline{
{ID: 1, Branch: "main", Event: "push", Status: "success"},
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
},
@ -107,13 +66,21 @@ func TestPipelineList(t *testing.T) {
for _, tt := range testtases {
t.Run(tt.name, func(t *testing.T) {
mockClient := mocks.NewClient(t)
mockClient.On("PipelineList", mock.Anything).Return(tt.pipelines, tt.pipelineErr)
mockClient.On("PipelineList", mock.Anything, mock.Anything).Return(func(_ int64, opt woodpecker.PipelineListOptions) ([]*woodpecker.Pipeline, error) {
if tt.pipelineErr != nil {
return nil, tt.pipelineErr
}
if opt.Page == 1 {
return tt.pipelines, nil
}
return []*woodpecker.Pipeline{}, nil
}).Maybe()
mockClient.On("RepoLookup", mock.Anything).Return(&woodpecker.Repo{ID: tt.repoID}, nil)
command := pipelineListCmd
command := buildPipelineListCmd()
command.Writer = io.Discard
command.Action = func(ctx context.Context, c *cli.Command) error {
pipelines, err := pipelineList(ctx, c, mockClient)
command.Action = func(_ context.Context, c *cli.Command) error {
pipelines, err := pipelineList(c, mockClient)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
return nil

View File

@ -24,5 +24,6 @@ var Command = &cli.Command{
Usage: "manage logs",
Commands: []*cli.Command{
logPurgeCmd,
logShowCmd,
},
}

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package pipeline
package log
import (
"context"
@ -27,14 +27,14 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var pipelineLogsCmd = &cli.Command{
Name: "logs",
var logShowCmd = &cli.Command{
Name: "show",
Usage: "show pipeline logs",
ArgsUsage: "<repo-id|repo-full-name> <pipeline> [step-number|step-name]",
Action: pipelineLogs,
Action: logShow,
}
func pipelineLogs(ctx context.Context, c *cli.Command) error {
func logShow(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(ctx, c)
if err != nil {
@ -59,17 +59,17 @@ func pipelineLogs(ctx context.Context, c *cli.Command) error {
stepArg := c.Args().Get(2) //nolint:mnd
if len(stepArg) == 0 {
return showPipelineLog(client, repoID, number)
return pipelineLog(client, repoID, number)
}
step, err := internal.ParseStep(client, repoID, number, stepArg)
if err != nil {
return fmt.Errorf("invalid step '%s': %w", stepArg, err)
}
return showStepLog(client, repoID, number, step)
return stepLog(client, repoID, number, step)
}
func showPipelineLog(client woodpecker.Client, repoID, number int64) error {
func pipelineLog(client woodpecker.Client, repoID, number int64) error {
pipeline, err := client.Pipeline(repoID, number)
if err != nil {
return err
@ -85,7 +85,7 @@ func showPipelineLog(client woodpecker.Client, repoID, number int64) error {
if err := tmpl.Execute(os.Stdout, map[string]any{"workflow": workflow, "step": step}); err != nil {
return err
}
err := showStepLog(client, repoID, number, step.ID)
err := stepLog(client, repoID, number, step.ID)
if err != nil {
return err
}
@ -95,7 +95,7 @@ func showPipelineLog(client woodpecker.Client, repoID, number int64) error {
return nil
}
func showStepLog(client woodpecker.Client, repoID, number, step int64) error {
func stepLog(client woodpecker.Client, repoID, number, step int64) error {
logs, err := client.StepLogEntries(repoID, number, step)
if err != nil {
return err

View File

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

View File

@ -23,7 +23,7 @@ func TestPipelineOutput(t *testing.T) {
{
name: "table output with default columns",
args: []string{},
expected: "NUMBER STATUS EVENT BRANCH MESSAGE AUTHOR\n1 success push main message John Doe\n",
expected: "NUMBER STATUS EVENT BRANCH MESSAGE AUTHOR\n1 success push main message multiline John Doe\n",
},
{
name: "table output with custom columns",
@ -33,7 +33,7 @@ func TestPipelineOutput(t *testing.T) {
{
name: "table output with no header",
args: []string{"output", "--output-no-headers"},
expected: "1 success push main message John Doe\n",
expected: "1 success push main message multiline John Doe\n",
},
{
name: "go-template output",
@ -47,14 +47,14 @@ func TestPipelineOutput(t *testing.T) {
},
}
pipelines := []woodpecker.Pipeline{
pipelines := []*woodpecker.Pipeline{
{
Number: 1,
Status: "success",
Event: "push",
Branch: "main",
Message: "message",
Author: "John Doe",
Message: "message\nmultiline",
Author: "John Doe\n",
},
}

View File

@ -25,6 +25,7 @@ import (
"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 pipelinePsCmd = &cli.Command{
@ -51,7 +52,7 @@ func pipelinePs(ctx context.Context, c *cli.Command) error {
if pipelineArg == "last" || len(pipelineArg) == 0 {
// Fetch the pipeline number from the last pipeline
pipeline, err := client.PipelineLast(repoID, "")
pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{})
if err != nil {
return err
}

157
cli/pipeline/purge.go Normal file
View File

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

105
cli/pipeline/purge_test.go Normal file
View File

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

View File

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

View File

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

View File

@ -25,8 +25,8 @@ var Command = &cli.Command{
Commands: []*cli.Command{
cronCreateCmd,
cronDeleteCmd,
cronUpdateCmd,
cronInfoCmd,
cronListCmd,
cronShowCmd,
cronUpdateCmd,
},
}

View File

@ -23,6 +23,7 @@ import (
"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 cronListCmd = &cli.Command{
@ -52,7 +53,8 @@ func cronList(ctx context.Context, c *cli.Command) error {
if err != nil {
return err
}
list, err := client.CronList(repoID)
opt := woodpecker.CronListOptions{}
list, err := client.CronList(repoID, opt)
if err != nil {
return err
}

View File

@ -25,11 +25,11 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var cronInfoCmd = &cli.Command{
Name: "info",
Usage: "display info about a cron job",
var cronShowCmd = &cli.Command{
Name: "show",
Usage: "show cron job information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: cronInfo,
Action: cronShow,
Flags: []cli.Flag{
common.RepoFlag,
&cli.StringFlag{
@ -41,7 +41,7 @@ var cronInfoCmd = &cli.Command{
},
}
func cronInfo(ctx context.Context, c *cli.Command) error {
func cronShow(ctx context.Context, c *cli.Command) error {
var (
cronID = c.Int("id")
repoIDOrFullName = c.String("repository")

View File

@ -28,9 +28,9 @@ var Command = &cli.Command{
Commands: []*cli.Command{
registryCreateCmd,
registryDeleteCmd,
registryUpdateCmd,
registryInfoCmd,
registryListCmd,
registryShowCmd,
registryUpdateCmd,
},
}

View File

@ -28,7 +28,7 @@ import (
var registryCreateCmd = &cli.Command{
Name: "add",
Usage: "adds a registry",
Usage: "add a registry",
ArgsUsage: "[repo-id|repo-full-name]",
Action: registryCreate,
Flags: []cli.Flag{

View File

@ -23,6 +23,7 @@ import (
"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 registryListCmd = &cli.Command{
@ -49,7 +50,9 @@ func registryList(ctx context.Context, c *cli.Command) error {
return err
}
list, err := client.RegistryList(repoID)
opt := woodpecker.RegistryListOptions{}
list, err := client.RegistryList(repoID, opt)
if err != nil {
return err
}

View File

@ -25,11 +25,11 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var registryInfoCmd = &cli.Command{
Name: "info",
Usage: "display registry info",
var registryShowCmd = &cli.Command{
Name: "show",
Usage: "show registry information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: registryInfo,
Action: registryShow,
Flags: []cli.Flag{
common.RepoFlag,
&cli.StringFlag{
@ -41,7 +41,7 @@ var registryInfoCmd = &cli.Command{
},
}
func registryInfo(ctx context.Context, c *cli.Command) error {
func registryShow(ctx context.Context, c *cli.Command) error {
var (
hostname = c.String("hostname")
format = c.String("format") + "\n"

View File

@ -17,7 +17,9 @@ package repo
import (
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/repo/cron"
"go.woodpecker-ci.org/woodpecker/v2/cli/repo/registry"
"go.woodpecker-ci.org/woodpecker/v2/cli/repo/secret"
)
// Command exports the repository command.
@ -25,14 +27,16 @@ var Command = &cli.Command{
Name: "repo",
Usage: "manage repositories",
Commands: []*cli.Command{
repoListCmd,
repoInfoCmd,
repoAddCmd,
repoUpdateCmd,
repoChownCmd,
cron.Command,
repoListCmd,
registry.Command,
repoRemoveCmd,
repoRepairCmd,
repoChownCmd,
secret.Command,
repoShowCmd,
repoSyncCmd,
registry.Command,
repoUpdateCmd,
},
}

View File

@ -22,6 +22,7 @@ import (
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
)
var repoAddCmd = &cli.Command{
@ -43,7 +44,11 @@ func repoAdd(ctx context.Context, c *cli.Command) error {
return err
}
repo, err := client.RepoPost(int64(forgeRemoteID))
opt := woodpecker.RepoPostOptions{
ForgeRemoteID: int64(forgeRemoteID),
}
repo, err := client.RepoPost(opt)
if err != nil {
return err
}

View File

@ -23,6 +23,7 @@ import (
"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 repoListCmd = &cli.Command{
@ -36,6 +37,10 @@ var repoListCmd = &cli.Command{
Name: "org",
Usage: "filter by organization",
},
&cli.BoolFlag{
Name: "all",
Usage: "query all repos, including inactive ones",
},
},
}
@ -45,7 +50,11 @@ func repoList(ctx context.Context, c *cli.Command) error {
return err
}
repos, err := client.RepoList()
opt := woodpecker.RepoListOptions{
All: c.Bool("all"),
}
repos, err := client.RepoList(opt)
if err != nil || len(repos) == 0 {
return err
}
@ -68,4 +77,4 @@ func repoList(ctx context.Context, c *cli.Command) error {
}
// Template for repository list items.
var tmplRepoList = "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }})"
var tmplRepoList = "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }}, isActive: {{ .IsActive }})"

View File

@ -25,15 +25,15 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
)
var repoInfoCmd = &cli.Command{
Name: "info",
Usage: "show repository details",
var repoShowCmd = &cli.Command{
Name: "show",
Usage: "show repository information",
ArgsUsage: "<repo-id|repo-full-name>",
Action: repoInfo,
Action: repoShow,
Flags: []cli.Flag{common.FormatFlag(tmplRepoInfo)},
}
func repoInfo(ctx context.Context, c *cli.Command) error {
func repoShow(ctx context.Context, c *cli.Command) error {
repoIDOrFullName := c.Args().First()
client, err := internal.NewClient(ctx, c)
if err != nil {
@ -65,6 +65,7 @@ Visibility: {{ .Visibility }}
Private: {{ .IsSCMPrivate }}
Trusted: {{ .IsTrusted }}
Gated: {{ .IsGated }}
Require approval for: {{ .RequireApproval }}
Clone url: {{ .Clone }}
Allow pull-requests: {{ .AllowPullRequests }}
`

View File

@ -23,6 +23,7 @@ import (
"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 repoSyncCmd = &cli.Command{
@ -40,7 +41,11 @@ func repoSync(ctx context.Context, c *cli.Command) error {
return err
}
repos, err := client.RepoListOpts(true)
opt := woodpecker.RepoListOptions{
All: true,
}
repos, err := client.RepoList(opt)
if err != nil || len(repos) == 0 {
return err
}

View File

@ -36,8 +36,12 @@ var repoUpdateCmd = &cli.Command{
Usage: "repository is trusted",
},
&cli.BoolFlag{
Name: "gated",
Usage: "repository is gated",
Name: "gated", // TODO: remove in next release
Hidden: true,
},
&cli.StringFlag{
Name: "require-approval",
Usage: "repository requires approval for",
},
&cli.DurationFlag{
Name: "timeout",
@ -49,7 +53,7 @@ var repoUpdateCmd = &cli.Command{
},
&cli.StringFlag{
Name: "config",
Usage: "repository configuration path (e.g. .woodpecker.yml)",
Usage: "repository configuration path. Example: .woodpecker.yml",
},
&cli.IntFlag{
Name: "pipeline-counter",
@ -57,7 +61,7 @@ var repoUpdateCmd = &cli.Command{
},
&cli.BoolFlag{
Name: "unsafe",
Usage: "validate updating the pipeline-counter is unsafe",
Usage: "allow unsafe operations",
},
},
}
@ -78,7 +82,7 @@ func repoUpdate(ctx context.Context, c *cli.Command) error {
config = c.String("config")
timeout = c.Duration("timeout")
trusted = c.Bool("trusted")
gated = c.Bool("gated")
requireApproval = c.String("require-approval")
pipelineCounter = int(c.Int("pipeline-counter"))
unsafe = c.Bool("unsafe")
)
@ -87,8 +91,18 @@ func repoUpdate(ctx context.Context, c *cli.Command) error {
if c.IsSet("trusted") {
patch.IsTrusted = &trusted
}
// TODO: remove in next release
if c.IsSet("gated") {
patch.IsGated = &gated
return fmt.Errorf("'gated' option has been set in version 2.8, use 'require-approval' in >= 3.0")
}
if c.IsSet("require-approval") {
if mode := woodpecker.ApprovalMode(requireApproval); mode.Valid() {
patch.RequireApproval = &mode
} else {
return fmt.Errorf("update approval mode failed: '%s' is no valid mode", mode)
}
}
if c.IsSet("timeout") {
v := int64(timeout / time.Minute)

View File

@ -15,10 +15,6 @@
package secret
import (
"fmt"
"strconv"
"strings"
"github.com/urfave/cli/v3"
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
@ -32,52 +28,17 @@ var Command = &cli.Command{
Commands: []*cli.Command{
secretCreateCmd,
secretDeleteCmd,
secretUpdateCmd,
secretInfoCmd,
secretListCmd,
secretShowCmd,
secretUpdateCmd,
},
}
func parseTargetArgs(client woodpecker.Client, c *cli.Command) (global bool, orgID, repoID int64, err error) {
if c.Bool("global") {
return true, -1, -1, nil
}
func parseTargetArgs(client woodpecker.Client, c *cli.Command) (repoID int64, err error) {
repoIDOrFullName := c.String("repository")
if repoIDOrFullName == "" {
repoIDOrFullName = c.Args().First()
}
orgIDOrName := c.String("organization")
if orgIDOrName == "" && repoIDOrFullName == "" {
if err := cli.ShowSubcommandHelp(c); err != nil {
return false, -1, -1, err
}
return false, -1, -1, fmt.Errorf("missing arguments")
}
if orgIDOrName != "" && repoIDOrFullName == "" {
if orgID, err := strconv.ParseInt(orgIDOrName, 10, 64); err == nil {
return false, orgID, -1, nil
}
org, err := client.OrgLookup(orgIDOrName)
if err != nil {
return false, -1, -1, err
}
return false, org.ID, -1, nil
}
if orgIDOrName != "" && !strings.Contains(repoIDOrFullName, "/") {
repoIDOrFullName = orgIDOrName + "/" + repoIDOrFullName
}
repoID, err = internal.ParseRepo(client, repoIDOrFullName)
if err != nil {
return false, -1, -1, err
}
return false, -1, repoID, nil
return internal.ParseRepo(client, repoIDOrFullName)
}

View File

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

View File

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

View File

@ -29,11 +29,6 @@ var secretDeleteCmd = &cli.Command{
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretDelete,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
&cli.StringFlag{
Name: "name",
@ -50,16 +45,10 @@ func secretDelete(ctx context.Context, c *cli.Command) error {
return err
}
global, orgID, repoID, err := parseTargetArgs(client, c)
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
if global {
return client.GlobalSecretDelete(secretName)
}
if orgID != -1 {
return client.OrgSecretDelete(orgID, secretName)
}
return client.SecretDelete(repoID, secretName)
}

View File

@ -32,11 +32,6 @@ var secretUpdateCmd = &cli.Command{
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretUpdate,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
&cli.StringFlag{
Name: "name",
@ -48,11 +43,11 @@ var secretUpdateCmd = &cli.Command{
},
&cli.StringSliceFlag{
Name: "event",
Usage: "secret limited to these events",
Usage: "limit secret to these events",
},
&cli.StringSliceFlag{
Name: "image",
Usage: "secret limited to these images",
Usage: "limit secret to these images",
},
},
}
@ -78,19 +73,11 @@ func secretUpdate(ctx context.Context, c *cli.Command) error {
secret.Value = string(out)
}
global, orgID, repoID, err := parseTargetArgs(client, c)
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
if global {
_, err = client.GlobalSecretUpdate(secret)
return err
}
if orgID != -1 {
_, err = client.OrgSecretUpdate(orgID, secret)
return err
}
_, err = client.SecretUpdate(repoID, secret)
return err
}

View File

@ -24,20 +24,14 @@ import (
"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 secretInfoCmd = &cli.Command{
Name: "info",
Usage: "display secret info",
var secretShowCmd = &cli.Command{
Name: "show",
Usage: "show secret information",
ArgsUsage: "[repo-id|repo-full-name]",
Action: secretInfo,
Action: secretShow,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "global",
Usage: "global secret",
},
common.OrgFlag,
common.RepoFlag,
&cli.StringFlag{
Name: "name",
@ -47,7 +41,7 @@ var secretInfoCmd = &cli.Command{
},
}
func secretInfo(ctx context.Context, c *cli.Command) error {
func secretShow(ctx context.Context, c *cli.Command) error {
var (
secretName = c.String("name")
format = c.String("format") + "\n"
@ -62,28 +56,14 @@ func secretInfo(ctx context.Context, c *cli.Command) error {
return err
}
global, orgID, repoID, err := parseTargetArgs(client, c)
repoID, err := parseTargetArgs(client, c)
if err != nil {
return err
}
var secret *woodpecker.Secret
switch {
case global:
secret, err = client.GlobalSecret(secretName)
if err != nil {
return err
}
case orgID != -1:
secret, err = client.OrgSecret(orgID, secretName)
if err != nil {
return err
}
default:
secret, err = client.Secret(repoID, secretName)
if err != nil {
return err
}
secret, err := client.Secret(repoID, secretName)
if err != nil {
return err
}
tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format)

View File

@ -20,11 +20,11 @@ var Command = &cli.Command{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "server",
Usage: "The URL of the woodpecker server",
Usage: "URL of the woodpecker server",
},
&cli.StringFlag{
Name: "token",
Usage: "The token to authenticate with the woodpecker server",
Usage: "token to authenticate with the woodpecker server",
},
},
Action: setup,
@ -41,7 +41,7 @@ func setup(ctx context.Context, c *cli.Command) error {
}
if !setupAgain {
log.Info().Msg("Configuration skipped")
log.Info().Msg("configuration skipped")
return nil
}
}
@ -87,7 +87,7 @@ func setup(ctx context.Context, c *cli.Command) error {
return err
}
log.Info().Msg("The woodpecker-cli has been successfully setup")
log.Info().Msg("woodpecker-cli has been successfully setup")
return nil
}

View File

@ -24,12 +24,12 @@ func receiveTokenFromUI(c context.Context, serverURL string) (string, error) {
srv.Handler = setupRouter(tokenReceived)
go func() {
log.Debug().Msgf("Listening for token response on :%d", port)
log.Debug().Msgf("listening for token response on :%d", port)
_ = srv.ListenAndServe()
}()
defer func() {
log.Debug().Msg("Shutting down server")
log.Debug().Msg("shutting down server")
_ = srv.Shutdown(c)
}()
@ -90,7 +90,7 @@ func setupRouter(tokenReceived chan string) *gin.Engine {
err := c.BindJSON(&data)
if err != nil {
log.Debug().Err(err).Msg("Failed to bind JSON")
log.Debug().Err(err).Msg("failed to bind JSON")
c.JSON(http.StatusBadRequest, gin.H{
"error": "invalid request",
})
@ -110,7 +110,7 @@ func setupRouter(tokenReceived chan string) *gin.Engine {
func openBrowser(url string) error {
var err error
log.Debug().Msgf("Opening browser with URL: %s", url)
log.Debug().Msgf("opening browser with URL: %s", url)
switch runtime.GOOS {
case "linux":

View File

@ -24,7 +24,7 @@ var Command = &cli.Command{
}
func update(ctx context.Context, c *cli.Command) error {
log.Info().Msg("Checking for updates ...")
log.Info().Msg("checking for updates ...")
newVersion, err := CheckForUpdate(ctx, c.Bool("force"))
if err != nil {
@ -32,11 +32,11 @@ func update(ctx context.Context, c *cli.Command) error {
}
if newVersion == nil {
fmt.Println("You are using the latest version of woodpecker-cli")
fmt.Println("you are using the latest version of woodpecker-cli")
return nil
}
log.Info().Msgf("New version %s is available! Updating ...", newVersion.Version)
log.Info().Msgf("new version %s is available! Updating ...", newVersion.Version)
var tarFilePath string
tarFilePath, err = downloadNewVersion(ctx, newVersion.AssetURL)
@ -44,14 +44,14 @@ func update(ctx context.Context, c *cli.Command) error {
return err
}
log.Debug().Msgf("New version %s has been downloaded successfully! Installing ...", newVersion.Version)
log.Debug().Msgf("new version %s has been downloaded successfully! Installing ...", newVersion.Version)
binFile, err := extractNewVersion(tarFilePath)
if err != nil {
return err
}
log.Debug().Msgf("New version %s has been extracted to %s", newVersion.Version, binFile)
log.Debug().Msgf("new version %s has been extracted to %s", newVersion.Version, binFile)
executablePathOrSymlink, err := os.Executable()
if err != nil {

View File

@ -22,10 +22,10 @@ func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) {
}
func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVersion, error) {
log.Debug().Msgf("Current version: %s", version.String())
log.Debug().Msgf("current version: %s", version.String())
if (version.String() == "dev" || strings.HasPrefix(version.String(), "next-")) && !force {
log.Debug().Msgf("Skipping update check for development & next versions")
log.Debug().Msgf("skipping update check for development/next versions")
return nil, nil
}
@ -61,11 +61,11 @@ func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVer
// using the latest release
if installedVersion == upstreamVersion && !force {
log.Debug().Msgf("No new version available")
log.Debug().Msgf("no new version available")
return nil, nil
}
log.Debug().Msgf("New version available: %s", upstreamVersion)
log.Debug().Msgf("new version available: %s", upstreamVersion)
assetURL := fmt.Sprintf(githubBinaryURL, upstreamVersion, runtime.GOOS, runtime.GOARCH)
return &NewVersion{
@ -75,7 +75,7 @@ func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVer
}
func downloadNewVersion(ctx context.Context, downloadURL string) (string, error) {
log.Debug().Msgf("Downloading new version from %s ...", downloadURL)
log.Debug().Msgf("downloading new version from %s ...", downloadURL)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil)
if err != nil {
@ -102,13 +102,13 @@ func downloadNewVersion(ctx context.Context, downloadURL string) (string, error)
return "", err
}
log.Debug().Msgf("New version downloaded to %s", file.Name())
log.Debug().Msgf("new version downloaded to %s", file.Name())
return file.Name(), nil
}
func extractNewVersion(tarFilePath string) (string, error) {
log.Debug().Msgf("Extracting new version from %s ...", tarFilePath)
log.Debug().Msgf("extracting new version from %s ...", tarFilePath)
tarFile, err := os.Open(tarFilePath)
if err != nil {
@ -132,7 +132,7 @@ func extractNewVersion(tarFilePath string) (string, error) {
return "", err
}
log.Debug().Msgf("New version extracted to %s", tmpDir)
log.Debug().Msgf("new version extracted to %s", tmpDir)
return path.Join(tmpDir, "woodpecker-cli"), nil
}

View File

@ -36,6 +36,9 @@ var flags = []cli.Flag{
Sources: cli.NewValueSourceChain(
cli.File(os.Getenv("WOODPECKER_AGENT_SECRET_FILE")),
cli.EnvVar("WOODPECKER_AGENT_SECRET")),
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_GRPC_SECURE"),

View File

@ -19,20 +19,14 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/cli/admin"
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
"go.woodpecker-ci.org/woodpecker/v2/cli/cron"
"go.woodpecker-ci.org/woodpecker/v2/cli/deploy"
"go.woodpecker-ci.org/woodpecker/v2/cli/exec"
"go.woodpecker-ci.org/woodpecker/v2/cli/info"
"go.woodpecker-ci.org/woodpecker/v2/cli/lint"
"go.woodpecker-ci.org/woodpecker/v2/cli/log"
"go.woodpecker-ci.org/woodpecker/v2/cli/loglevel"
"go.woodpecker-ci.org/woodpecker/v2/cli/org"
"go.woodpecker-ci.org/woodpecker/v2/cli/pipeline"
"go.woodpecker-ci.org/woodpecker/v2/cli/repo"
"go.woodpecker-ci.org/woodpecker/v2/cli/secret"
"go.woodpecker-ci.org/woodpecker/v2/cli/setup"
"go.woodpecker-ci.org/woodpecker/v2/cli/update"
"go.woodpecker-ci.org/woodpecker/v2/cli/user"
"go.woodpecker-ci.org/woodpecker/v2/version"
)
@ -49,18 +43,12 @@ func newApp() *cli.Command {
app.Suggest = true
app.Commands = []*cli.Command{
admin.Command,
org.Command,
repo.Command,
pipeline.Command,
log.Command,
deploy.Command,
exec.Command,
info.Command,
secret.Command,
user.Command,
lint.Command,
loglevel.Command,
cron.Command,
org.Command,
pipeline.Command,
repo.Command,
setup.Command,
update.Command,
}

View File

@ -93,16 +93,6 @@ var flags = append([]cli.Flag{
Name: "custom-js-file",
Usage: "file path for the server to serve a custom .JS file, used for customizing the UI",
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_LETS_ENCRYPT_EMAIL"),
Name: "lets-encrypt-email",
Usage: "let's encrypt email",
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_LETS_ENCRYPT"),
Name: "lets-encrypt",
Usage: "enable let's encrypt",
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_GRPC_ADDR"),
Name: "grpc-addr",
@ -116,6 +106,9 @@ var flags = append([]cli.Flag{
Name: "grpc-secret",
Usage: "grpc jwt secret",
Value: "secret",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_METRICS_SERVER_ADDR"),
@ -173,6 +166,11 @@ var flags = append([]cli.Flag{
Usage: "The maximum time in minutes you can set in the repo settings before a pipeline gets killed",
Value: 120,
},
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_DEFAULT_WORKFLOW_LABELS"),
Name: "default-workflow-labels",
Usage: "The default label filter to set for workflows that has no label filter set. By default workflows will be allowed to run on any agent, if not specified in the workflow.",
},
&cli.DurationFlag{
Sources: cli.EnvVars("WOODPECKER_SESSION_EXPIRES"),
Name: "session-expires",
@ -187,7 +185,7 @@ var flags = append([]cli.Flag{
&cli.StringSliceFlag{
Sources: cli.EnvVars("WOODPECKER_PLUGINS_TRUSTED_CLONE"),
Name: "plugins-trusted-clone",
Usage: "Plugins which are trusted to handle the netrc info in clone steps",
Usage: "Plugins which are trusted to handle Git credentials in clone steps",
Value: constant.TrustedClonePlugins,
},
&cli.StringSliceFlag{
@ -212,6 +210,14 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_AGENT_SECRET")),
Name: "agent-secret",
Usage: "server-agent shared password",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_DISABLE_USER_AGENT_REGISTRATION"),
Name: "disable-user-agent-registration",
Usage: "Disable user registered agents",
},
&cli.DurationFlag{
Sources: cli.EnvVars("WOODPECKER_KEEPALIVE_MIN_TIME"),
@ -238,6 +244,9 @@ var flags = append([]cli.Flag{
Aliases: []string{"datasource"}, // TODO: remove in v4.0.0
Usage: "database driver configuration string",
Value: datasourceDefaultValue(),
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.NewValueSourceChain(
@ -245,6 +254,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_PROMETHEUS_AUTH_TOKEN")),
Name: "prometheus-auth-token",
Usage: "token to secure prometheus metrics endpoint",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_STATUS_CONTEXT", "WOODPECKER_GITHUB_CONTEXT", "WOODPECKER_GITEA_CONTEXT"),
@ -344,6 +356,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_BITBUCKET_DC_CLIENT_ID")),
Name: "forge-oauth-client",
Usage: "oauth2 client id",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.NewValueSourceChain(
@ -365,6 +380,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_BITBUCKET_DC_CLIENT_SECRET")),
Name: "forge-oauth-secret",
Usage: "oauth2 client secret",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.BoolFlag{
Name: "forge-skip-verify",
@ -456,6 +474,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_BITBUCKET_DC_GIT_USERNAME")),
Name: "bitbucket-dc-git-username",
Usage: "Bitbucket DataCenter/Server service account username",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.NewValueSourceChain(
@ -463,6 +484,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_BITBUCKET_DC_GIT_PASSWORD")),
Name: "bitbucket-dc-git-password",
Usage: "Bitbucket DataCenter/Server service account password",
Config: cli.StringConfig{
TrimSpace: true,
},
},
//
// development flags
@ -490,6 +514,9 @@ var flags = append([]cli.Flag{
cli.EnvVar("WOODPECKER_ENCRYPTION_KEY")),
Name: "encryption-raw-key",
Usage: "Raw encryption key",
Config: cli.StringConfig{
TrimSpace: true,
},
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_ENCRYPTION_TINK_KEYSET_FILE"),

View File

@ -39,7 +39,7 @@ func pinger(_ context.Context, c *cli.Command) error {
}
// if woodpecker do ssl on it's own
if c.String("server-cert") != "" || c.Bool("lets-encrypt") {
if c.String("server-cert") != "" {
scheme = "https"
}

View File

@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !generate
// +build !generate
package main
import (
@ -22,7 +25,7 @@ import (
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v3"
_ "go.woodpecker-ci.org/woodpecker/v2/cmd/server/docs"
_ "go.woodpecker-ci.org/woodpecker/v2/cmd/server/openapi"
"go.woodpecker-ci.org/woodpecker/v2/shared/utils"
"go.woodpecker-ci.org/woodpecker/v2/version"
)
@ -46,7 +49,7 @@ func main() {
}
app.Flags = flags
setupSwaggerStaticConfig()
setupOpenAPIStaticConfig()
if err := app.Run(ctx, os.Args); err != nil {
log.Error().Err(err).Msgf("error running server")

38
cmd/server/openapi.go Normal file
View File

@ -0,0 +1,38 @@
// 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 main
import (
"go.woodpecker-ci.org/woodpecker/v2/cmd/server/openapi"
"go.woodpecker-ci.org/woodpecker/v2/version"
)
// Generate docs/openapi.json via:
//go:generate go run github.com/swaggo/swag/cmd/swag init -g cmd/server/openapi.go --outputTypes go -output openapi -d ../../
//go:generate go run openapi_json_gen.go openapi.go
//go:generate go run github.com/getkin/kin-openapi/cmd/validate@latest ../../docs/openapi.json
// setupOpenAPIStaticConfig initializes static content (version) for the OpenAPI config.
//
// @title Woodpecker CI API
// @description Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.
// @description To get a personal access token (PAT) for authentication, please log in your Woodpecker server,
// @description and go to you personal profile page, by clicking the user icon at the top right.
// @BasePath /api
// @contact.name Woodpecker CI
// @contact.url https://woodpecker-ci.org/
func setupOpenAPIStaticConfig() {
openapi.SwaggerInfo.Version = version.String()
}

View File

@ -1,5 +1,5 @@
// Package docs Code generated by swaggo/swag. DO NOT EDIT
package docs
// Package openapi Code generated by swaggo/swag. DO NOT EDIT
package openapi
import "github.com/swaggo/swag"
@ -10,7 +10,7 @@ const docTemplate = `{
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {
"name": "Woodpecker CI Community",
"name": "Woodpecker CI",
"url": "https://woodpecker-ci.org/"
},
"version": "{{.Version}}"
@ -122,7 +122,7 @@ const docTemplate = `{
{
"type": "integer",
"description": "the agent's id",
"name": "agent",
"name": "agent_id",
"in": "path",
"required": true
}
@ -156,7 +156,7 @@ const docTemplate = `{
{
"type": "integer",
"description": "the agent's id",
"name": "agent",
"name": "agent_id",
"in": "path",
"required": true
}
@ -187,7 +187,7 @@ const docTemplate = `{
{
"type": "integer",
"description": "the agent's id",
"name": "agent",
"name": "agent_id",
"in": "path",
"required": true
},
@ -232,7 +232,7 @@ const docTemplate = `{
{
"type": "integer",
"description": "the agent's id",
"name": "agent",
"name": "agent_id",
"in": "path",
"required": true
}
@ -2966,6 +2966,30 @@ const docTemplate = `{
"description": "only return pipelines after this RFC3339 date",
"name": "after",
"in": "query"
},
{
"type": "string",
"description": "filter pipelines by branch",
"name": "branch",
"in": "query"
},
{
"type": "string",
"description": "filter pipelines by webhook events (comma separated)",
"name": "event",
"in": "query"
},
{
"type": "string",
"description": "filter pipelines by strings contained in ref",
"name": "ref",
"in": "query"
},
{
"type": "string",
"description": "filter pipelines by status",
"name": "status",
"in": "query"
}
],
"responses": {
@ -4554,22 +4578,15 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"type": "object",
"properties": {
"source": {
"type": "string"
},
{
"type": "object",
"properties": {
"source": {
"type": "string"
},
"version": {
"type": "string"
}
}
"version": {
"type": "string"
}
]
}
}
}
}
@ -4905,6 +4922,9 @@ const docTemplate = `{
"forge_url": {
"type": "string"
},
"from_fork": {
"type": "boolean"
},
"id": {
"type": "integer"
},
@ -5068,17 +5088,17 @@ const docTemplate = `{
"full_name": {
"type": "string"
},
"gated": {
"type": "boolean"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"netrc_only_trusted": {
"type": "boolean"
"netrc_trusted": {
"type": "array",
"items": {
"type": "string"
}
},
"org_id": {
"type": "integer"
@ -5092,6 +5112,9 @@ const docTemplate = `{
"private": {
"type": "boolean"
},
"require_approval": {
"$ref": "#/definitions/model.ApprovalMode"
},
"scm": {
"$ref": "#/definitions/SCMKind"
},
@ -5125,10 +5148,17 @@ const docTemplate = `{
"type": "string"
},
"gated": {
"description": "TODO: deprecated in favor of RequireApproval =\u003e Remove in next major release",
"type": "boolean"
},
"netrc_only_trusted": {
"type": "boolean"
"netrc_trusted": {
"type": "array",
"items": {
"type": "string"
}
},
"require_approval": {
"type": "string"
},
"timeout": {
"type": "integer"
@ -5621,6 +5651,27 @@ const docTemplate = `{
}
}
},
"model.ApprovalMode": {
"type": "string",
"enum": [
"none",
"forks",
"pull_requests",
"all_events"
],
"x-enum-comments": {
"RequireApprovalAllEvents": "require approval for all external events",
"RequireApprovalForks": "require approval for PRs from forks (default)",
"RequireApprovalNone": "require approval for no events",
"RequireApprovalPullRequests": "require approval for all PRs"
},
"x-enum-varnames": [
"RequireApprovalNone",
"RequireApprovalForks",
"RequireApprovalPullRequests",
"RequireApprovalAllEvents"
]
},
"model.ForgeType": {
"type": "string",
"enum": [
@ -5763,10 +5814,10 @@ const docTemplate = `{
var SwaggerInfo = &swag.Spec{
Version: "",
Host: "",
BasePath: "",
BasePath: "/api",
Schemes: []string{},
Title: "",
Description: "",
Title: "Woodpecker CI API",
Description: "Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.\nTo get a personal access token (PAT) for authentication, please log in your Woodpecker server,\nand go to you personal profile page, by clicking the user icon at the top right.",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",

View File

@ -12,9 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// ************************************************************************************************
// This is a generator tool, to update the Markdown documentation for the woodpecker-ci.org website
// ************************************************************************************************
// *********************************************************
// This is a generator tool, to update the openapi.json file
// *********************************************************
//go:build generate
// +build generate
@ -30,23 +30,24 @@ import (
"github.com/getkin/kin-openapi/openapi2"
"github.com/getkin/kin-openapi/openapi2conv"
"go.woodpecker-ci.org/woodpecker/v2/cmd/server/docs"
"go.woodpecker-ci.org/woodpecker/v2/cmd/server/openapi"
)
func main() {
// set swagger infos
setupSwaggerStaticConfig()
// set openapi infos
setupOpenAPIStaticConfig()
basePath := path.Join("..", "..")
filePath := path.Join(basePath, "docs", "swagger.json")
filePath := path.Join(basePath, "docs", "openapi.json")
// generate swagger file
// generate openapi file
f, err := os.Create(filePath)
if err != nil {
panic(err)
}
defer f.Close()
doc := docs.SwaggerInfo.ReadDoc()
doc := openapi.SwaggerInfo.ReadDoc()
doc, err = removeHost(doc)
if err != nil {
panic(err)
@ -56,6 +57,8 @@ func main() {
panic(err)
}
fmt.Println("generated openapi.json")
// convert to OpenApi3
if err := toOpenApi3(filePath, filePath); err != nil {
fmt.Printf("converting '%s' from openapi v2 to v3 failed\n", filePath)

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