mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 11:21:02 +00:00
Merge branch 'main' into github-refresh
This commit is contained in:
commit
6a4f2f17ad
146 changed files with 8322 additions and 5333 deletions
|
@ -15,6 +15,7 @@
|
|||
"apimachinery",
|
||||
"Archlinux",
|
||||
"autoincr",
|
||||
"automerge",
|
||||
"autoscaler",
|
||||
"backporting",
|
||||
"backports",
|
||||
|
|
1
.github/renovate.json
vendored
1
.github/renovate.json
vendored
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>woodpecker-ci/renovate-config"],
|
||||
"automergeType": "pr",
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
|
|
1
.lycheeignore
Normal file
1
.lycheeignore
Normal file
|
@ -0,0 +1 @@
|
|||
https://stackoverflow.com/*
|
|
@ -5,16 +5,16 @@ repos:
|
|||
- id: check-hooks-apply
|
||||
- id: check-useless-excludes
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.6.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.59.1
|
||||
rev: v1.61.0
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.41.0
|
||||
rev: v0.42.0
|
||||
hooks:
|
||||
- id: markdownlint
|
||||
exclude: '^(docs/versioned_docs/.*|CHANGELOG.md)$'
|
||||
|
@ -24,7 +24,7 @@ repos:
|
|||
- id: checkmake
|
||||
exclude: '^docker/Dockerfile.make$' # actually a Dockerfile and not a makefile
|
||||
- repo: https://github.com/hadolint/hadolint
|
||||
rev: v2.13.0-beta
|
||||
rev: v2.13.1-beta
|
||||
hooks:
|
||||
- id: hadolint
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
|
|
|
@ -6,7 +6,7 @@ when:
|
|||
|
||||
variables:
|
||||
- &golang_image 'docker.io/golang:1.23'
|
||||
- &node_image 'docker.io/node:22-alpine'
|
||||
- &node_image 'docker.io/node:23-alpine'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x'
|
||||
|
||||
# cspell:words bindata netgo
|
||||
|
@ -97,7 +97,7 @@ steps:
|
|||
release:
|
||||
depends_on:
|
||||
- checksums
|
||||
image: woodpeckerci/plugin-release:0.1.0
|
||||
image: woodpeckerci/plugin-release:0.2.1
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: github_token
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
variables:
|
||||
- &golang_image 'docker.io/golang:1.23'
|
||||
- &node_image 'docker.io/node:22-alpine'
|
||||
- &node_image 'docker.io/node:23-alpine'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x'
|
||||
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:4.2.0'
|
||||
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:5.0.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'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
variables:
|
||||
- &golang_image 'docker.io/golang:1.22'
|
||||
- &node_image 'docker.io/node:21-alpine'
|
||||
- &alpine_image 'docker.io/alpine:3.19'
|
||||
- &golang_image 'docker.io/golang:1.23'
|
||||
- &node_image 'docker.io/node:23-alpine'
|
||||
- &alpine_image 'docker.io/alpine:3.20'
|
||||
- path: &when_path
|
||||
- 'docs/**'
|
||||
- '.woodpecker/docs.yaml'
|
||||
|
@ -60,7 +60,7 @@ steps:
|
|||
- event: manual
|
||||
|
||||
deploy-preview:
|
||||
image: docker.io/woodpeckerci/plugin-surge-preview:1.3.0
|
||||
image: docker.io/woodpeckerci/plugin-surge-preview:1.3.2
|
||||
settings:
|
||||
path: 'docs/build/'
|
||||
surge_token:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
steps:
|
||||
release-helper:
|
||||
image: woodpeckerci/plugin-ready-release-go:1.2.0
|
||||
image: woodpeckerci/plugin-ready-release-go:2.0.0
|
||||
pull: true
|
||||
settings:
|
||||
release_branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
|
|
@ -6,7 +6,7 @@ when:
|
|||
- renovate/*
|
||||
|
||||
variables:
|
||||
- &trivy_plugin docker.io/woodpeckerci/plugin-trivy:1.1.0
|
||||
- &trivy_plugin docker.io/woodpeckerci/plugin-trivy:1.1.1
|
||||
|
||||
steps:
|
||||
backend:
|
||||
|
|
|
@ -13,7 +13,7 @@ steps:
|
|||
branch: renovate/*
|
||||
|
||||
- name: spellcheck
|
||||
image: docker.io/node:22-alpine
|
||||
image: docker.io/node:23-alpine
|
||||
depends_on: []
|
||||
commands:
|
||||
- corepack enable
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
variables:
|
||||
- &golang_image 'docker.io/golang:1.22'
|
||||
- &golang_image 'docker.io/golang:1.23'
|
||||
- &when
|
||||
- path: &when_path # related config files
|
||||
- '.woodpecker/test.yaml'
|
||||
|
@ -40,7 +40,7 @@ steps:
|
|||
- go run go.woodpecker-ci.org/woodpecker/v2/cmd/cli lint
|
||||
environment:
|
||||
WOODPECKER_DISABLE_UPDATE_CHECK: true
|
||||
WOODPECKER_PLUGINS_PRIVILEGED: 'docker.io/woodpeckerci/plugin-docker-buildx:4.2.0'
|
||||
WOODPECKER_PLUGINS_PRIVILEGED: 'docker.io/woodpeckerci/plugin-docker-buildx:5.0.0'
|
||||
when:
|
||||
- event: pull_request
|
||||
path:
|
||||
|
@ -129,7 +129,7 @@ steps:
|
|||
- test
|
||||
- sqlite
|
||||
pull: true
|
||||
image: docker.io/woodpeckerci/plugin-codecov:2.1.2
|
||||
image: docker.io/woodpeckerci/plugin-codecov:2.1.5
|
||||
settings:
|
||||
files:
|
||||
- agent-coverage.out
|
||||
|
@ -145,7 +145,7 @@ steps:
|
|||
|
||||
services:
|
||||
postgres:
|
||||
image: docker.io/postgres:16
|
||||
image: docker.io/postgres:17
|
||||
ports: ['5432']
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
|
@ -153,7 +153,7 @@ services:
|
|||
when: *when
|
||||
|
||||
mysql:
|
||||
image: docker.io/mysql:8.2.0
|
||||
image: docker.io/mysql:9.1.0
|
||||
ports: ['3306']
|
||||
environment:
|
||||
MYSQL_DATABASE: test
|
||||
|
|
|
@ -6,7 +6,7 @@ when:
|
|||
- renovate/*
|
||||
|
||||
variables:
|
||||
- &node_image 'docker.io/node:22-alpine'
|
||||
- &node_image 'docker.io/node:23-alpine'
|
||||
- &when
|
||||
path:
|
||||
# related config files
|
||||
|
|
|
@ -480,12 +480,15 @@ func (c *client) sendLogs(ctx context.Context, entries []*proto.LogEntry) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *client) RegisterAgent(ctx context.Context, platform, backend, version string, capacity int) (int64, error) {
|
||||
func (c *client) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) {
|
||||
req := new(proto.RegisterAgentRequest)
|
||||
req.Platform = platform
|
||||
req.Backend = backend
|
||||
req.Version = version
|
||||
req.Capacity = int32(capacity)
|
||||
req.Info = &proto.AgentInfo{
|
||||
Platform: info.Platform,
|
||||
Backend: info.Backend,
|
||||
Version: info.Version,
|
||||
Capacity: int32(info.Capacity),
|
||||
CustomLabels: info.CustomLabels,
|
||||
}
|
||||
|
||||
res, err := c.client.RegisterAgent(ctx, req)
|
||||
return res.GetAgentId(), err
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/drone/envsubst"
|
||||
"github.com/oklog/ulid/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
|
@ -36,6 +37,7 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local"
|
||||
backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter"
|
||||
|
@ -75,6 +77,7 @@ func execDir(ctx context.Context, c *cli.Command, dir string) error {
|
|||
if runtime.GOOS == "windows" {
|
||||
repoPath = convertPathForWindows(repoPath)
|
||||
}
|
||||
// TODO: respect depends_on and do parallel runs with output to multiple _windows_ e.g. tmux like
|
||||
return filepath.Walk(dir, func(path string, info os.FileInfo, e error) error {
|
||||
if e != nil {
|
||||
return e
|
||||
|
@ -83,7 +86,7 @@ func execDir(ctx context.Context, c *cli.Command, dir string) error {
|
|||
// check if it is a regular file (not dir)
|
||||
if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) {
|
||||
fmt.Println("#", info.Name())
|
||||
_ = runExec(ctx, c, path, repoPath) // TODO: should we drop errors or store them and report back?
|
||||
_ = runExec(ctx, c, path, repoPath, false) // TODO: should we drop errors or store them and report back?
|
||||
fmt.Println("")
|
||||
return nil
|
||||
}
|
||||
|
@ -102,10 +105,10 @@ func execFile(ctx context.Context, c *cli.Command, file string) error {
|
|||
if runtime.GOOS == "windows" {
|
||||
repoPath = convertPathForWindows(repoPath)
|
||||
}
|
||||
return runExec(ctx, c, file, repoPath)
|
||||
return runExec(ctx, c, file, repoPath, true)
|
||||
}
|
||||
|
||||
func runExec(ctx context.Context, c *cli.Command, file, repoPath string) error {
|
||||
func runExec(ctx context.Context, c *cli.Command, file, repoPath string, singleExec bool) error {
|
||||
dat, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -120,7 +123,7 @@ func runExec(ctx context.Context, c *cli.Command, file, repoPath string) error {
|
|||
axes = append(axes, matrix.Axis{})
|
||||
}
|
||||
for _, axis := range axes {
|
||||
err := execWithAxis(ctx, c, file, repoPath, axis)
|
||||
err := execWithAxis(ctx, c, file, repoPath, axis, singleExec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -128,11 +131,20 @@ func runExec(ctx context.Context, c *cli.Command, file, repoPath string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, axis matrix.Axis) error {
|
||||
metadata, err := metadataFromContext(ctx, c, axis)
|
||||
func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, axis matrix.Axis, singleExec bool) error {
|
||||
metadataWorkflow := &metadata.Workflow{}
|
||||
if !singleExec {
|
||||
// TODO: proper try to use the engine to generate the same metadata for workflows
|
||||
// https://github.com/woodpecker-ci/woodpecker/pull/3967
|
||||
metadataWorkflow.Name = strings.TrimSuffix(strings.TrimSuffix(file, ".yaml"), ".yml")
|
||||
}
|
||||
metadata, err := metadataFromContext(ctx, c, axis, metadataWorkflow)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create metadata: %w", err)
|
||||
} else if metadata == nil {
|
||||
return fmt.Errorf("metadata is nil")
|
||||
}
|
||||
|
||||
environ := metadata.Environ()
|
||||
var secrets []compiler.Secret
|
||||
for key, val := range metadata.Workflow.Matrix {
|
||||
|
@ -170,6 +182,9 @@ func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, ax
|
|||
return err
|
||||
}
|
||||
|
||||
// emulate server behavior https://github.com/woodpecker-ci/woodpecker/blob/eebaa10d104cbc3fa7ce4c0e344b0b7978405135/server/pipeline/stepbuilder/stepBuilder.go#L289-L295
|
||||
prefix := "wp_" + ulid.Make().String()
|
||||
|
||||
// configure volumes for local execution
|
||||
volumes := c.StringSlice("volumes")
|
||||
if c.Bool("local") {
|
||||
|
@ -184,7 +199,7 @@ func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, ax
|
|||
workspacePath = c.String("workspace-path")
|
||||
}
|
||||
|
||||
volumes = append(volumes, c.String("prefix")+"_default:"+workspaceBase)
|
||||
volumes = append(volumes, prefix+"_default:"+workspaceBase)
|
||||
volumes = append(volumes, repoPath+":"+path.Join(workspaceBase, workspacePath))
|
||||
}
|
||||
|
||||
|
@ -221,9 +236,7 @@ func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, ax
|
|||
compiler.WithNetworks(
|
||||
c.StringSlice("network")...,
|
||||
),
|
||||
compiler.WithPrefix(
|
||||
c.String("prefix"),
|
||||
),
|
||||
compiler.WithPrefix(prefix),
|
||||
compiler.WithProxy(compiler.ProxyOptions{
|
||||
NoProxy: c.String("backend-no-proxy"),
|
||||
HTTPProxy: c.String("backend-http-proxy"),
|
||||
|
@ -237,7 +250,7 @@ func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, ax
|
|||
c.String("netrc-password"),
|
||||
c.String("netrc-machine"),
|
||||
),
|
||||
compiler.WithMetadata(metadata),
|
||||
compiler.WithMetadata(*metadata),
|
||||
compiler.WithSecret(secrets...),
|
||||
compiler.WithEnviron(pipelineEnv),
|
||||
).Compile(conf)
|
||||
|
|
|
@ -32,6 +32,11 @@ var flags = []cli.Flag{
|
|||
Name: "repo-path",
|
||||
Usage: "path to local repository",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_METADATA_FILE"),
|
||||
Name: "metadata-file",
|
||||
Usage: "path to pipeline metadata file (normally downloaded from UI). Parameters can be adjusted by applying additional cli flags",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_TIMEOUT"),
|
||||
Name: "timeout",
|
||||
|
@ -48,13 +53,6 @@ var flags = []cli.Flag{
|
|||
Name: "network",
|
||||
Usage: "external networks",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_PREFIX"),
|
||||
Name: "prefix",
|
||||
Value: "woodpecker",
|
||||
Usage: "prefix used for containers, volumes, networks, ... created by woodpecker",
|
||||
Hidden: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_PLUGINS_PRIVILEGED"),
|
||||
Name: "plugins-privileged",
|
||||
|
@ -235,7 +233,7 @@ var flags = []cli.Flag{
|
|||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("CI_PIPELINE_FILES"),
|
||||
Usage: "Set the metadata environment variable \"CI_PIPELINE_FILES\", either json formatted list of strings, or comma separated string list.",
|
||||
Name: "pipeline-files",
|
||||
Name: "pipeline-changed-files",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("CI_COMMIT_SHA"),
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
|
@ -29,108 +30,131 @@ import (
|
|||
)
|
||||
|
||||
// return the metadata from the cli context.
|
||||
func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis) (metadata.Metadata, error) {
|
||||
func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis, w *metadata.Workflow) (*metadata.Metadata, error) {
|
||||
m := &metadata.Metadata{}
|
||||
|
||||
if c.IsSet("metadata-file") {
|
||||
metadataFile, err := os.Open(c.String("metadata-file"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer metadataFile.Close()
|
||||
|
||||
if err := json.NewDecoder(metadataFile).Decode(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
platform := c.String("system-platform")
|
||||
if platform == "" {
|
||||
platform = runtime.GOOS + "/" + runtime.GOARCH
|
||||
}
|
||||
|
||||
fullRepoName := c.String("repo-name")
|
||||
repoOwner := ""
|
||||
repoName := ""
|
||||
if idx := strings.LastIndex(fullRepoName, "/"); idx != -1 {
|
||||
repoOwner = fullRepoName[:idx]
|
||||
repoName = fullRepoName[idx+1:]
|
||||
metadataFileAndOverrideOrDefault(c, "repo-name", func(fullRepoName string) {
|
||||
if idx := strings.LastIndex(fullRepoName, "/"); idx != -1 {
|
||||
m.Repo.Owner = fullRepoName[:idx]
|
||||
m.Repo.Name = fullRepoName[idx+1:]
|
||||
}
|
||||
}, c.String)
|
||||
|
||||
var err error
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-changed-files", func(changedFilesRaw string) {
|
||||
var changedFiles []string
|
||||
if len(changedFilesRaw) != 0 && changedFilesRaw[0] == '[' {
|
||||
if jsonErr := json.Unmarshal([]byte(changedFilesRaw), &changedFiles); jsonErr != nil {
|
||||
err = fmt.Errorf("pipeline-changed-files detected json but could not parse it: %w", jsonErr)
|
||||
}
|
||||
} else {
|
||||
for _, file := range strings.Split(changedFilesRaw, ",") {
|
||||
changedFiles = append(changedFiles, strings.TrimSpace(file))
|
||||
}
|
||||
}
|
||||
m.Curr.Commit.ChangedFiles = changedFiles
|
||||
}, c.String)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var changedFiles []string
|
||||
changedFilesRaw := c.String("pipeline-files")
|
||||
if len(changedFilesRaw) != 0 && changedFilesRaw[0] == '[' {
|
||||
if err := json.Unmarshal([]byte(changedFilesRaw), &changedFiles); err != nil {
|
||||
return metadata.Metadata{}, fmt.Errorf("pipeline-files detected json but could not parse it: %w", err)
|
||||
}
|
||||
} else {
|
||||
for _, file := range strings.Split(changedFilesRaw, ",") {
|
||||
changedFiles = append(changedFiles, strings.TrimSpace(file))
|
||||
}
|
||||
// Repo
|
||||
metadataFileAndOverrideOrDefault(c, "repo-remote-id", func(s string) { m.Repo.RemoteID = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-url", func(s string) { m.Repo.ForgeURL = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-scm", func(s string) { m.Repo.SCM = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-default-branch", func(s string) { m.Repo.Branch = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-clone-url", func(s string) { m.Repo.CloneURL = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-clone-ssh-url", func(s string) { m.Repo.CloneSSHURL = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-private", func(b bool) { m.Repo.Private = b }, c.Bool)
|
||||
metadataFileAndOverrideOrDefault(c, "repo-trusted", func(b bool) { m.Repo.Trusted = b }, c.Bool)
|
||||
|
||||
// Current Pipeline
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-number", func(i int64) { m.Curr.Number = i }, c.Int)
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-parent", func(i int64) { m.Curr.Parent = i }, c.Int)
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-created", func(i int64) { m.Curr.Created = i }, c.Int)
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-started", func(i int64) { m.Curr.Started = i }, c.Int)
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-finished", func(i int64) { m.Curr.Finished = i }, c.Int)
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-status", func(s string) { m.Curr.Status = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-event", func(s string) { m.Curr.Event = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-url", func(s string) { m.Curr.ForgeURL = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-deploy-to", func(s string) { m.Curr.DeployTo = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "pipeline-deploy-task", func(s string) { m.Curr.DeployTask = s }, c.String)
|
||||
|
||||
// Current Pipeline Commit
|
||||
metadataFileAndOverrideOrDefault(c, "commit-sha", func(s string) { m.Curr.Commit.Sha = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "commit-ref", func(s string) { m.Curr.Commit.Ref = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "commit-refspec", func(s string) { m.Curr.Commit.Refspec = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "commit-branch", func(s string) { m.Curr.Commit.Branch = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "commit-message", func(s string) { m.Curr.Commit.Message = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "commit-author-name", func(s string) { m.Curr.Commit.Author.Name = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "commit-author-email", func(s string) { m.Curr.Commit.Author.Email = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "commit-author-avatar", func(s string) { m.Curr.Commit.Author.Avatar = s }, c.String)
|
||||
|
||||
metadataFileAndOverrideOrDefault(c, "commit-pull-labels", func(sl []string) { m.Curr.Commit.PullRequestLabels = sl }, c.StringSlice)
|
||||
metadataFileAndOverrideOrDefault(c, "commit-release-is-pre", func(b bool) { m.Curr.Commit.IsPrerelease = b }, c.Bool)
|
||||
|
||||
// Previous Pipeline
|
||||
metadataFileAndOverrideOrDefault(c, "prev-pipeline-number", func(i int64) { m.Prev.Number = i }, c.Int)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-pipeline-created", func(i int64) { m.Prev.Created = i }, c.Int)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-pipeline-started", func(i int64) { m.Prev.Started = i }, c.Int)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-pipeline-finished", func(i int64) { m.Prev.Finished = i }, c.Int)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-pipeline-status", func(s string) { m.Prev.Status = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-pipeline-event", func(s string) { m.Prev.Event = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-pipeline-url", func(s string) { m.Prev.ForgeURL = s }, c.String)
|
||||
|
||||
// Previous Pipeline Commit
|
||||
metadataFileAndOverrideOrDefault(c, "prev-commit-sha", func(s string) { m.Prev.Commit.Sha = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-commit-ref", func(s string) { m.Prev.Commit.Ref = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-commit-refspec", func(s string) { m.Prev.Commit.Refspec = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-commit-branch", func(s string) { m.Prev.Commit.Branch = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-commit-message", func(s string) { m.Prev.Commit.Message = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-commit-author-name", func(s string) { m.Prev.Commit.Author.Name = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-commit-author-email", func(s string) { m.Prev.Commit.Author.Email = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "prev-commit-author-avatar", func(s string) { m.Prev.Commit.Author.Avatar = s }, c.String)
|
||||
|
||||
// Workflow
|
||||
metadataFileAndOverrideOrDefault(c, "workflow-name", func(s string) { m.Workflow.Name = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "workflow-number", func(i int64) { m.Workflow.Number = int(i) }, c.Int)
|
||||
m.Workflow.Matrix = axis
|
||||
|
||||
// System
|
||||
metadataFileAndOverrideOrDefault(c, "system-name", func(s string) { m.Sys.Name = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "system-url", func(s string) { m.Sys.URL = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "system-host", func(s string) { m.Sys.Host = s }, c.String)
|
||||
m.Sys.Platform = platform
|
||||
m.Sys.Version = version.Version
|
||||
|
||||
// Forge
|
||||
metadataFileAndOverrideOrDefault(c, "forge-type", func(s string) { m.Forge.Type = s }, c.String)
|
||||
metadataFileAndOverrideOrDefault(c, "forge-url", func(s string) { m.Forge.URL = s }, c.String)
|
||||
|
||||
if w != nil {
|
||||
m.Workflow = *w
|
||||
}
|
||||
|
||||
return metadata.Metadata{
|
||||
Repo: metadata.Repo{
|
||||
Name: repoName,
|
||||
Owner: repoOwner,
|
||||
RemoteID: c.String("repo-remote-id"),
|
||||
ForgeURL: c.String("repo-url"),
|
||||
SCM: c.String("repo-scm"),
|
||||
Branch: c.String("repo-default-branch"),
|
||||
CloneURL: c.String("repo-clone-url"),
|
||||
CloneSSHURL: c.String("repo-clone-ssh-url"),
|
||||
Private: c.Bool("repo-private"),
|
||||
Trusted: c.Bool("repo-trusted"),
|
||||
},
|
||||
Curr: metadata.Pipeline{
|
||||
Number: c.Int("pipeline-number"),
|
||||
Parent: c.Int("pipeline-parent"),
|
||||
Created: c.Int("pipeline-created"),
|
||||
Started: c.Int("pipeline-started"),
|
||||
Finished: c.Int("pipeline-finished"),
|
||||
Status: c.String("pipeline-status"),
|
||||
Event: c.String("pipeline-event"),
|
||||
ForgeURL: c.String("pipeline-url"),
|
||||
DeployTo: c.String("pipeline-deploy-to"),
|
||||
DeployTask: c.String("pipeline-deploy-task"),
|
||||
Commit: metadata.Commit{
|
||||
Sha: c.String("commit-sha"),
|
||||
Ref: c.String("commit-ref"),
|
||||
Refspec: c.String("commit-refspec"),
|
||||
Branch: c.String("commit-branch"),
|
||||
Message: c.String("commit-message"),
|
||||
Author: metadata.Author{
|
||||
Name: c.String("commit-author-name"),
|
||||
Email: c.String("commit-author-email"),
|
||||
Avatar: c.String("commit-author-avatar"),
|
||||
},
|
||||
PullRequestLabels: c.StringSlice("commit-pull-labels"),
|
||||
IsPrerelease: c.Bool("commit-release-is-pre"),
|
||||
ChangedFiles: changedFiles,
|
||||
},
|
||||
},
|
||||
Prev: metadata.Pipeline{
|
||||
Number: c.Int("prev-pipeline-number"),
|
||||
Created: c.Int("prev-pipeline-created"),
|
||||
Started: c.Int("prev-pipeline-started"),
|
||||
Finished: c.Int("prev-pipeline-finished"),
|
||||
Status: c.String("prev-pipeline-status"),
|
||||
Event: c.String("prev-pipeline-event"),
|
||||
ForgeURL: c.String("prev-pipeline-url"),
|
||||
Commit: metadata.Commit{
|
||||
Sha: c.String("prev-commit-sha"),
|
||||
Ref: c.String("prev-commit-ref"),
|
||||
Refspec: c.String("prev-commit-refspec"),
|
||||
Branch: c.String("prev-commit-branch"),
|
||||
Message: c.String("prev-commit-message"),
|
||||
Author: metadata.Author{
|
||||
Name: c.String("prev-commit-author-name"),
|
||||
Email: c.String("prev-commit-author-email"),
|
||||
Avatar: c.String("prev-commit-author-avatar"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Workflow: metadata.Workflow{
|
||||
Name: c.String("workflow-name"),
|
||||
Number: int(c.Int("workflow-number")),
|
||||
Matrix: axis,
|
||||
},
|
||||
Sys: metadata.System{
|
||||
Name: c.String("system-name"),
|
||||
URL: c.String("system-url"),
|
||||
Host: c.String("system-host"),
|
||||
Platform: platform,
|
||||
Version: version.Version,
|
||||
},
|
||||
Forge: metadata.Forge{
|
||||
Type: c.String("forge-type"),
|
||||
URL: c.String("forge-url"),
|
||||
},
|
||||
}, nil
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// metadataFileAndOverrideOrDefault will either use the flag default or if metadata file is set only overload if explicit set.
|
||||
func metadataFileAndOverrideOrDefault[T any](c *cli.Command, flag string, setter func(T), getter func(string) T) {
|
||||
if !c.IsSet("metadata-file") || c.IsSet(flag) {
|
||||
setter(getter(flag))
|
||||
}
|
||||
}
|
||||
|
|
142
cli/exec/metadata_test.go
Normal file
142
cli/exec/metadata_test.go
Normal file
|
@ -0,0 +1,142 @@
|
|||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v3"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix"
|
||||
)
|
||||
|
||||
func TestMetadataFromContext(t *testing.T) {
|
||||
sampleMetadata := &metadata.Metadata{
|
||||
Repo: metadata.Repo{Owner: "test-user", Name: "test-repo"},
|
||||
Curr: metadata.Pipeline{Number: 5},
|
||||
}
|
||||
|
||||
runCommand := func(flags []cli.Flag, fn func(c *cli.Command)) {
|
||||
c := &cli.Command{
|
||||
Flags: flags,
|
||||
Action: func(_ context.Context, c *cli.Command) error {
|
||||
fn(c)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
assert.NoError(t, c.Run(context.Background(), []string{"woodpecker-cli"}))
|
||||
}
|
||||
|
||||
t.Run("LoadFromFile", func(t *testing.T) {
|
||||
tempFileName := createTempFile(t, sampleMetadata)
|
||||
|
||||
flags := []cli.Flag{
|
||||
&cli.StringFlag{Name: "metadata-file"},
|
||||
}
|
||||
|
||||
runCommand(flags, func(c *cli.Command) {
|
||||
_ = c.Set("metadata-file", tempFileName)
|
||||
|
||||
m, err := metadataFromContext(context.Background(), c, nil, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "test-repo", m.Repo.Name)
|
||||
assert.Equal(t, int64(5), m.Curr.Number)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("OverrideFromFlags", func(t *testing.T) {
|
||||
tempFileName := createTempFile(t, sampleMetadata)
|
||||
|
||||
flags := []cli.Flag{
|
||||
&cli.StringFlag{Name: "metadata-file"},
|
||||
&cli.StringFlag{Name: "repo-name"},
|
||||
&cli.IntFlag{Name: "pipeline-number"},
|
||||
}
|
||||
|
||||
runCommand(flags, func(c *cli.Command) {
|
||||
_ = c.Set("metadata-file", tempFileName)
|
||||
_ = c.Set("repo-name", "aUser/override-repo")
|
||||
_ = c.Set("pipeline-number", "10")
|
||||
|
||||
m, err := metadataFromContext(context.Background(), c, nil, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "override-repo", m.Repo.Name)
|
||||
assert.Equal(t, int64(10), m.Curr.Number)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("InvalidFile", func(t *testing.T) {
|
||||
tempFile, err := os.CreateTemp("", "invalid.json")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.Remove(tempFile.Name()) })
|
||||
|
||||
_, err = tempFile.Write([]byte("invalid json"))
|
||||
require.NoError(t, err)
|
||||
|
||||
flags := []cli.Flag{
|
||||
&cli.StringFlag{Name: "metadata-file"},
|
||||
}
|
||||
|
||||
runCommand(flags, func(c *cli.Command) {
|
||||
_ = c.Set("metadata-file", tempFile.Name())
|
||||
|
||||
_, err = metadataFromContext(context.Background(), c, nil, nil)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("DefaultValues", func(t *testing.T) {
|
||||
flags := []cli.Flag{
|
||||
&cli.StringFlag{Name: "repo-name", Value: "test/default-repo"},
|
||||
&cli.IntFlag{Name: "pipeline-number", Value: 1},
|
||||
}
|
||||
|
||||
runCommand(flags, func(c *cli.Command) {
|
||||
m, err := metadataFromContext(context.Background(), c, nil, nil)
|
||||
require.NoError(t, err)
|
||||
if assert.NotNil(t, m) {
|
||||
assert.Equal(t, "test", m.Repo.Owner)
|
||||
assert.Equal(t, "default-repo", m.Repo.Name)
|
||||
assert.Equal(t, int64(1), m.Curr.Number)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("MatrixAxis", func(t *testing.T) {
|
||||
runCommand([]cli.Flag{}, func(c *cli.Command) {
|
||||
axis := matrix.Axis{"go": "1.16", "os": "linux"}
|
||||
m, err := metadataFromContext(context.Background(), c, axis, nil)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, map[string]string{"go": "1.16", "os": "linux"}, m.Workflow.Matrix)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func createTempFile(t *testing.T, content any) string {
|
||||
t.Helper()
|
||||
tempFile, err := os.CreateTemp("", "metadata.json")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.Remove(tempFile.Name()) })
|
||||
|
||||
err = json.NewEncoder(tempFile).Encode(content)
|
||||
require.NoError(t, err)
|
||||
return tempFile.Name()
|
||||
}
|
|
@ -20,6 +20,7 @@ import (
|
|||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -198,7 +199,22 @@ func run(ctx context.Context, c *cli.Command, backends []types.Backend) error {
|
|||
log.Debug().Msgf("loaded %s backend engine", backendEngine.Name())
|
||||
|
||||
maxWorkflows := int(c.Int("max-workflows"))
|
||||
agentConfig.AgentID, err = client.RegisterAgent(grpcCtx, engInfo.Platform, backendEngine.Name(), version.String(), maxWorkflows) //nolint:contextcheck
|
||||
|
||||
customLabels := make(map[string]string)
|
||||
if err := stringSliceAddToMap(c.StringSlice("labels"), customLabels); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(customLabels) != 0 {
|
||||
log.Debug().Msgf("custom labels detected: %#v", customLabels)
|
||||
}
|
||||
|
||||
agentConfig.AgentID, err = client.RegisterAgent(grpcCtx, rpc.AgentInfo{ //nolint:contextcheck
|
||||
Version: version.String(),
|
||||
Backend: backendEngine.Name(),
|
||||
Platform: engInfo.Platform,
|
||||
Capacity: maxWorkflows,
|
||||
CustomLabels: customLabels,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -210,7 +226,7 @@ func run(ctx context.Context, c *cli.Command, backends []types.Backend) error {
|
|||
<-agentCtx.Done()
|
||||
// Remove stateless agents from server
|
||||
if !agentConfigPersisted.Load() {
|
||||
log.Debug().Msg("unregistering agent from server ...")
|
||||
log.Debug().Msg("unregister agent from server ...")
|
||||
// we want to run it explicit run when context got canceled so run it in background
|
||||
err := client.UnregisterAgent(grpcClientCtx)
|
||||
if err != nil {
|
||||
|
@ -228,16 +244,17 @@ func run(ctx context.Context, c *cli.Command, backends []types.Backend) error {
|
|||
}
|
||||
}
|
||||
|
||||
// set default labels ...
|
||||
labels := map[string]string{
|
||||
"hostname": hostname,
|
||||
"platform": engInfo.Platform,
|
||||
"backend": backendEngine.Name(),
|
||||
"repo": "*", // allow all repos by default
|
||||
}
|
||||
// ... and let it overwrite by custom ones
|
||||
maps.Copy(labels, customLabels)
|
||||
|
||||
if err := stringSliceAddToMap(c.StringSlice("filter"), labels); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug().Any("labels", labels).Msgf("agent configured with labels")
|
||||
|
||||
filter := rpc.Filter{
|
||||
Labels: labels,
|
||||
|
|
|
@ -60,8 +60,9 @@ var flags = []cli.Flag{
|
|||
Value: "/etc/woodpecker/agent.conf",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_FILTER_LABELS"),
|
||||
Name: "filter",
|
||||
Sources: cli.EnvVars("WOODPECKER_AGENT_LABELS", "WOODPECKER_FILTER_LABELS"), // remove WOODPECKER_FILTER_LABELS in v4.x
|
||||
Name: "labels",
|
||||
Aliases: []string{"filter"}, // remove in v4.x
|
||||
Usage: "List of labels to filter tasks on. An agent must be assigned every tag listed in a task to be selected.",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
|
|
|
@ -101,7 +101,7 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/agents/{agent}": {
|
||||
"/agents/{agent_id}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
|
@ -211,7 +211,7 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/agents/{agent}/tasks": {
|
||||
"/agents/{agent_id}/tasks": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
|
@ -1063,6 +1063,193 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org_id}/agents": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "List agents for an organization",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the organization's id",
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 1,
|
||||
"description": "for response pagination, page offset number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 50,
|
||||
"description": "for response pagination, max items per page",
|
||||
"name": "perPage",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Creates a new agent with a random token, scoped to the specified organization",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Create a new organization-scoped agent",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the organization's id",
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "the agent's data (only 'name' and 'no_schedule' are read)",
|
||||
"name": "agent",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Agent"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org_id}/agents/{agent_id}": {
|
||||
"delete": {
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Delete an organization-scoped agent",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the organization's id",
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the agent's id",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Update an organization-scoped agent",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the organization's id",
|
||||
"name": "org_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the agent's id",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "the agent's updated data",
|
||||
"name": "agent",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Agent"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orgs/{org_id}/permissions": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
@ -3144,6 +3331,49 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/pipelines/{number}/metadata": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Get metadata for a pipeline or a specific workflow, including previous pipeline info",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the repository id",
|
||||
"name": "repo_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the number of the pipeline",
|
||||
"name": "number",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/metadata.Metadata"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/pull_requests": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
@ -4359,6 +4589,12 @@ const docTemplate = `{
|
|||
"created": {
|
||||
"type": "integer"
|
||||
},
|
||||
"custom_labels": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -4375,6 +4611,10 @@ const docTemplate = `{
|
|||
"no_schedule": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"org_id": {
|
||||
"description": "OrgID is counted as unset if set to -1, this is done to ensure a new(Agent) still enforce the OrgID check by default",
|
||||
"type": "integer"
|
||||
},
|
||||
"owner_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -5148,6 +5388,225 @@ const docTemplate = `{
|
|||
"EventManual"
|
||||
]
|
||||
},
|
||||
"metadata.Author": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata.Commit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"$ref": "#/definitions/metadata.Author"
|
||||
},
|
||||
"branch": {
|
||||
"type": "string"
|
||||
},
|
||||
"changed_files": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"is_prerelease": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"labels": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"ref": {
|
||||
"type": "string"
|
||||
},
|
||||
"refspec": {
|
||||
"type": "string"
|
||||
},
|
||||
"sha": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata.Forge": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata.Metadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"curr": {
|
||||
"$ref": "#/definitions/metadata.Pipeline"
|
||||
},
|
||||
"forge": {
|
||||
"$ref": "#/definitions/metadata.Forge"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"prev": {
|
||||
"$ref": "#/definitions/metadata.Pipeline"
|
||||
},
|
||||
"repo": {
|
||||
"$ref": "#/definitions/metadata.Repo"
|
||||
},
|
||||
"step": {
|
||||
"$ref": "#/definitions/metadata.Step"
|
||||
},
|
||||
"sys": {
|
||||
"$ref": "#/definitions/metadata.System"
|
||||
},
|
||||
"workflow": {
|
||||
"$ref": "#/definitions/metadata.Workflow"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata.Pipeline": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"commit": {
|
||||
"$ref": "#/definitions/metadata.Commit"
|
||||
},
|
||||
"created": {
|
||||
"type": "integer"
|
||||
},
|
||||
"cron": {
|
||||
"type": "string"
|
||||
},
|
||||
"event": {
|
||||
"type": "string"
|
||||
},
|
||||
"finished": {
|
||||
"type": "integer"
|
||||
},
|
||||
"forge_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"number": {
|
||||
"type": "integer"
|
||||
},
|
||||
"parent": {
|
||||
"type": "integer"
|
||||
},
|
||||
"started": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"target": {
|
||||
"type": "string"
|
||||
},
|
||||
"task": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata.Repo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"clone_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"clone_url_ssh": {
|
||||
"type": "string"
|
||||
},
|
||||
"default_branch": {
|
||||
"type": "string"
|
||||
},
|
||||
"forge_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"private": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"remote_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"scm": {
|
||||
"type": "string"
|
||||
},
|
||||
"trusted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata.Step": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"number": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata.System": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"arch": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata.Workflow": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"matrix": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"number": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.ForgeType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
|
@ -287,7 +287,7 @@ var flags = append([]cli.Flag{
|
|||
Sources: cli.EnvVars("WOODPECKER_FORGE_TIMEOUT"),
|
||||
Name: "forge-timeout",
|
||||
Usage: "how many seconds before timeout when fetching the Woodpecker configuration from a Forge",
|
||||
Value: time.Second * 3,
|
||||
Value: time.Second * 5,
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_FORGE_RETRY"),
|
||||
|
@ -295,36 +295,8 @@ var flags = append([]cli.Flag{
|
|||
Usage: "How many retries of fetching the Woodpecker configuration from a forge are done before we fail",
|
||||
Value: 3,
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_LIMIT_MEM_SWAP"),
|
||||
Name: "limit-mem-swap",
|
||||
Usage: "maximum memory used for swap in bytes",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_LIMIT_MEM"),
|
||||
Name: "limit-mem",
|
||||
Usage: "maximum memory allowed in bytes",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_LIMIT_SHM_SIZE"),
|
||||
Name: "limit-shm-size",
|
||||
Usage: "docker compose /dev/shm allowed in bytes",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_LIMIT_CPU_QUOTA"),
|
||||
Name: "limit-cpu-quota",
|
||||
Usage: "impose a cpu quota",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_LIMIT_CPU_SHARES"),
|
||||
Name: "limit-cpu-shares",
|
||||
Usage: "change the cpu shares",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_LIMIT_CPU_SET"),
|
||||
Name: "limit-cpu-set",
|
||||
Usage: "set the cpus allowed to execute containers",
|
||||
},
|
||||
//
|
||||
// generic forge settings
|
||||
//
|
||||
&cli.StringFlag{
|
||||
Name: "forge-url",
|
||||
|
|
|
@ -178,14 +178,6 @@ func setupEvilGlobals(ctx context.Context, c *cli.Command, s store.Store) error
|
|||
server.Config.Pipeline.DefaultTimeout = c.Int("default-pipeline-timeout")
|
||||
server.Config.Pipeline.MaxTimeout = c.Int("max-pipeline-timeout")
|
||||
|
||||
// limits
|
||||
server.Config.Pipeline.Limits.MemSwapLimit = c.Int("limit-mem-swap")
|
||||
server.Config.Pipeline.Limits.MemLimit = c.Int("limit-mem")
|
||||
server.Config.Pipeline.Limits.ShmSize = c.Int("limit-shm-size")
|
||||
server.Config.Pipeline.Limits.CPUQuota = c.Int("limit-cpu-quota")
|
||||
server.Config.Pipeline.Limits.CPUShares = c.Int("limit-cpu-shares")
|
||||
server.Config.Pipeline.Limits.CPUSet = c.String("limit-cpu-set")
|
||||
|
||||
// backend options for pipeline compiler
|
||||
server.Config.Pipeline.Proxy.No = c.String("backend-no-proxy")
|
||||
server.Config.Pipeline.Proxy.HTTP = c.String("backend-http-proxy")
|
||||
|
|
|
@ -3,7 +3,7 @@ version: '3'
|
|||
|
||||
services:
|
||||
gitea-database:
|
||||
image: postgres:16.3-alpine
|
||||
image: postgres:17.0-alpine
|
||||
environment:
|
||||
POSTGRES_USER: gitea
|
||||
POSTGRES_PASSWORD: 123456
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# docker build --rm -f docker/Dockerfile.make -t woodpecker/make:local .
|
||||
FROM docker.io/golang:1.23-alpine3.19 as golang_image
|
||||
FROM docker.io/node:22-alpine3.19
|
||||
FROM docker.io/node:23-alpine3.19
|
||||
|
||||
# renovate: datasource=repology depName=alpine_3_19/make versioning=loose
|
||||
ENV MAKE_VERSION="4.4.1-r2"
|
||||
|
|
|
@ -179,12 +179,6 @@ Woodpecker provides the ability to pass environment variables to individual step
|
|||
|
||||
For more details, check the [environment docs](./50-environment.md).
|
||||
|
||||
### `secrets`
|
||||
|
||||
Woodpecker provides the ability to store named parameters external to the YAML configuration file, in a central secret store. These secrets can be passed to individual steps of the workflow at runtime.
|
||||
|
||||
For more details, check the [secrets docs](./40-secrets.md).
|
||||
|
||||
### `failure`
|
||||
|
||||
Some of the steps may be allowed to fail without causing the whole workflow and therefore pipeline to report a failure (e.g., a step executing a linting check). To enable this, add `failure: ignore` to your step. If Woodpecker encounters an error while executing the step, it will report it as failed but still executes the next steps of the workflow, if any, without affecting the status of the workflow.
|
||||
|
@ -610,7 +604,7 @@ For more details check the [matrix build docs](./30-matrix-workflows.md).
|
|||
|
||||
You can set labels for your workflow to select an agent to execute the workflow on. An agent will pick up and run a workflow when **every** label assigned to it matches the agents labels.
|
||||
|
||||
To set additional agent labels, check the [agent configuration options](../30-administration/15-agent-config.md#woodpecker_filter_labels). Agents will have at least four default labels: `platform=agent-os/agent-arch`, `hostname=my-agent`, `backend=docker` (type of the agent backend) and `repo=*`. Agents can use a `*` as a wildcard for a label. For example `repo=*` will match every repo.
|
||||
To set additional agent labels, check the [agent configuration options](../30-administration/15-agent-config.md#woodpecker_agent_labels). Agents will have at least four default labels: `platform=agent-os/agent-arch`, `hostname=my-agent`, `backend=docker` (type of the agent backend) and `repo=*`. Agents can use a `*` as a wildcard for a label. For example `repo=*` will match every repo.
|
||||
|
||||
Workflow labels with an empty value will be ignored.
|
||||
By default, each workflow has at least the `repo=your-user/your-repo-name` label. If you have set the [platform attribute](#platform) for your workflow it will have a label like `platform=your-os/your-arch` as well.
|
||||
|
|
|
@ -11,26 +11,7 @@ Woodpecker provides three different levels to add secrets to your pipeline. The
|
|||
|
||||
## Usage
|
||||
|
||||
### Use secrets in commands
|
||||
|
||||
Secrets are exposed to your pipeline steps and plugins as uppercase environment variables and can therefore be referenced in the commands section of your pipeline,
|
||||
once their usage is declared in the `secrets` section:
|
||||
|
||||
```diff
|
||||
steps:
|
||||
- name: docker
|
||||
image: docker
|
||||
commands:
|
||||
+ - echo $docker_username
|
||||
+ - echo $DOCKER_PASSWORD
|
||||
+ secrets: [ docker_username, DOCKER_PASSWORD ]
|
||||
```
|
||||
|
||||
The case of the environment variables is not changed, but secret matching is done case-insensitively. In the example above, `DOCKER_PASSWORD` would also match if the secret is called `docker_password`.
|
||||
|
||||
### Use secrets in settings and environment
|
||||
|
||||
You can set an setting or environment value from secrets using the `from_secret` syntax.
|
||||
You can set a setting or an environment value from secrets using the `from_secret` syntax.
|
||||
|
||||
In this example, the secret named `secret_token` would be passed to the setting named `token`,which will be available in the plugin as environment variable named `PLUGIN_TOKEN` (See [plugins](./51-plugins/20-creating-plugins.md#settings) for details), and to the environment variable `TOKEN_ENV`.
|
||||
|
||||
|
@ -55,11 +36,11 @@ Please note parameter expressions are subject to pre-processing. When using secr
|
|||
- name: docker
|
||||
image: docker
|
||||
commands:
|
||||
- - echo ${docker_username}
|
||||
- - echo ${DOCKER_PASSWORD}
|
||||
+ - echo $${docker_username}
|
||||
+ - echo $${DOCKER_PASSWORD}
|
||||
secrets: [ docker_username, DOCKER_PASSWORD ]
|
||||
- - echo ${TOKEN_ENV}
|
||||
+ - echo $${TOKEN_ENV}
|
||||
environment:
|
||||
TOKEN_ENV:
|
||||
from_secret: secret_token
|
||||
```
|
||||
|
||||
### Use in Pull Requests events
|
||||
|
|
|
@ -35,10 +35,6 @@ Example registry hostname matching logic:
|
|||
- Hostname `docker.io` matches `bradrydzewski/golang`
|
||||
- Hostname `docker.io` matches `bradrydzewski/golang:latest`
|
||||
|
||||
:::note
|
||||
The flow above doesn't work in Kubernetes. There is [workaround](../30-administration/22-backends/40-kubernetes.md#images-from-private-registries).
|
||||
:::
|
||||
|
||||
## Global registry support
|
||||
|
||||
To make a private registry globally available, check the [server configuration docs](../30-administration/10-server-config.md#global-registry-setting).
|
||||
|
|
|
@ -42,7 +42,7 @@ Values like this are converted to JSON and then passed to your plugin. In the ex
|
|||
|
||||
### Secrets
|
||||
|
||||
Secrets should be passed as settings too. Therefore, users should use [`from_secret`](../40-secrets.md#use-secrets-in-settings-and-environment).
|
||||
Secrets should be passed as settings too. Therefore, users should use [`from_secret`](../40-secrets.md#usage).
|
||||
|
||||
## Plugin library
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ While normal steps are all about arbitrary code execution, plugins should only a
|
|||
|
||||
That's why there are a few limitations. The workspace base is always mounted at `/woodpecker`, but the working directory is dynamically
|
||||
adjusted accordingly, as user of a plugin you should not have to care about this. Also, you cannot use the plugin together with `commands`
|
||||
or `entrypoint` which will fail. Using `secrets` or `environment` is possible, but in this case, the plugin is internally not treated as plugin
|
||||
or `entrypoint` which will fail. Using `environment` is possible, but in this case, the plugin is internally not treated as plugin
|
||||
anymore. The container then cannot access secrets with plugin filter anymore and the containers won't be privileged without explicit definition.
|
||||
|
||||
## Finding Plugins
|
||||
|
|
|
@ -49,7 +49,7 @@ Woodpecker needs to know its own address. You must therefore provide the public
|
|||
+ - WOODPECKER_HOST=${WOODPECKER_HOST}
|
||||
```
|
||||
|
||||
Woodpecker can also have its port's configured. It uses a separate port for gRPC and for HTTP. The agent performs gRPC calls and connects to the gRPC port.
|
||||
Woodpecker can also have its ports configured. It uses a separate port for gRPC and for HTTP. The agent performs gRPC calls and connects to the gRPC port.
|
||||
They can be configured with `*_ADDR` variables:
|
||||
|
||||
```diff title="docker-compose.yaml"
|
||||
|
|
|
@ -476,44 +476,6 @@ Supported variables:
|
|||
|
||||
---
|
||||
|
||||
### `WOODPECKER_LIMIT_MEM_SWAP`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The maximum amount of memory a single pipeline container is allowed to swap to disk, configured in bytes. There is no limit if `0`.
|
||||
|
||||
### `WOODPECKER_LIMIT_MEM`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The maximum amount of memory a single pipeline container can use, configured in bytes. There is no limit if `0`.
|
||||
|
||||
### `WOODPECKER_LIMIT_SHM_SIZE`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The maximum amount of memory of `/dev/shm` allowed in bytes. There is no limit if `0`.
|
||||
|
||||
### `WOODPECKER_LIMIT_CPU_QUOTA`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The number of microseconds per CPU period that the container is limited to before throttled. There is no limit if `0`.
|
||||
|
||||
### `WOODPECKER_LIMIT_CPU_SHARES`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The relative weight vs. other containers.
|
||||
|
||||
### `WOODPECKER_LIMIT_CPU_SET`
|
||||
|
||||
> Default: empty
|
||||
|
||||
Comma-separated list to limit the specific CPUs or cores a pipeline container can use.
|
||||
|
||||
Example: `WOODPECKER_LIMIT_CPU_SET=1,2`
|
||||
|
||||
### `WOODPECKER_CONFIG_SERVICE_ENDPOINT`
|
||||
|
||||
> Default: empty
|
||||
|
@ -522,7 +484,7 @@ Specify a configuration service endpoint, see [Configuration Extension](./40-adv
|
|||
|
||||
### `WOODPECKER_FORGE_TIMEOUT`
|
||||
|
||||
> Default: 3s
|
||||
> Default: 5s
|
||||
|
||||
Specify timeout when fetching the Woodpecker configuration from forge. See <https://pkg.go.dev/time#ParseDuration> for syntax reference.
|
||||
|
||||
|
|
|
@ -120,11 +120,14 @@ Configures the path of the agent config file.
|
|||
|
||||
Configures the number of parallel workflows.
|
||||
|
||||
### `WOODPECKER_FILTER_LABELS`
|
||||
### `WOODPECKER_AGENT_LABELS`
|
||||
|
||||
> Default: empty
|
||||
|
||||
Configures labels to filter pipeline pick up. Use a list of key-value pairs like `key=value,second-key=*`. `*` can be used as a wildcard. By default, agents provide three additional labels `platform=os/arch`, `hostname=my-agent` and `repo=*` which can be overwritten if needed. To learn how labels work, check out the [pipeline syntax page](../20-usage/20-workflow-syntax.md#labels).
|
||||
Configures custom labels for the agent, to let workflows filter by it.
|
||||
Use a list of key-value pairs like `key=value,second-key=*`. `*` can be used as a wildcard.
|
||||
By default, agents provide three additional labels `platform=os/arch`, `hostname=my-agent` and `repo=*` which can be overwritten if needed.
|
||||
To learn how labels work, check out the [pipeline syntax page](../20-usage/20-workflow-syntax.md#labels).
|
||||
|
||||
### `WOODPECKER_HEALTHCHECK`
|
||||
|
||||
|
|
|
@ -64,3 +64,41 @@ Enable IPv6 for the networks used by pipeline containers (steps). Make sure you
|
|||
|
||||
List of default volumes separated by comma to be mounted to all pipeline containers (steps). For example to use custom CA
|
||||
certificates installed on host and host timezone use `/etc/ssl/certs:/etc/ssl/certs:ro,/etc/timezone:/etc/timezone`.
|
||||
|
||||
### `WOODPECKER_BACKEND_DOCKER_LIMIT_MEM_SWAP`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The maximum amount of memory a single pipeline container is allowed to swap to disk, configured in bytes. There is no limit if `0`.
|
||||
|
||||
### `WOODPECKER_BACKEND_DOCKER_LIMIT_MEM`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The maximum amount of memory a single pipeline container can use, configured in bytes. There is no limit if `0`.
|
||||
|
||||
### `WOODPECKER_BACKEND_DOCKER_LIMIT_SHM_SIZE`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The maximum amount of memory of `/dev/shm` allowed in bytes. There is no limit if `0`.
|
||||
|
||||
### `WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_QUOTA`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The number of microseconds per CPU period that the container is limited to before throttled. There is no limit if `0`.
|
||||
|
||||
### `WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SHARES`
|
||||
|
||||
> Default: `0`
|
||||
|
||||
The relative weight vs. other containers.
|
||||
|
||||
### `WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SET`
|
||||
|
||||
> Default: empty
|
||||
|
||||
Comma-separated list to limit the specific CPUs or cores a pipeline container can use.
|
||||
|
||||
Example: `WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SET=1,2`
|
||||
|
|
|
@ -8,9 +8,9 @@ The Kubernetes backend executes steps inside standalone Pods. A temporary PVC is
|
|||
|
||||
## Images from private registries
|
||||
|
||||
In order to pull private container images defined in your pipeline YAML you must provide [registry credentials in Kubernetes Secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/).
|
||||
As the Secret is Agent-wide, it has to be placed in namespace defined by `WOODPECKER_BACKEND_K8S_NAMESPACE`.
|
||||
Besides, you need to provide the Secret name to Agent via `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES`.
|
||||
In addition to [registries specified in the UI](../../20-usage/41-registries.md), you may provide [registry credentials in Kubernetes Secrets](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) to pull private container images defined in your pipeline YAML.
|
||||
|
||||
Place these Secrets in namespace defined by `WOODPECKER_BACKEND_K8S_NAMESPACE` and provide the Secret names to Agents via `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES`.
|
||||
|
||||
## Job specific configuration
|
||||
|
||||
|
|
|
@ -4,13 +4,15 @@ Some versions need some changes to the server configuration or the pipeline conf
|
|||
|
||||
## `next`
|
||||
|
||||
- Set `/woodpecker` as defautl workdir for the **woodpecker-cli** container
|
||||
- Deprecate `WOODPECKER_FILTER_LABELS` use `WOODPECKER_AGENT_LABELS`
|
||||
- Removed built-in environment variables:
|
||||
- `CI_COMMIT_URL` use `CI_PIPELINE_FORGE_URL`
|
||||
- `CI_STEP_FINISHED` as empty during execution
|
||||
- `CI_PIPELINE_FINISHED` as empty during execution
|
||||
- `CI_PIPELINE_STATUS` was always `success`
|
||||
- `CI_STEP_STATUS` was always `success`
|
||||
- Set `/woodpecker` as defautl workdir for the **woodpecker-cli** container
|
||||
- Move docker resource limit settings from server into agent configuration
|
||||
- Rename server environment variable `WOODPECKER_ESCALATE` to `WOODPECKER_PLUGINS_PRIVILEGED`
|
||||
- All default privileged plugins (like `woodpeckerci/plugin-docker-buildx`) were removed. Please carefully [re-add those plugins](./30-administration/10-server-config.md#woodpecker_plugins_privileged) you trust and rely on.
|
||||
- `WOODPECKER_DEFAULT_CLONE_IMAGE` got depricated use `WOODPECKER_DEFAULT_CLONE_PLUGIN`
|
||||
|
@ -23,7 +25,7 @@ Some versions need some changes to the server configuration or the pipeline conf
|
|||
- Pipelines without a config file will now be skipped instead of failing
|
||||
- Removed implicitly defined `regcred` image pull secret name. Set it explicitly via `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES`
|
||||
- Removed `includes` and `excludes` support from **event** filter
|
||||
- Removed uppercasing all secret env vars, instead, the value of the `secrets` property is used. [Read more](./20-usage/40-secrets.md#use-secrets-in-commands)
|
||||
- Removed uppercasing all secret env vars, instead, the value of the `secrets` property is used. [Read more](./20-usage/40-secrets.md#usage)
|
||||
- Removed alternative names for secrets, use `environment` with `from_secret`
|
||||
- Removed slice definition for env vars
|
||||
- Removed `environment` filter, use `when.evaluate`
|
||||
|
@ -36,6 +38,7 @@ Some versions need some changes to the server configuration or the pipeline conf
|
|||
- Removed old API routes: `registry/` -> `registries`, `/authorize/token`
|
||||
- Replaced `registry` command with `repo registry` in cli
|
||||
- Disallow upgrades from 1.x, upgrade to 2.x first
|
||||
- Deprecated `secrets`, use `environment` with `from_secret`
|
||||
|
||||
## 2.0.0
|
||||
|
||||
|
|
|
@ -147,7 +147,7 @@ const config: Config = {
|
|||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Woodpecker CI. Built with Docusaurus.`,
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Woodpecker Authors. Built with Docusaurus.`,
|
||||
},
|
||||
prism: {
|
||||
theme: themes.github,
|
||||
|
|
|
@ -14,19 +14,19 @@
|
|||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.1.0",
|
||||
"@docusaurus/plugin-content-blog": "^3.1.0",
|
||||
"@docusaurus/preset-classic": "^3.1.0",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.44.0",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"@docusaurus/core": "^3.5.2",
|
||||
"@docusaurus/plugin-content-blog": "^3.5.2",
|
||||
"@docusaurus/preset-classic": "^3.5.2",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.45.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"clsx": "^2.1.0",
|
||||
"esbuild-loader": "^4.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"esbuild-loader": "^4.2.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"prism-react-renderer": "^2.4.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"redocusaurus": "^2.0.2",
|
||||
"react-dom": "^18.3.1",
|
||||
"redocusaurus": "^2.1.2",
|
||||
"url-loader": "^4.1.1"
|
||||
},
|
||||
"browserslist": {
|
||||
|
@ -42,18 +42,20 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.1.0",
|
||||
"@docusaurus/module-type-aliases": "^3.5.2",
|
||||
"@docusaurus/tsconfig": "3.5.2",
|
||||
"@docusaurus/types": "^3.1.0",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/react": "^18.2.67",
|
||||
"@docusaurus/types": "^3.5.2",
|
||||
"@types/node": "^20.17.1",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"typescript": "^5.4.3"
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"got": "^14.0.0"
|
||||
"got": "^14.0.0",
|
||||
"path-to-regexp": "^3.3.0",
|
||||
"cookie": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"@tsconfig/docusaurus": "^2.0.3",
|
||||
"@types/node": "^20.12.13",
|
||||
"axios": "^1.7.2",
|
||||
"concurrently": "^8.2.2",
|
||||
"concurrently": "^9.0.0",
|
||||
"isomorphic-dompurify": "^2.11.0",
|
||||
"marked": "^14.0.0",
|
||||
"tslib": "^2.6.2",
|
||||
|
|
|
@ -40,11 +40,6 @@
|
|||
"docs": "https://raw.githubusercontent.com/woodpecker-ci/plugin-extend-env/main/docs.md",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"name": "Block Git changes",
|
||||
"docs": "https://codeberg.org/qwerty287/woodpecker-block-git-changes/raw/branch/main/docs.md",
|
||||
"verified": false
|
||||
},
|
||||
{
|
||||
"name": "Regex check",
|
||||
"docs": "https://codeberg.org/qwerty287/woodpecker-regex-check/raw/branch/main/docs.md",
|
||||
|
@ -219,6 +214,11 @@
|
|||
"name": "Docker Tags",
|
||||
"docs": "https://raw.githubusercontent.com/dvjn/woodpecker-docker-tags-plugin/main/docs.md",
|
||||
"verified": false
|
||||
},
|
||||
{
|
||||
"name": "Telegram",
|
||||
"docs": "https://raw.githubusercontent.com/appleboy/drone-telegram/refs/heads/master/DOCS.md",
|
||||
"verified": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
4832
docs/pnpm-lock.yaml
4832
docs/pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
12
flake.lock
12
flake.lock
|
@ -5,11 +5,11 @@
|
|||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1724224976,
|
||||
"narHash": "sha256-Z/ELQhrSd7bMzTO8r7NZgi9g5emh+aRKoCdaAv5fiO0=",
|
||||
"lastModified": 1729665710,
|
||||
"narHash": "sha256-AlcmCXJZPIlO5dmFzV3V2XF6x/OpNWUV8Y/FMPGd8Z4=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c374d94f1536013ca8e92341b540eba4c22f9c62",
|
||||
"rev": "2768c7d042a37de65bb1b5b3268fc987e534c49d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
go-mockery
|
||||
protobuf
|
||||
sqlite
|
||||
go-swag # for generate-swagger
|
||||
addlicense
|
||||
protoc-gen-go
|
||||
protoc-gen-go-grpc
|
||||
];
|
||||
CFLAGS = "-I${pkgs.glibc.dev}/include";
|
||||
LDFLAGS = "-L${pkgs.glibc}/lib";
|
||||
|
|
76
go.mod
76
go.mod
|
@ -2,24 +2,24 @@ module go.woodpecker-ci.org/woodpecker/v2
|
|||
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.23.0
|
||||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
al.essio.dev/pkg/shellescape v1.5.0
|
||||
al.essio.dev/pkg/shellescape v1.5.1
|
||||
code.gitea.io/sdk/gitea v0.19.0
|
||||
codeberg.org/6543/go-yaml2json v1.0.0
|
||||
codeberg.org/6543/xyaml v1.1.0
|
||||
codeberg.org/mvdkleijn/forgejo-sdk/forgejo v1.1.1
|
||||
github.com/6543/logfile-open v1.2.1
|
||||
github.com/adrg/xdg v0.5.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/caddyserver/certmagic v0.21.3
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1
|
||||
github.com/caddyserver/certmagic v0.21.4
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/charmbracelet/huh v0.5.3
|
||||
github.com/charmbracelet/huh v0.6.0
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20240327025511-ec643317aa10
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli v27.1.2+incompatible
|
||||
github.com/docker/docker v27.1.2+incompatible
|
||||
github.com/docker/cli v27.3.1+incompatible
|
||||
github.com/docker/docker v27.3.1+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/drone/envsubst v1.0.3
|
||||
|
@ -32,48 +32,48 @@ require (
|
|||
github.com/gitsight/go-vcsurl v1.0.1
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/go-github/v64 v64.0.0
|
||||
github.com/google/go-github/v66 v66.0.0
|
||||
github.com/google/tink/go v1.7.0
|
||||
github.com/gorilla/securecookie v1.1.2
|
||||
github.com/hashicorp/go-hclog v1.6.3
|
||||
github.com/hashicorp/go-plugin v1.6.1
|
||||
github.com/jellydator/ttlcache/v3 v3.2.1
|
||||
github.com/jellydator/ttlcache/v3 v3.3.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kinbiko/jsonassert v1.1.1
|
||||
github.com/kinbiko/jsonassert v1.2.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/mattn/go-sqlite3 v1.14.24
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/moby/moby v27.1.2+incompatible
|
||||
github.com/moby/moby v27.3.1+incompatible
|
||||
github.com/moby/term v0.5.0
|
||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a
|
||||
github.com/muesli/termenv v0.15.3-0.20240912151726-82936c5ea257
|
||||
github.com/neticdk/go-bitbucket v1.0.0
|
||||
github.com/oklog/ulid/v2 v2.1.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/client_golang v1.20.2
|
||||
github.com/prometheus/client_golang v1.20.4
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha5.0.20240714105325-1da00919bcb4
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240811205807-fc07a8c3673f
|
||||
github.com/xanzy/go-gitlab v0.108.0
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.0.20241004184838-20ef97b2155a
|
||||
github.com/xanzy/go-gitlab v0.111.0
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/yaronf/httpsign v0.3.1
|
||||
github.com/zalando/go-keyring v0.2.5
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/oauth2 v0.22.0
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/net v0.30.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/term v0.23.0
|
||||
golang.org/x/text v0.17.0
|
||||
google.golang.org/grpc v1.65.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
golang.org/x/term v0.25.0
|
||||
golang.org/x/text v0.19.0
|
||||
google.golang.org/grpc v1.67.1
|
||||
google.golang.org/protobuf v1.35.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.31.0
|
||||
k8s.io/apimachinery v0.31.0
|
||||
k8s.io/client-go v0.31.0
|
||||
k8s.io/api v0.31.1
|
||||
k8s.io/apimachinery v0.31.1
|
||||
k8s.io/client-go v0.31.1
|
||||
src.techknowlogick.com/xormigrate v1.7.1
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.9
|
||||
|
@ -93,10 +93,10 @@ require (
|
|||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/catppuccin/go v0.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.19.0 // indirect
|
||||
github.com/charmbracelet/bubbletea v0.27.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.20.0 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.1.0 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.13.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.2.2 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.2.3 // indirect
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
|
@ -147,7 +147,7 @@ require (
|
|||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
|
@ -162,8 +162,8 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mholt/acmez/v2 v2.0.1 // indirect
|
||||
github.com/miekg/dns v1.1.59 // indirect
|
||||
github.com/mholt/acmez/v2 v2.0.3 // indirect
|
||||
github.com/miekg/dns v1.1.62 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
|
@ -195,7 +195,7 @@ require (
|
|||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect
|
||||
|
@ -204,12 +204,12 @@ require (
|
|||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
|
|
149
go.sum
149
go.sum
|
@ -1,5 +1,5 @@
|
|||
al.essio.dev/pkg/shellescape v1.5.0 h1:7oTvSsQ5kg9WksA9O58y9wjYnY4jP0CL82/Q8WLUGKk=
|
||||
al.essio.dev/pkg/shellescape v1.5.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
|
||||
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
|
||||
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
|
||||
code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y=
|
||||
code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI=
|
||||
codeberg.org/6543/go-yaml2json v1.0.0 h1:heGqo9VEi7gY2yNqjj7X4ADs5nzlFIbGsJtgYDLrnig=
|
||||
|
@ -40,16 +40,16 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
|
|||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
|
||||
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0=
|
||||
github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI=
|
||||
github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0=
|
||||
github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=
|
||||
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
|
||||
|
@ -58,18 +58,18 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
|
|||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.19.0 h1:gKZkKXPP6GlDk6EcfujDK19PCQqRjaJZQ7QRERx1UF0=
|
||||
github.com/charmbracelet/bubbles v0.19.0/go.mod h1:WILteEqZ+krG5c3ntGEMeG99nCupcuIk7V0/zOP0tOA=
|
||||
github.com/charmbracelet/bubbletea v0.27.0 h1:Mznj+vvYuYagD9Pn2mY7fuelGvP0HAXtZYGgRBCbHvU=
|
||||
github.com/charmbracelet/bubbletea v0.27.0/go.mod h1:5MdP9XH6MbQkgGhnlxUqCNmBXf9I74KRQ8HIidRxV1Y=
|
||||
github.com/charmbracelet/huh v0.5.3 h1:3KLP4a/K1/S4dq4xFMTNMt3XWhgMl/yx8NYtygQ0bmg=
|
||||
github.com/charmbracelet/huh v0.5.3/go.mod h1:OZC3lshuF+/y8laj//DoZdFSHxC51OrtXLJI8xWVouQ=
|
||||
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
|
||||
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
|
||||
github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c=
|
||||
github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4=
|
||||
github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8=
|
||||
github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU=
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20240327025511-ec643317aa10 h1:/HZJSyFVH5rB1MlCDfkhQhRbLPD2Er29ngWXiUQ8bik=
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20240327025511-ec643317aa10/go.mod h1:nrBG0YEHaxdbqHXW1xvG1hPqkuac9Eg7RTMvogiXuz0=
|
||||
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
||||
github.com/charmbracelet/x/ansi v0.2.2 h1:BC7xzaVpfWIYZRNE8NhO9zo8KA4eGUL6L/JWXDh3GF0=
|
||||
github.com/charmbracelet/x/ansi v0.2.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
|
||||
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
||||
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
|
||||
|
@ -107,10 +107,10 @@ github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDror
|
|||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/docker/cli v27.1.2+incompatible h1:nYviRv5Y+YAKx3dFrTvS1ErkyVVunKOhoweCTE1BsnI=
|
||||
github.com/docker/cli v27.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY=
|
||||
github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ=
|
||||
github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
|
||||
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
|
@ -222,8 +222,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v64 v64.0.0 h1:4G61sozmY3eiPAjjoOHponXDBONm+utovTKbyUb2Qdg=
|
||||
github.com/google/go-github/v64 v64.0.0/go.mod h1:xB3vqMQNdHzilXBiO2I+M7iEFtHf+DP/omBOv6tQzVo=
|
||||
github.com/google/go-github/v66 v66.0.0 h1:ADJsaXj9UotwdgK8/iFZtv7MLc8E8WBl62WLd/D/9+M=
|
||||
github.com/google/go-github/v66 v66.0.0/go.mod h1:+4SO9Zkuyf8ytMj0csN1NR/5OTR+MfqPp8P8dVlcvY4=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
@ -302,8 +302,8 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f
|
|||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jellydator/ttlcache/v3 v3.2.1 h1:eS8ljnYY7BllYGkXw/TfczWZrXUu/CH7SIkC6ugn9Js=
|
||||
github.com/jellydator/ttlcache/v3 v3.2.1/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
|
||||
github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
|
||||
github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
|
||||
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
|
||||
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
|
@ -317,16 +317,15 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d
|
|||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kinbiko/jsonassert v1.1.1 h1:DB12divY+YB+cVpHULLuKePSi6+ui4M/shHSzJISkSE=
|
||||
github.com/kinbiko/jsonassert v1.1.1/go.mod h1:NO4lzrogohtIdNUNzx8sdzB55M4R4Q1bsrWVdqQ7C+A=
|
||||
github.com/kinbiko/jsonassert v1.2.0 h1:+/JthIVXdIrThrOtSN9ry0mNtWKXMWuvxR0nU7gQ+tI=
|
||||
github.com/kinbiko/jsonassert v1.2.0/go.mod h1:pCc3uudOt+lVAbkji9O0uw8MSVt4s+1ZJ0y8Ux2F1Og=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
|
@ -387,12 +386,12 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
|
|||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k=
|
||||
github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U=
|
||||
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mholt/acmez/v2 v2.0.3 h1:CgDBlEwg3QBp6s45tPQmFIBrkRIkBT4rW4orMM6p4sw=
|
||||
github.com/mholt/acmez/v2 v2.0.3/go.mod h1:pQ1ysaDeGrIMvJ9dfJMk5kJNkn7L2sb3UhyrX6Q91cw=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
|
@ -401,8 +400,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
|||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/moby v27.1.2+incompatible h1:vqOs4c7YktTdEBnPQNm0Q+M+IOuxxTCkrYJLBAVsEHQ=
|
||||
github.com/moby/moby v27.1.2+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
|
||||
github.com/moby/moby v27.3.1+incompatible h1:KQbXBjo7PavKpzIl7UkHT31y9lw/e71Uvrqhr4X+zMA=
|
||||
github.com/moby/moby v27.3.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -419,8 +418,8 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D
|
|||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
|
||||
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
|
||||
github.com/muesli/termenv v0.15.3-0.20240912151726-82936c5ea257 h1:RNw/zu+CJemcRlDFPjElZUbY2UlI/MA2B3I6PM3Isiw=
|
||||
github.com/muesli/termenv v0.15.3-0.20240912151726-82936c5ea257/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/neticdk/go-bitbucket v1.0.0 h1:FPvHEgPHoDwD2VHbpyu2R2gnoWQ867RxZd2FivS4wSw=
|
||||
|
@ -453,8 +452,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
|
||||
github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
|
||||
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
|
@ -526,12 +525,12 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
|
|||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha5.0.20240714105325-1da00919bcb4 h1:exFN/ZOxXslYr9t2AjrniP7wPjp/VLLAJhgazj92EBg=
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha5.0.20240714105325-1da00919bcb4/go.mod h1:AIqom6Q60U4tiqHp41i7+/AB2XHgi1WvQ7jOFlccmZ4=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240811205807-fc07a8c3673f h1:C3vgiHDZBVKtNLp3PCQ9//V2NBhGOwUhLILmZhB6/jY=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240811205807-fc07a8c3673f/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.0.20241004184838-20ef97b2155a h1:ipFw/N7kumxX+CA9UoKXX86MNfYsfsom8YOdUC+Rsfw=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.0.20241004184838-20ef97b2155a/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/go-gitlab v0.108.0 h1:IEvEUWFR5G1seslRhJ8gC//INiIUqYXuSUoBd7/gFKE=
|
||||
github.com/xanzy/go-gitlab v0.108.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
|
||||
github.com/xanzy/go-gitlab v0.111.0 h1:4zT52QdDVxGYAGxN2VY8upSvZIiuiI+Z4d+c+7D/lII=
|
||||
github.com/xanzy/go-gitlab v0.111.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
|
@ -548,8 +547,8 @@ github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8L
|
|||
github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
|
@ -604,16 +603,16 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
@ -627,10 +626,10 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -667,14 +666,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
@ -682,8 +681,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -700,22 +699,22 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade h1:WxZOF2yayUHpHSbUE6NMzumUzBxYc3YGwo0YHnbzsJY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a h1:hqK4+jJZXCU4pW7jsAdGOVFIfLHQeV7LaizZKnZ84HI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240723171418-e6d459c13d2a/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
@ -740,12 +739,12 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo=
|
||||
k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE=
|
||||
k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc=
|
||||
k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8=
|
||||
k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU=
|
||||
k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU=
|
||||
k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI=
|
||||
k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U=
|
||||
k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0=
|
||||
k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
|
||||
|
|
|
@ -10,7 +10,7 @@ section: daemon/system
|
|||
contents:
|
||||
- src: ./dist/agent/linux_amd64/woodpecker-agent
|
||||
dst: /usr/local/bin/woodpecker-agent
|
||||
- src: ./woodpecker-agent.service
|
||||
- src: ./nfpm/woodpecker-agent.service
|
||||
dst: /usr/local/lib/systemd/system/
|
||||
- src: ./woodpecker-agent.env.example
|
||||
- src: ./nfpm/woodpecker-agent.env.example
|
||||
dst: /etc/woodpecker/
|
||||
|
|
|
@ -10,7 +10,7 @@ section: daemon/system
|
|||
contents:
|
||||
- src: ./dist/server/linux_amd64/woodpecker-server
|
||||
dst: /usr/local/bin/woodpecker-server
|
||||
- src: ./woodpecker-server.service
|
||||
- src: ./nfpm/woodpecker-server.service
|
||||
dst: /usr/local/lib/systemd/system/
|
||||
- src: ./woodpecker-server.env.example
|
||||
- src: ./nfpm/woodpecker-server.env.example
|
||||
dst: /etc/woodpecker/
|
||||
|
|
71
pipeline/backend/docker/config.go
Normal file
71
pipeline/backend/docker/config.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
enableIPv6 bool
|
||||
network string
|
||||
volumes []string
|
||||
resourceLimit resourceLimit
|
||||
}
|
||||
|
||||
type resourceLimit struct {
|
||||
MemSwapLimit int64
|
||||
MemLimit int64
|
||||
ShmSize int64
|
||||
CPUQuota int64
|
||||
CPUShares int64
|
||||
CPUSet string
|
||||
}
|
||||
|
||||
func configFromCli(c *cli.Command) (config, error) {
|
||||
conf := config{
|
||||
enableIPv6: c.Bool("backend-docker-ipv6"),
|
||||
network: c.String("backend-docker-network"),
|
||||
resourceLimit: resourceLimit{
|
||||
MemSwapLimit: c.Int("backend-docker-limit-mem-swap"),
|
||||
MemLimit: c.Int("backend-docker-limit-mem"),
|
||||
ShmSize: c.Int("backend-docker-limit-shm-size"),
|
||||
CPUQuota: c.Int("backend-docker-limit-cpu-quota"),
|
||||
CPUShares: c.Int("backend-docker-limit-cpu-shares"),
|
||||
CPUSet: c.String("backend-docker-limit-cpu-set"),
|
||||
},
|
||||
}
|
||||
|
||||
volumes := strings.Split(c.String("backend-docker-volumes"), ",")
|
||||
conf.volumes = make([]string, 0, len(volumes))
|
||||
// Validate provided volume definitions
|
||||
for _, v := range volumes {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
parts, err := splitVolumeParts(v)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("can not parse volume config")
|
||||
return conf, fmt.Errorf("invalid volume '%s' provided in WOODPECKER_BACKEND_DOCKER_VOLUMES: %w", v, err)
|
||||
}
|
||||
conf.volumes = append(conf.volumes, strings.Join(parts, ":"))
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
|
@ -68,20 +68,20 @@ func toContainerName(step *types.Step) string {
|
|||
}
|
||||
|
||||
// returns a container host configuration.
|
||||
func toHostConfig(step *types.Step) *container.HostConfig {
|
||||
func toHostConfig(step *types.Step, conf *config) *container.HostConfig {
|
||||
config := &container.HostConfig{
|
||||
Resources: container.Resources{
|
||||
CPUQuota: step.CPUQuota,
|
||||
CPUShares: step.CPUShares,
|
||||
CpusetCpus: step.CPUSet,
|
||||
Memory: step.MemLimit,
|
||||
MemorySwap: step.MemSwapLimit,
|
||||
CPUQuota: conf.resourceLimit.CPUQuota,
|
||||
CPUShares: conf.resourceLimit.CPUShares,
|
||||
CpusetCpus: conf.resourceLimit.CPUSet,
|
||||
Memory: conf.resourceLimit.MemLimit,
|
||||
MemorySwap: conf.resourceLimit.MemSwapLimit,
|
||||
},
|
||||
ShmSize: conf.resourceLimit.ShmSize,
|
||||
LogConfig: container.LogConfig{
|
||||
Type: "json-file",
|
||||
},
|
||||
Privileged: step.Privileged,
|
||||
ShmSize: step.ShmSize,
|
||||
}
|
||||
|
||||
if len(step.NetworkMode) != 0 {
|
||||
|
|
|
@ -196,37 +196,44 @@ func TestToConfigSmall(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestToConfigFull(t *testing.T) {
|
||||
engine := docker{info: system.Info{OSType: "linux/riscv64"}}
|
||||
engine := docker{
|
||||
info: system.Info{OSType: "linux/riscv64"},
|
||||
config: config{
|
||||
enableIPv6: true,
|
||||
resourceLimit: resourceLimit{
|
||||
MemSwapLimit: 12,
|
||||
MemLimit: 13,
|
||||
ShmSize: 14,
|
||||
CPUQuota: 15,
|
||||
CPUShares: 16,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
conf := engine.toConfig(&backend.Step{
|
||||
Name: "test",
|
||||
UUID: "09238932",
|
||||
Type: backend.StepTypeCommands,
|
||||
Image: "golang:1.2.3",
|
||||
Pull: true,
|
||||
Detached: true,
|
||||
Privileged: true,
|
||||
WorkingDir: "/src/abc",
|
||||
Environment: map[string]string{"TAGS": "sqlite"},
|
||||
Commands: []string{"go test", "go vet ./..."},
|
||||
ExtraHosts: []backend.HostAlias{{Name: "t", IP: "1.2.3.4"}},
|
||||
Volumes: []string{"/cache:/cache"},
|
||||
Tmpfs: []string{"/tmp"},
|
||||
Devices: []string{"/dev/sdc"},
|
||||
Networks: []backend.Conn{{Name: "extra-net", Aliases: []string{"extra.net"}}},
|
||||
DNS: []string{"9.9.9.9", "8.8.8.8"},
|
||||
DNSSearch: nil,
|
||||
MemSwapLimit: 12,
|
||||
MemLimit: 13,
|
||||
ShmSize: 14,
|
||||
CPUQuota: 15,
|
||||
CPUShares: 16,
|
||||
OnFailure: true,
|
||||
OnSuccess: true,
|
||||
Failure: "fail",
|
||||
AuthConfig: backend.Auth{Username: "user", Password: "123456"},
|
||||
NetworkMode: "bridge",
|
||||
Ports: []backend.Port{{Number: 21}, {Number: 22}},
|
||||
Name: "test",
|
||||
UUID: "09238932",
|
||||
Type: backend.StepTypeCommands,
|
||||
Image: "golang:1.2.3",
|
||||
Pull: true,
|
||||
Detached: true,
|
||||
Privileged: true,
|
||||
WorkingDir: "/src/abc",
|
||||
Environment: map[string]string{"TAGS": "sqlite"},
|
||||
Commands: []string{"go test", "go vet ./..."},
|
||||
ExtraHosts: []backend.HostAlias{{Name: "t", IP: "1.2.3.4"}},
|
||||
Volumes: []string{"/cache:/cache"},
|
||||
Tmpfs: []string{"/tmp"},
|
||||
Devices: []string{"/dev/sdc"},
|
||||
Networks: []backend.Conn{{Name: "extra-net", Aliases: []string{"extra.net"}}},
|
||||
DNS: []string{"9.9.9.9", "8.8.8.8"},
|
||||
DNSSearch: nil,
|
||||
OnFailure: true,
|
||||
OnSuccess: true,
|
||||
Failure: "fail",
|
||||
AuthConfig: backend.Auth{Username: "user", Password: "123456"},
|
||||
NetworkMode: "bridge",
|
||||
Ports: []backend.Port{{Number: 21}, {Number: 22}},
|
||||
})
|
||||
|
||||
assert.NotNil(t, conf)
|
||||
|
|
|
@ -40,11 +40,9 @@ import (
|
|||
)
|
||||
|
||||
type docker struct {
|
||||
client client.APIClient
|
||||
enableIPv6 bool
|
||||
network string
|
||||
volumes []string
|
||||
info system.Info
|
||||
client client.APIClient
|
||||
info system.Info
|
||||
config config
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -132,22 +130,9 @@ func (e *docker) Load(ctx context.Context) (*backend.BackendInfo, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
e.enableIPv6 = c.Bool("backend-docker-ipv6")
|
||||
e.network = c.String("backend-docker-network")
|
||||
|
||||
volumes := strings.Split(c.String("backend-docker-volumes"), ",")
|
||||
e.volumes = make([]string, 0, len(volumes))
|
||||
// Validate provided volume definitions
|
||||
for _, v := range volumes {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
parts, err := splitVolumeParts(v)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("invalid volume '%s' provided in WOODPECKER_BACKEND_DOCKER_VOLUMES", v)
|
||||
continue
|
||||
}
|
||||
e.volumes = append(e.volumes, strings.Join(parts, ":"))
|
||||
e.config, err = configFromCli(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &backend.BackendInfo{
|
||||
|
@ -175,7 +160,7 @@ func (e *docker) SetupWorkflow(ctx context.Context, conf *backend.Config, taskUU
|
|||
for _, n := range conf.Networks {
|
||||
_, err := e.client.NetworkCreate(ctx, n.Name, network.CreateOptions{
|
||||
Driver: networkDriver,
|
||||
EnableIPv6: &e.enableIPv6,
|
||||
EnableIPv6: &e.config.enableIPv6,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -188,7 +173,7 @@ func (e *docker) StartStep(ctx context.Context, step *backend.Step, taskUUID str
|
|||
log.Trace().Str("taskUUID", taskUUID).Msgf("start step %s", step.Name)
|
||||
|
||||
config := e.toConfig(step)
|
||||
hostConfig := toHostConfig(step)
|
||||
hostConfig := toHostConfig(step, &e.config)
|
||||
containerName := toContainerName(step)
|
||||
|
||||
// create pull options with encoded authorization credentials.
|
||||
|
@ -217,7 +202,7 @@ func (e *docker) StartStep(ctx context.Context, step *backend.Step, taskUUID str
|
|||
}
|
||||
|
||||
// add default volumes to the host configuration
|
||||
hostConfig.Binds = utils.DeduplicateStrings(append(hostConfig.Binds, e.volumes...))
|
||||
hostConfig.Binds = utils.DeduplicateStrings(append(hostConfig.Binds, e.config.volumes...))
|
||||
|
||||
_, err := e.client.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName)
|
||||
if client.IsErrNotFound(err) {
|
||||
|
@ -251,8 +236,8 @@ func (e *docker) StartStep(ctx context.Context, step *backend.Step, taskUUID str
|
|||
}
|
||||
|
||||
// join the container to an existing network
|
||||
if e.network != "" {
|
||||
err = e.client.NetworkConnect(ctx, e.network, containerName, &network.EndpointSettings{})
|
||||
if e.config.network != "" {
|
||||
err = e.client.NetworkConnect(ctx, e.config.network, containerName, &network.EndpointSettings{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -56,4 +56,37 @@ var Flags = []cli.Flag{
|
|||
Name: "backend-docker-volumes",
|
||||
Usage: "backend docker volumes (comma separated)",
|
||||
},
|
||||
//
|
||||
// resource limit parameters
|
||||
//
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_MEM_SWAP", "WOODPECKER_LIMIT_MEM_SWAP"),
|
||||
Name: "backend-docker-limit-mem-swap",
|
||||
Usage: "maximum memory used for swap in bytes",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_MEM", "WOODPECKER_LIMIT_MEM"),
|
||||
Name: "backend-docker-limit-mem",
|
||||
Usage: "maximum memory allowed in bytes",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_SHM_SIZE", "WOODPECKER_LIMIT_SHM_SIZE"),
|
||||
Name: "backend-docker-limit-shm-size",
|
||||
Usage: "docker /dev/shm allowed in bytes",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_QUOTA", "WOODPECKER_LIMIT_CPU_QUOTA"),
|
||||
Name: "backend-docker-limit-cpu-quota",
|
||||
Usage: "impose a cpu quota",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SHARES", "WOODPECKER_LIMIT_CPU_SHARES"),
|
||||
Name: "backend-docker-limit-cpu-shares",
|
||||
Usage: "change the cpu shares",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SET", "WOODPECKER_LIMIT_CPU_SET"),
|
||||
Name: "backend-docker-limit-cpu-set",
|
||||
Usage: "set the cpus allowed to execute containers",
|
||||
},
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ package kubernetes
|
|||
|
||||
import (
|
||||
"context"
|
||||
std_errs "errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
|
@ -225,6 +226,13 @@ func (e *kube) StartStep(ctx context.Context, step *types.Step, taskUUID string)
|
|||
log.Error().Err(err).Msg("could not parse backend options")
|
||||
}
|
||||
|
||||
if needsRegistrySecret(step) {
|
||||
err = startRegistrySecret(ctx, e, step)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace().Str("taskUUID", taskUUID).Msgf("starting step: %s", step.Name)
|
||||
_, err = startPod(ctx, e, step, options)
|
||||
return err
|
||||
|
@ -382,9 +390,20 @@ func (e *kube) TailStep(ctx context.Context, step *types.Step, taskUUID string)
|
|||
}
|
||||
|
||||
func (e *kube) DestroyStep(ctx context.Context, step *types.Step, taskUUID string) error {
|
||||
var errs []error
|
||||
log.Trace().Str("taskUUID", taskUUID).Msgf("Stopping step: %s", step.Name)
|
||||
if needsRegistrySecret(step) {
|
||||
err := stopRegistrySecret(ctx, e, step, defaultDeleteOptions)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
err := stopPod(ctx, e, step, defaultDeleteOptions)
|
||||
return err
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return std_errs.Join(errs...)
|
||||
}
|
||||
|
||||
// DestroyWorkflow destroys the pipeline environment.
|
||||
|
|
|
@ -163,6 +163,14 @@ func podSpec(step *types.Step, config *config, options BackendOptions, nsp nativ
|
|||
|
||||
log.Trace().Msgf("using the image pull secrets: %v", config.ImagePullSecretNames)
|
||||
spec.ImagePullSecrets = secretsReferences(config.ImagePullSecretNames)
|
||||
if needsRegistrySecret(step) {
|
||||
log.Trace().Msgf("using an image pull secret from registries")
|
||||
name, err := registrySecretName(step)
|
||||
if err != nil {
|
||||
return spec, err
|
||||
}
|
||||
spec.ImagePullSecrets = append(spec.ImagePullSecrets, secretReference(name))
|
||||
}
|
||||
|
||||
spec.Volumes = append(spec.Volumes, nsp.volumes...)
|
||||
|
||||
|
@ -514,6 +522,7 @@ func stopPod(ctx context.Context, engine *kube, step *types.Step, deleteOpts met
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Trace().Str("name", podName).Msg("deleting pod")
|
||||
|
||||
err = engine.client.CoreV1().Pods(engine.config.Namespace).Delete(ctx, podName, deleteOpts)
|
||||
|
|
|
@ -264,6 +264,9 @@ func TestFullPod(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"name": "another-pull-secret"
|
||||
},
|
||||
{
|
||||
"name": "wp-01he8bebctabr3kgk0qj36d2me-0"
|
||||
}
|
||||
],
|
||||
"tolerations": [
|
||||
|
@ -317,6 +320,7 @@ func TestFullPod(t *testing.T) {
|
|||
},
|
||||
}
|
||||
pod, err := mkPod(&types.Step{
|
||||
UUID: "01he8bebctabr3kgk0qj36d2me-0",
|
||||
Name: "go-test",
|
||||
Image: "meltwater/drone-cache",
|
||||
WorkingDir: "/woodpecker/src",
|
||||
|
@ -328,6 +332,10 @@ func TestFullPod(t *testing.T) {
|
|||
Environment: map[string]string{"CGO": "0"},
|
||||
ExtraHosts: hostAliases,
|
||||
Ports: ports,
|
||||
AuthConfig: types.Auth{
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
},
|
||||
}, &config{
|
||||
Namespace: "woodpecker",
|
||||
ImagePullSecretNames: []string{"regcred", "another-pull-secret"},
|
||||
|
|
|
@ -15,11 +15,21 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
config_file "github.com/docker/cli/cli/config/configfile"
|
||||
config_file_types "github.com/docker/cli/cli/config/types"
|
||||
"github.com/rs/zerolog/log"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/utils"
|
||||
)
|
||||
|
||||
type nativeSecretsProcessor struct {
|
||||
|
@ -189,3 +199,96 @@ func secretReference(name string) v1.LocalObjectReference {
|
|||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func needsRegistrySecret(step *types.Step) bool {
|
||||
return step.AuthConfig.Username != "" && step.AuthConfig.Password != ""
|
||||
}
|
||||
|
||||
func mkRegistrySecret(step *types.Step, config *config) (*v1.Secret, error) {
|
||||
name, err := registrySecretName(step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labels, err := registrySecretLabels(step)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
named, err := utils.ParseNamed(step.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authConfig := config_file.ConfigFile{
|
||||
AuthConfigs: map[string]config_file_types.AuthConfig{
|
||||
reference.Domain(named): {
|
||||
Username: step.AuthConfig.Username,
|
||||
Password: step.AuthConfig.Password,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
configFileJSON, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &v1.Secret{
|
||||
ObjectMeta: meta_v1.ObjectMeta{
|
||||
Namespace: config.Namespace,
|
||||
Name: name,
|
||||
Labels: labels,
|
||||
},
|
||||
Type: v1.SecretTypeDockerConfigJson,
|
||||
Data: map[string][]byte{
|
||||
v1.DockerConfigJsonKey: configFileJSON,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func registrySecretName(step *types.Step) (string, error) {
|
||||
return podName(step)
|
||||
}
|
||||
|
||||
func registrySecretLabels(step *types.Step) (map[string]string, error) {
|
||||
var err error
|
||||
labels := make(map[string]string)
|
||||
|
||||
if step.Type == types.StepTypeService {
|
||||
labels[ServiceLabel], _ = serviceName(step)
|
||||
}
|
||||
labels[StepLabel], err = stepLabel(step)
|
||||
if err != nil {
|
||||
return labels, err
|
||||
}
|
||||
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func startRegistrySecret(ctx context.Context, engine *kube, step *types.Step) error {
|
||||
secret, err := mkRegistrySecret(step, engine.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace().Msgf("creating secret: %s", secret.Name)
|
||||
_, err = engine.client.CoreV1().Secrets(engine.config.Namespace).Create(ctx, secret, meta_v1.CreateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stopRegistrySecret(ctx context.Context, engine *kube, step *types.Step, deleteOpts meta_v1.DeleteOptions) error {
|
||||
name, err := registrySecretName(step)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace().Str("name", name).Msg("deleting secret")
|
||||
|
||||
err = engine.client.CoreV1().Secrets(engine.config.Namespace).Delete(ctx, name, deleteOpts)
|
||||
if errors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -15,10 +15,14 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/kinbiko/jsonassert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
||||
)
|
||||
|
||||
func TestNativeSecretsEnabled(t *testing.T) {
|
||||
|
@ -178,3 +182,61 @@ func TestFileSecret(t *testing.T) {
|
|||
},
|
||||
}, nsp.mounts)
|
||||
}
|
||||
|
||||
func TestNoAuthNoSecret(t *testing.T) {
|
||||
assert.False(t, needsRegistrySecret(&types.Step{}))
|
||||
}
|
||||
|
||||
func TestNoPasswordNoSecret(t *testing.T) {
|
||||
assert.False(t, needsRegistrySecret(&types.Step{
|
||||
AuthConfig: types.Auth{Username: "foo"},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestNoUsernameNoSecret(t *testing.T) {
|
||||
assert.False(t, needsRegistrySecret(&types.Step{
|
||||
AuthConfig: types.Auth{Password: "foo"},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestUsernameAndPasswordNeedsSecret(t *testing.T) {
|
||||
assert.True(t, needsRegistrySecret(&types.Step{
|
||||
AuthConfig: types.Auth{Username: "foo", Password: "bar"},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestRegistrySecret(t *testing.T) {
|
||||
const expected = `{
|
||||
"metadata": {
|
||||
"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
|
||||
"namespace": "woodpecker",
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"step": "go-test"
|
||||
}
|
||||
},
|
||||
"type": "kubernetes.io/dockerconfigjson",
|
||||
"data": {
|
||||
".dockerconfigjson": "eyJhdXRocyI6eyJkb2NrZXIuaW8iOnsidXNlcm5hbWUiOiJmb28iLCJwYXNzd29yZCI6ImJhciJ9fX0="
|
||||
}
|
||||
}`
|
||||
|
||||
secret, err := mkRegistrySecret(&types.Step{
|
||||
UUID: "01he8bebctabr3kgk0qj36d2me-0",
|
||||
Name: "go-test",
|
||||
Image: "meltwater/drone-cache",
|
||||
AuthConfig: types.Auth{
|
||||
Username: "foo",
|
||||
Password: "bar",
|
||||
},
|
||||
}, &config{
|
||||
Namespace: "woodpecker",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
secretJSON, err := json.Marshal(secret)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ja := jsonassert.New(t)
|
||||
ja.Assertf(string(secretJSON), expected)
|
||||
}
|
||||
|
|
|
@ -34,12 +34,6 @@ type Step struct {
|
|||
Networks []Conn `json:"networks,omitempty"`
|
||||
DNS []string `json:"dns,omitempty"`
|
||||
DNSSearch []string `json:"dns_search,omitempty"`
|
||||
MemSwapLimit int64 `json:"memswap_limit,omitempty"`
|
||||
MemLimit int64 `json:"mem_limit,omitempty"`
|
||||
ShmSize int64 `json:"shm_size,omitempty"`
|
||||
CPUQuota int64 `json:"cpu_quota,omitempty"`
|
||||
CPUShares int64 `json:"cpu_shares,omitempty"`
|
||||
CPUSet string `json:"cpu_set,omitempty"`
|
||||
OnFailure bool `json:"on_failure,omitempty"`
|
||||
OnSuccess bool `json:"on_success,omitempty"`
|
||||
Failure string `json:"failure,omitempty"`
|
||||
|
|
|
@ -46,7 +46,7 @@ type Secret struct {
|
|||
func (s *Secret) Available(event string, container *yaml_types.Container) error {
|
||||
onlyAllowSecretForPlugins := len(s.AllowedPlugins) > 0
|
||||
if onlyAllowSecretForPlugins && !container.IsPlugin() {
|
||||
return fmt.Errorf("secret %q only allowed to be used by plugins by step %q", s.Name, container.Name)
|
||||
return fmt.Errorf("secret %q is only allowed to be used by plugins (a filter has been set on the secret). Note: Image filters do not work for normal steps", s.Name)
|
||||
}
|
||||
|
||||
if onlyAllowSecretForPlugins && !utils.MatchImageDynamic(container.Image, s.AllowedPlugins...) {
|
||||
|
@ -81,15 +81,6 @@ func (s *Secret) Match(event string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
type ResourceLimit struct {
|
||||
MemSwapLimit int64
|
||||
MemLimit int64
|
||||
ShmSize int64
|
||||
CPUQuota int64
|
||||
CPUShares int64
|
||||
CPUSet string
|
||||
}
|
||||
|
||||
// Compiler compiles the yaml.
|
||||
type Compiler struct {
|
||||
local bool
|
||||
|
@ -104,7 +95,6 @@ type Compiler struct {
|
|||
metadata metadata.Metadata
|
||||
registries []Registry
|
||||
secrets map[string]Secret
|
||||
reslimit ResourceLimit
|
||||
defaultClonePlugin string
|
||||
trustedClonePlugins []string
|
||||
trustedPipeline bool
|
||||
|
|
|
@ -50,7 +50,7 @@ func TestSecretAvailable(t *testing.T) {
|
|||
assert.ErrorContains(t, secret.Available("push", &yaml_types.Container{
|
||||
Image: "golang",
|
||||
Commands: yaml_base_types.StringOrSlice{"echo 'this is not a plugin'"},
|
||||
}), "only allowed to be used by plugins by step")
|
||||
}), "is only allowed to be used by plugins (a filter has been set on the secret). Note: Image filters do not work for normal steps")
|
||||
assert.ErrorContains(t, secret.Available("push", &yaml_types.Container{
|
||||
Image: "not-golang",
|
||||
Commands: yaml_base_types.StringOrSlice{},
|
||||
|
|
|
@ -151,31 +151,6 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe
|
|||
}
|
||||
}
|
||||
|
||||
memSwapLimit := int64(container.MemSwapLimit)
|
||||
if c.reslimit.MemSwapLimit != 0 {
|
||||
memSwapLimit = c.reslimit.MemSwapLimit
|
||||
}
|
||||
memLimit := int64(container.MemLimit)
|
||||
if c.reslimit.MemLimit != 0 {
|
||||
memLimit = c.reslimit.MemLimit
|
||||
}
|
||||
shmSize := int64(container.ShmSize)
|
||||
if c.reslimit.ShmSize != 0 {
|
||||
shmSize = c.reslimit.ShmSize
|
||||
}
|
||||
cpuQuota := int64(container.CPUQuota)
|
||||
if c.reslimit.CPUQuota != 0 {
|
||||
cpuQuota = c.reslimit.CPUQuota
|
||||
}
|
||||
cpuShares := int64(container.CPUShares)
|
||||
if c.reslimit.CPUShares != 0 {
|
||||
cpuShares = c.reslimit.CPUShares
|
||||
}
|
||||
cpuSet := container.CPUSet
|
||||
if c.reslimit.CPUSet != "" {
|
||||
cpuSet = c.reslimit.CPUSet
|
||||
}
|
||||
|
||||
var ports []backend_types.Port
|
||||
for _, portDef := range container.Ports {
|
||||
port, err := convertPort(portDef)
|
||||
|
@ -214,12 +189,6 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe
|
|||
Networks: networks,
|
||||
DNS: container.DNS,
|
||||
DNSSearch: container.DNSSearch,
|
||||
MemSwapLimit: memSwapLimit,
|
||||
MemLimit: memLimit,
|
||||
ShmSize: shmSize,
|
||||
CPUQuota: cpuQuota,
|
||||
CPUShares: cpuShares,
|
||||
CPUSet: cpuSet,
|
||||
AuthConfig: authConfig,
|
||||
OnSuccess: onSuccess,
|
||||
OnFailure: onFailure,
|
||||
|
|
|
@ -157,21 +157,6 @@ func WithNetworks(networks ...string) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithResourceLimit configures the compiler with default resource limits that
|
||||
// are applied each container in the pipeline.
|
||||
func WithResourceLimit(swap, mem, shmSize, cpuQuota, cpuShares int64, cpuSet string) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.reslimit = ResourceLimit{
|
||||
MemSwapLimit: swap,
|
||||
MemLimit: mem,
|
||||
ShmSize: shmSize,
|
||||
CPUQuota: cpuQuota,
|
||||
CPUShares: cpuShares,
|
||||
CPUSet: cpuSet,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithDefaultClonePlugin(cloneImage string) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.defaultClonePlugin = cloneImage
|
||||
|
|
|
@ -67,25 +67,6 @@ func TestWithNetworks(t *testing.T) {
|
|||
assert.Equal(t, "overlay_bar", compiler.networks[1])
|
||||
}
|
||||
|
||||
func TestWithResourceLimit(t *testing.T) {
|
||||
compiler := New(
|
||||
WithResourceLimit(
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
"0,2-5",
|
||||
),
|
||||
)
|
||||
assert.EqualValues(t, 1, compiler.reslimit.MemSwapLimit)
|
||||
assert.EqualValues(t, 2, compiler.reslimit.MemLimit)
|
||||
assert.EqualValues(t, 3, compiler.reslimit.ShmSize)
|
||||
assert.EqualValues(t, 4, compiler.reslimit.CPUQuota)
|
||||
assert.EqualValues(t, 5, compiler.reslimit.CPUShares)
|
||||
assert.Equal(t, "0,2-5", compiler.reslimit.CPUSet)
|
||||
}
|
||||
|
||||
func TestWithPrefix(t *testing.T) {
|
||||
assert.Equal(t, "someprefix_", New(WithPrefix("someprefix_")).prefix)
|
||||
}
|
||||
|
|
|
@ -207,9 +207,6 @@ func (l *Linter) lintTrusted(config *WorkflowConfig, c *types.Container, area st
|
|||
if c.Privileged {
|
||||
errors = append(errors, "Insufficient privileges to use privileged mode")
|
||||
}
|
||||
if c.ShmSize != 0 {
|
||||
errors = append(errors, "Insufficient privileges to override shm_size")
|
||||
}
|
||||
if len(c.DNS) != 0 {
|
||||
errors = append(errors, "Insufficient privileges to use custom dns")
|
||||
}
|
||||
|
@ -268,6 +265,21 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
for _, container := range parsed.Steps.ContainerList {
|
||||
if len(container.Secrets) > 0 {
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "Secrets are deprecated, use environment with from_secret",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
Field: fmt.Sprintf("steps.%s.secrets", container.Name),
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/secrets#usage",
|
||||
},
|
||||
IsWarning: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -120,10 +120,6 @@ func TestLintErrors(t *testing.T) {
|
|||
from: "steps: { build: { image: golang, privileged: true } }",
|
||||
want: "Insufficient privileges to use privileged mode",
|
||||
},
|
||||
{
|
||||
from: "steps: { build: { image: golang, shm_size: 10gb } }",
|
||||
want: "Insufficient privileges to override shm_size",
|
||||
},
|
||||
{
|
||||
from: "steps: { build: { image: golang, dns: [ 8.8.8.8 ] } }",
|
||||
want: "Insufficient privileges to use custom dns",
|
||||
|
|
|
@ -47,25 +47,21 @@ type (
|
|||
Ports []string `yaml:"ports,omitempty"`
|
||||
DependsOn base.StringOrSlice `yaml:"depends_on,omitempty"`
|
||||
|
||||
Secrets []string `yaml:"secrets,omitempty"`
|
||||
Environment map[string]any `yaml:"environment,omitempty"`
|
||||
|
||||
// Deprecated
|
||||
Secrets []string `yaml:"secrets,omitempty"`
|
||||
|
||||
// Docker and Kubernetes Specific
|
||||
Privileged bool `yaml:"privileged,omitempty"`
|
||||
|
||||
// Undocumented
|
||||
CPUQuota base.StringOrInt `yaml:"cpu_quota,omitempty"`
|
||||
CPUSet string `yaml:"cpuset,omitempty"`
|
||||
CPUShares base.StringOrInt `yaml:"cpu_shares,omitempty"`
|
||||
Devices []string `yaml:"devices,omitempty"`
|
||||
DNSSearch base.StringOrSlice `yaml:"dns_search,omitempty"`
|
||||
DNS base.StringOrSlice `yaml:"dns,omitempty"`
|
||||
ExtraHosts []string `yaml:"extra_hosts,omitempty"`
|
||||
MemLimit base.MemStringOrInt `yaml:"mem_limit,omitempty"`
|
||||
MemSwapLimit base.MemStringOrInt `yaml:"memswap_limit,omitempty"`
|
||||
NetworkMode string `yaml:"network_mode,omitempty"`
|
||||
ShmSize base.MemStringOrInt `yaml:"shm_size,omitempty"`
|
||||
Tmpfs []string `yaml:"tmpfs,omitempty"`
|
||||
Devices []string `yaml:"devices,omitempty"`
|
||||
DNSSearch base.StringOrSlice `yaml:"dns_search,omitempty"`
|
||||
DNS base.StringOrSlice `yaml:"dns,omitempty"`
|
||||
ExtraHosts []string `yaml:"extra_hosts,omitempty"`
|
||||
NetworkMode string `yaml:"network_mode,omitempty"`
|
||||
Tmpfs []string `yaml:"tmpfs,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -30,9 +30,6 @@ image: golang:latest
|
|||
commands:
|
||||
- go build
|
||||
- go test
|
||||
cpu_quota: 11
|
||||
cpuset: 1,2
|
||||
cpu_shares: 99
|
||||
detach: true
|
||||
devices:
|
||||
- /dev/ttyUSB0:/dev/ttyUSB0
|
||||
|
@ -54,9 +51,6 @@ networks:
|
|||
- other-network
|
||||
pull: true
|
||||
privileged: true
|
||||
shm_size: 1kb
|
||||
mem_limit: 1kb
|
||||
memswap_limit: 1kb
|
||||
volumes:
|
||||
- /var/lib/mysql
|
||||
- /opt/data:/var/lib/mysql
|
||||
|
@ -78,27 +72,21 @@ ports:
|
|||
|
||||
func TestUnmarshalContainer(t *testing.T) {
|
||||
want := Container{
|
||||
Commands: base.StringOrSlice{"go build", "go test"},
|
||||
CPUQuota: base.StringOrInt(11),
|
||||
CPUSet: "1,2",
|
||||
CPUShares: base.StringOrInt(99),
|
||||
Detached: true,
|
||||
Devices: []string{"/dev/ttyUSB0:/dev/ttyUSB0"},
|
||||
Directory: "example/",
|
||||
DNS: base.StringOrSlice{"8.8.8.8"},
|
||||
DNSSearch: base.StringOrSlice{"example.com"},
|
||||
Entrypoint: []string{"/bin/sh", "-c"},
|
||||
Environment: map[string]any{"RACK_ENV": "development", "SHOW": true},
|
||||
ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229", "ipv6:2001:db8::10"},
|
||||
Image: "golang:latest",
|
||||
MemLimit: base.MemStringOrInt(1024),
|
||||
MemSwapLimit: base.MemStringOrInt(1024),
|
||||
Name: "my-build-container",
|
||||
NetworkMode: "bridge",
|
||||
Pull: true,
|
||||
Privileged: true,
|
||||
ShmSize: base.MemStringOrInt(1024),
|
||||
Tmpfs: base.StringOrSlice{"/var/lib/test"},
|
||||
Commands: base.StringOrSlice{"go build", "go test"},
|
||||
Detached: true,
|
||||
Devices: []string{"/dev/ttyUSB0:/dev/ttyUSB0"},
|
||||
Directory: "example/",
|
||||
DNS: base.StringOrSlice{"8.8.8.8"},
|
||||
DNSSearch: base.StringOrSlice{"example.com"},
|
||||
Entrypoint: []string{"/bin/sh", "-c"},
|
||||
Environment: map[string]any{"RACK_ENV": "development", "SHOW": true},
|
||||
ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229", "ipv6:2001:db8::10"},
|
||||
Image: "golang:latest",
|
||||
Name: "my-build-container",
|
||||
NetworkMode: "bridge",
|
||||
Pull: true,
|
||||
Privileged: true,
|
||||
Tmpfs: base.StringOrSlice{"/var/lib/test"},
|
||||
Volumes: Volumes{
|
||||
Volumes: []*Volume{
|
||||
{Source: "", Destination: "/var/lib/mysql"},
|
||||
|
|
|
@ -84,14 +84,19 @@ func imageHasTag(name string) bool {
|
|||
return strings.Contains(name, ":")
|
||||
}
|
||||
|
||||
// ParseNamed parses an image as a reference to validate it then parses it as a named reference.
|
||||
func ParseNamed(image string) (reference.Named, error) {
|
||||
ref, err := reference.ParseAnyReference(image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return reference.ParseNamed(ref.String())
|
||||
}
|
||||
|
||||
// MatchHostname returns true if the image hostname
|
||||
// matches the specified hostname.
|
||||
func MatchHostname(image, hostname string) bool {
|
||||
ref, err := reference.ParseAnyReference(image)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
named, err := reference.ParseNamed(ref.String())
|
||||
named, err := ParseNamed(image)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -106,9 +106,9 @@ func (_m *Peer) Next(c context.Context, f rpc.Filter) (*rpc.Workflow, error) {
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// RegisterAgent provides a mock function with given fields: ctx, platform, backend, version, capacity
|
||||
func (_m *Peer) RegisterAgent(ctx context.Context, platform string, backend string, version string, capacity int) (int64, error) {
|
||||
ret := _m.Called(ctx, platform, backend, version, capacity)
|
||||
// RegisterAgent provides a mock function with given fields: ctx, info
|
||||
func (_m *Peer) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) {
|
||||
ret := _m.Called(ctx, info)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for RegisterAgent")
|
||||
|
@ -116,17 +116,17 @@ func (_m *Peer) RegisterAgent(ctx context.Context, platform string, backend stri
|
|||
|
||||
var r0 int64
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, int) (int64, error)); ok {
|
||||
return rf(ctx, platform, backend, version, capacity)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, rpc.AgentInfo) (int64, error)); ok {
|
||||
return rf(ctx, info)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, string, int) int64); ok {
|
||||
r0 = rf(ctx, platform, backend, version, capacity)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, rpc.AgentInfo) int64); ok {
|
||||
r0 = rf(ctx, info)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string, string, string, int) error); ok {
|
||||
r1 = rf(ctx, platform, backend, version, capacity)
|
||||
if rf, ok := ret.Get(1).(func(context.Context, rpc.AgentInfo) error); ok {
|
||||
r1 = rf(ctx, info)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
|
|
@ -55,6 +55,15 @@ type (
|
|||
GrpcVersion int32 `json:"grpc_version,omitempty"`
|
||||
ServerVersion string `json:"server_version,omitempty"`
|
||||
}
|
||||
|
||||
// AgentInfo represents all the metadata that should be known about an agent.
|
||||
AgentInfo struct {
|
||||
Version string `json:"version"`
|
||||
Platform string `json:"platform"`
|
||||
Backend string `json:"backend"`
|
||||
Capacity int `json:"capacity"`
|
||||
CustomLabels map[string]string `json:"custom_labels"`
|
||||
}
|
||||
)
|
||||
|
||||
//go:generate mockery --name Peer --output mocks --case underscore --note "+build test"
|
||||
|
@ -86,7 +95,7 @@ type Peer interface {
|
|||
EnqueueLog(logEntry *LogEntry)
|
||||
|
||||
// RegisterAgent register our agent to the server
|
||||
RegisterAgent(ctx context.Context, platform, backend, version string, capacity int) (int64, error)
|
||||
RegisterAgent(ctx context.Context, info AgentInfo) (int64, error)
|
||||
|
||||
// UnregisterAgent unregister our agent from the server
|
||||
UnregisterAgent(ctx context.Context) error
|
||||
|
|
|
@ -16,4 +16,4 @@ package proto
|
|||
|
||||
// Version is the version of the woodpecker.proto file,
|
||||
// IMPORTANT: increased by 1 each time it get changed.
|
||||
const Version int32 = 10
|
||||
const Version int32 = 11
|
||||
|
|
|
@ -812,21 +812,97 @@ func (x *ReportHealthRequest) GetStatus() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
type AgentInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"`
|
||||
Capacity int32 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"`
|
||||
Backend string `protobuf:"bytes,3,opt,name=backend,proto3" json:"backend,omitempty"`
|
||||
Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
|
||||
CustomLabels map[string]string `protobuf:"bytes,5,rep,name=customLabels,proto3" json:"customLabels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
func (x *AgentInfo) Reset() {
|
||||
*x = AgentInfo{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_woodpecker_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *AgentInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AgentInfo) ProtoMessage() {}
|
||||
|
||||
func (x *AgentInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_woodpecker_proto_msgTypes[14]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AgentInfo.ProtoReflect.Descriptor instead.
|
||||
func (*AgentInfo) Descriptor() ([]byte, []int) {
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
func (x *AgentInfo) GetPlatform() string {
|
||||
if x != nil {
|
||||
return x.Platform
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AgentInfo) GetCapacity() int32 {
|
||||
if x != nil {
|
||||
return x.Capacity
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *AgentInfo) GetBackend() string {
|
||||
if x != nil {
|
||||
return x.Backend
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AgentInfo) GetVersion() string {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *AgentInfo) GetCustomLabels() map[string]string {
|
||||
if x != nil {
|
||||
return x.CustomLabels
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type RegisterAgentRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"`
|
||||
Capacity int32 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"`
|
||||
Backend string `protobuf:"bytes,3,opt,name=backend,proto3" json:"backend,omitempty"`
|
||||
Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
|
||||
Info *AgentInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"`
|
||||
}
|
||||
|
||||
func (x *RegisterAgentRequest) Reset() {
|
||||
*x = RegisterAgentRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_woodpecker_proto_msgTypes[14]
|
||||
mi := &file_woodpecker_proto_msgTypes[15]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -839,7 +915,7 @@ func (x *RegisterAgentRequest) String() string {
|
|||
func (*RegisterAgentRequest) ProtoMessage() {}
|
||||
|
||||
func (x *RegisterAgentRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_woodpecker_proto_msgTypes[14]
|
||||
mi := &file_woodpecker_proto_msgTypes[15]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -852,35 +928,14 @@ func (x *RegisterAgentRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use RegisterAgentRequest.ProtoReflect.Descriptor instead.
|
||||
func (*RegisterAgentRequest) Descriptor() ([]byte, []int) {
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{14}
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{15}
|
||||
}
|
||||
|
||||
func (x *RegisterAgentRequest) GetPlatform() string {
|
||||
func (x *RegisterAgentRequest) GetInfo() *AgentInfo {
|
||||
if x != nil {
|
||||
return x.Platform
|
||||
return x.Info
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RegisterAgentRequest) GetCapacity() int32 {
|
||||
if x != nil {
|
||||
return x.Capacity
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *RegisterAgentRequest) GetBackend() string {
|
||||
if x != nil {
|
||||
return x.Backend
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RegisterAgentRequest) GetVersion() string {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
type VersionResponse struct {
|
||||
|
@ -895,7 +950,7 @@ type VersionResponse struct {
|
|||
func (x *VersionResponse) Reset() {
|
||||
*x = VersionResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_woodpecker_proto_msgTypes[15]
|
||||
mi := &file_woodpecker_proto_msgTypes[16]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -908,7 +963,7 @@ func (x *VersionResponse) String() string {
|
|||
func (*VersionResponse) ProtoMessage() {}
|
||||
|
||||
func (x *VersionResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_woodpecker_proto_msgTypes[15]
|
||||
mi := &file_woodpecker_proto_msgTypes[16]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -921,7 +976,7 @@ func (x *VersionResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead.
|
||||
func (*VersionResponse) Descriptor() ([]byte, []int) {
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{15}
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *VersionResponse) GetGrpcVersion() int32 {
|
||||
|
@ -949,7 +1004,7 @@ type NextResponse struct {
|
|||
func (x *NextResponse) Reset() {
|
||||
*x = NextResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_woodpecker_proto_msgTypes[16]
|
||||
mi := &file_woodpecker_proto_msgTypes[17]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -962,7 +1017,7 @@ func (x *NextResponse) String() string {
|
|||
func (*NextResponse) ProtoMessage() {}
|
||||
|
||||
func (x *NextResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_woodpecker_proto_msgTypes[16]
|
||||
mi := &file_woodpecker_proto_msgTypes[17]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -975,7 +1030,7 @@ func (x *NextResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use NextResponse.ProtoReflect.Descriptor instead.
|
||||
func (*NextResponse) Descriptor() ([]byte, []int) {
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{16}
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{17}
|
||||
}
|
||||
|
||||
func (x *NextResponse) GetWorkflow() *Workflow {
|
||||
|
@ -996,7 +1051,7 @@ type RegisterAgentResponse struct {
|
|||
func (x *RegisterAgentResponse) Reset() {
|
||||
*x = RegisterAgentResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_woodpecker_proto_msgTypes[17]
|
||||
mi := &file_woodpecker_proto_msgTypes[18]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1009,7 +1064,7 @@ func (x *RegisterAgentResponse) String() string {
|
|||
func (*RegisterAgentResponse) ProtoMessage() {}
|
||||
|
||||
func (x *RegisterAgentResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_woodpecker_proto_msgTypes[17]
|
||||
mi := &file_woodpecker_proto_msgTypes[18]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1022,7 +1077,7 @@ func (x *RegisterAgentResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use RegisterAgentResponse.ProtoReflect.Descriptor instead.
|
||||
func (*RegisterAgentResponse) Descriptor() ([]byte, []int) {
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{17}
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{18}
|
||||
}
|
||||
|
||||
func (x *RegisterAgentResponse) GetAgentId() int64 {
|
||||
|
@ -1044,7 +1099,7 @@ type AuthRequest struct {
|
|||
func (x *AuthRequest) Reset() {
|
||||
*x = AuthRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_woodpecker_proto_msgTypes[18]
|
||||
mi := &file_woodpecker_proto_msgTypes[19]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1057,7 +1112,7 @@ func (x *AuthRequest) String() string {
|
|||
func (*AuthRequest) ProtoMessage() {}
|
||||
|
||||
func (x *AuthRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_woodpecker_proto_msgTypes[18]
|
||||
mi := &file_woodpecker_proto_msgTypes[19]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1070,7 +1125,7 @@ func (x *AuthRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use AuthRequest.ProtoReflect.Descriptor instead.
|
||||
func (*AuthRequest) Descriptor() ([]byte, []int) {
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{18}
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{19}
|
||||
}
|
||||
|
||||
func (x *AuthRequest) GetAgentToken() string {
|
||||
|
@ -1100,7 +1155,7 @@ type AuthResponse struct {
|
|||
func (x *AuthResponse) Reset() {
|
||||
*x = AuthResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_woodpecker_proto_msgTypes[19]
|
||||
mi := &file_woodpecker_proto_msgTypes[20]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -1113,7 +1168,7 @@ func (x *AuthResponse) String() string {
|
|||
func (*AuthResponse) ProtoMessage() {}
|
||||
|
||||
func (x *AuthResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_woodpecker_proto_msgTypes[19]
|
||||
mi := &file_woodpecker_proto_msgTypes[20]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -1126,7 +1181,7 @@ func (x *AuthResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use AuthResponse.ProtoReflect.Descriptor instead.
|
||||
func (*AuthResponse) Descriptor() ([]byte, []int) {
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{19}
|
||||
return file_woodpecker_proto_rawDescGZIP(), []int{20}
|
||||
}
|
||||
|
||||
func (x *AuthResponse) GetStatus() string {
|
||||
|
@ -1220,83 +1275,95 @@ var file_woodpecker_proto_rawDesc = []byte{
|
|||
0x22, 0x2d, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22,
|
||||
0x82, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74,
|
||||
0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74,
|
||||
0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
|
||||
0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
|
||||
0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65,
|
||||
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x22, 0x5b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x70, 0x63, 0x5f,
|
||||
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x67,
|
||||
0x72, 0x70, 0x63, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65,
|
||||
0x72, 0x76, 0x65, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,
|
||||
0x6e, 0x22, 0x3b, 0x0a, 0x0c, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x2b, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x6f, 0x72, 0x6b,
|
||||
0x66, 0x6c, 0x6f, 0x77, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x32,
|
||||
0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x49, 0x64, 0x22, 0x49, 0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b,
|
||||
0x65, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02,
|
||||
0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x64, 0x0a,
|
||||
0x0c, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a,
|
||||
0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73,
|
||||
0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69,
|
||||
0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64,
|
||||
0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f,
|
||||
0x6b, 0x65, 0x6e, 0x32, 0xbb, 0x04, 0x0a, 0x0a, 0x57, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b,
|
||||
0x65, 0x72, 0x12, 0x31, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0c, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x04, 0x4e, 0x65, 0x78, 0x74, 0x12, 0x12, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2a, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74,
|
||||
0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70,
|
||||
0x74, 0x79, 0x22, 0x00, 0x12, 0x2a, 0x0a, 0x04, 0x57, 0x61, 0x69, 0x74, 0x12, 0x12, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x80, 0x02, 0x0a, 0x09, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a,
|
||||
0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70,
|
||||
0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70,
|
||||
0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x0c, 0x63, 0x75, 0x73,
|
||||
0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32,
|
||||
0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66,
|
||||
0x6f, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e,
|
||||
0x74, 0x72, 0x79, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c,
|
||||
0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c,
|
||||
0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
|
||||
0x38, 0x01, 0x22, 0x3c, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x69, 0x6e,
|
||||
0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f,
|
||||
0x22, 0x5b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73,
|
||||
0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x67, 0x72, 0x70, 0x63, 0x56,
|
||||
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,
|
||||
0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
|
||||
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3b, 0x0a,
|
||||
0x0c, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a,
|
||||
0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77,
|
||||
0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x32, 0x0a, 0x15, 0x52, 0x65,
|
||||
0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||
0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18,
|
||||
0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x49,
|
||||
0x0a, 0x0b, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a,
|
||||
0x0b, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0a, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x19,
|
||||
0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
|
||||
0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x64, 0x0a, 0x0c, 0x41, 0x75, 0x74,
|
||||
0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61,
|
||||
0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
|
||||
0x73, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c,
|
||||
0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x32,
|
||||
0xbb, 0x04, 0x0a, 0x0a, 0x57, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x12, 0x31,
|
||||
0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
|
||||
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x12, 0x31, 0x0a, 0x04, 0x4e, 0x65, 0x78, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x12, 0x2a, 0x0a, 0x04, 0x49, 0x6e, 0x69, 0x74, 0x12, 0x12, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,
|
||||
0x12, 0x2a, 0x0a, 0x04, 0x44, 0x6f, 0x6e, 0x65, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2e, 0x44, 0x6f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x06,
|
||||
0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45,
|
||||
0x78, 0x74, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x06,
|
||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x28, 0x0a, 0x03,
|
||||
0x4c, 0x6f, 0x67, 0x12, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45,
|
||||
0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
|
||||
0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
|
||||
0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x67,
|
||||
0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x0f, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74,
|
||||
0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
|
||||
0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d,
|
||||
0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48,
|
||||
0x65, 0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65,
|
||||
0x70, 0x6f, 0x72, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,
|
||||
0x00, 0x32, 0x43, 0x0a, 0x0e, 0x57, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x41,
|
||||
0x75, 0x74, 0x68, 0x12, 0x31, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x12, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x6f, 0x2e, 0x77, 0x6f, 0x6f,
|
||||
0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x2d, 0x63, 0x69, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77,
|
||||
0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x69, 0x70,
|
||||
0x65, 0x6c, 0x69, 0x6e, 0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
|
||||
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x12, 0x2a, 0x0a, 0x04, 0x57, 0x61, 0x69, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2e, 0x57, 0x61, 0x69, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2a, 0x0a, 0x04,
|
||||
0x44, 0x6f, 0x6e, 0x65, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x44, 0x6f, 0x6e,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x06, 0x45, 0x78, 0x74, 0x65,
|
||||
0x6e, 0x64, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e,
|
||||
0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x28, 0x0a, 0x03, 0x4c, 0x6f, 0x67, 0x12,
|
||||
0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79,
|
||||
0x22, 0x00, 0x12, 0x4c, 0x0a, 0x0d, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x67, 0x69,
|
||||
0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65,
|
||||
0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x12, 0x2f, 0x0a, 0x0f, 0x55, 0x6e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x12, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74,
|
||||
0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22,
|
||||
0x00, 0x12, 0x3a, 0x0a, 0x0c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74,
|
||||
0x68, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74,
|
||||
0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x32, 0x43, 0x0a,
|
||||
0x0e, 0x57, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63, 0x6b, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x12,
|
||||
0x31, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
|
||||
0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x6f, 0x2e, 0x77, 0x6f, 0x6f, 0x64, 0x70, 0x65, 0x63,
|
||||
0x6b, 0x65, 0x72, 0x2d, 0x63, 0x69, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x77, 0x6f, 0x6f, 0x64, 0x70,
|
||||
0x65, 0x63, 0x6b, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x70, 0x69, 0x70, 0x65, 0x6c, 0x69, 0x6e,
|
||||
0x65, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -1311,7 +1378,7 @@ func file_woodpecker_proto_rawDescGZIP() []byte {
|
|||
return file_woodpecker_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 21)
|
||||
var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
|
||||
var file_woodpecker_proto_goTypes = []interface{}{
|
||||
(*StepState)(nil), // 0: proto.StepState
|
||||
(*WorkflowState)(nil), // 1: proto.WorkflowState
|
||||
|
@ -1327,51 +1394,55 @@ var file_woodpecker_proto_goTypes = []interface{}{
|
|||
(*LogRequest)(nil), // 11: proto.LogRequest
|
||||
(*Empty)(nil), // 12: proto.Empty
|
||||
(*ReportHealthRequest)(nil), // 13: proto.ReportHealthRequest
|
||||
(*RegisterAgentRequest)(nil), // 14: proto.RegisterAgentRequest
|
||||
(*VersionResponse)(nil), // 15: proto.VersionResponse
|
||||
(*NextResponse)(nil), // 16: proto.NextResponse
|
||||
(*RegisterAgentResponse)(nil), // 17: proto.RegisterAgentResponse
|
||||
(*AuthRequest)(nil), // 18: proto.AuthRequest
|
||||
(*AuthResponse)(nil), // 19: proto.AuthResponse
|
||||
nil, // 20: proto.Filter.LabelsEntry
|
||||
(*AgentInfo)(nil), // 14: proto.AgentInfo
|
||||
(*RegisterAgentRequest)(nil), // 15: proto.RegisterAgentRequest
|
||||
(*VersionResponse)(nil), // 16: proto.VersionResponse
|
||||
(*NextResponse)(nil), // 17: proto.NextResponse
|
||||
(*RegisterAgentResponse)(nil), // 18: proto.RegisterAgentResponse
|
||||
(*AuthRequest)(nil), // 19: proto.AuthRequest
|
||||
(*AuthResponse)(nil), // 20: proto.AuthResponse
|
||||
nil, // 21: proto.Filter.LabelsEntry
|
||||
nil, // 22: proto.AgentInfo.CustomLabelsEntry
|
||||
}
|
||||
var file_woodpecker_proto_depIdxs = []int32{
|
||||
20, // 0: proto.Filter.labels:type_name -> proto.Filter.LabelsEntry
|
||||
21, // 0: proto.Filter.labels:type_name -> proto.Filter.LabelsEntry
|
||||
3, // 1: proto.NextRequest.filter:type_name -> proto.Filter
|
||||
1, // 2: proto.InitRequest.state:type_name -> proto.WorkflowState
|
||||
1, // 3: proto.DoneRequest.state:type_name -> proto.WorkflowState
|
||||
0, // 4: proto.UpdateRequest.state:type_name -> proto.StepState
|
||||
2, // 5: proto.LogRequest.logEntries:type_name -> proto.LogEntry
|
||||
4, // 6: proto.NextResponse.workflow:type_name -> proto.Workflow
|
||||
12, // 7: proto.Woodpecker.Version:input_type -> proto.Empty
|
||||
5, // 8: proto.Woodpecker.Next:input_type -> proto.NextRequest
|
||||
6, // 9: proto.Woodpecker.Init:input_type -> proto.InitRequest
|
||||
7, // 10: proto.Woodpecker.Wait:input_type -> proto.WaitRequest
|
||||
8, // 11: proto.Woodpecker.Done:input_type -> proto.DoneRequest
|
||||
9, // 12: proto.Woodpecker.Extend:input_type -> proto.ExtendRequest
|
||||
10, // 13: proto.Woodpecker.Update:input_type -> proto.UpdateRequest
|
||||
11, // 14: proto.Woodpecker.Log:input_type -> proto.LogRequest
|
||||
14, // 15: proto.Woodpecker.RegisterAgent:input_type -> proto.RegisterAgentRequest
|
||||
12, // 16: proto.Woodpecker.UnregisterAgent:input_type -> proto.Empty
|
||||
13, // 17: proto.Woodpecker.ReportHealth:input_type -> proto.ReportHealthRequest
|
||||
18, // 18: proto.WoodpeckerAuth.Auth:input_type -> proto.AuthRequest
|
||||
15, // 19: proto.Woodpecker.Version:output_type -> proto.VersionResponse
|
||||
16, // 20: proto.Woodpecker.Next:output_type -> proto.NextResponse
|
||||
12, // 21: proto.Woodpecker.Init:output_type -> proto.Empty
|
||||
12, // 22: proto.Woodpecker.Wait:output_type -> proto.Empty
|
||||
12, // 23: proto.Woodpecker.Done:output_type -> proto.Empty
|
||||
12, // 24: proto.Woodpecker.Extend:output_type -> proto.Empty
|
||||
12, // 25: proto.Woodpecker.Update:output_type -> proto.Empty
|
||||
12, // 26: proto.Woodpecker.Log:output_type -> proto.Empty
|
||||
17, // 27: proto.Woodpecker.RegisterAgent:output_type -> proto.RegisterAgentResponse
|
||||
12, // 28: proto.Woodpecker.UnregisterAgent:output_type -> proto.Empty
|
||||
12, // 29: proto.Woodpecker.ReportHealth:output_type -> proto.Empty
|
||||
19, // 30: proto.WoodpeckerAuth.Auth:output_type -> proto.AuthResponse
|
||||
19, // [19:31] is the sub-list for method output_type
|
||||
7, // [7:19] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
22, // 6: proto.AgentInfo.customLabels:type_name -> proto.AgentInfo.CustomLabelsEntry
|
||||
14, // 7: proto.RegisterAgentRequest.info:type_name -> proto.AgentInfo
|
||||
4, // 8: proto.NextResponse.workflow:type_name -> proto.Workflow
|
||||
12, // 9: proto.Woodpecker.Version:input_type -> proto.Empty
|
||||
5, // 10: proto.Woodpecker.Next:input_type -> proto.NextRequest
|
||||
6, // 11: proto.Woodpecker.Init:input_type -> proto.InitRequest
|
||||
7, // 12: proto.Woodpecker.Wait:input_type -> proto.WaitRequest
|
||||
8, // 13: proto.Woodpecker.Done:input_type -> proto.DoneRequest
|
||||
9, // 14: proto.Woodpecker.Extend:input_type -> proto.ExtendRequest
|
||||
10, // 15: proto.Woodpecker.Update:input_type -> proto.UpdateRequest
|
||||
11, // 16: proto.Woodpecker.Log:input_type -> proto.LogRequest
|
||||
15, // 17: proto.Woodpecker.RegisterAgent:input_type -> proto.RegisterAgentRequest
|
||||
12, // 18: proto.Woodpecker.UnregisterAgent:input_type -> proto.Empty
|
||||
13, // 19: proto.Woodpecker.ReportHealth:input_type -> proto.ReportHealthRequest
|
||||
19, // 20: proto.WoodpeckerAuth.Auth:input_type -> proto.AuthRequest
|
||||
16, // 21: proto.Woodpecker.Version:output_type -> proto.VersionResponse
|
||||
17, // 22: proto.Woodpecker.Next:output_type -> proto.NextResponse
|
||||
12, // 23: proto.Woodpecker.Init:output_type -> proto.Empty
|
||||
12, // 24: proto.Woodpecker.Wait:output_type -> proto.Empty
|
||||
12, // 25: proto.Woodpecker.Done:output_type -> proto.Empty
|
||||
12, // 26: proto.Woodpecker.Extend:output_type -> proto.Empty
|
||||
12, // 27: proto.Woodpecker.Update:output_type -> proto.Empty
|
||||
12, // 28: proto.Woodpecker.Log:output_type -> proto.Empty
|
||||
18, // 29: proto.Woodpecker.RegisterAgent:output_type -> proto.RegisterAgentResponse
|
||||
12, // 30: proto.Woodpecker.UnregisterAgent:output_type -> proto.Empty
|
||||
12, // 31: proto.Woodpecker.ReportHealth:output_type -> proto.Empty
|
||||
20, // 32: proto.WoodpeckerAuth.Auth:output_type -> proto.AuthResponse
|
||||
21, // [21:33] is the sub-list for method output_type
|
||||
9, // [9:21] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_woodpecker_proto_init() }
|
||||
|
@ -1549,7 +1620,7 @@ func file_woodpecker_proto_init() {
|
|||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RegisterAgentRequest); i {
|
||||
switch v := v.(*AgentInfo); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1561,7 +1632,7 @@ func file_woodpecker_proto_init() {
|
|||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*VersionResponse); i {
|
||||
switch v := v.(*RegisterAgentRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1573,7 +1644,7 @@ func file_woodpecker_proto_init() {
|
|||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*NextResponse); i {
|
||||
switch v := v.(*VersionResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1585,7 +1656,7 @@ func file_woodpecker_proto_init() {
|
|||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*RegisterAgentResponse); i {
|
||||
switch v := v.(*NextResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1597,7 +1668,7 @@ func file_woodpecker_proto_init() {
|
|||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AuthRequest); i {
|
||||
switch v := v.(*RegisterAgentResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1609,6 +1680,18 @@ func file_woodpecker_proto_init() {
|
|||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AuthRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_woodpecker_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*AuthResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1627,7 +1710,7 @@ func file_woodpecker_proto_init() {
|
|||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_woodpecker_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 21,
|
||||
NumMessages: 23,
|
||||
NumExtensions: 0,
|
||||
NumServices: 2,
|
||||
},
|
||||
|
|
|
@ -116,11 +116,16 @@ message ReportHealthRequest {
|
|||
string status = 1;
|
||||
}
|
||||
|
||||
message RegisterAgentRequest {
|
||||
message AgentInfo {
|
||||
string platform = 1;
|
||||
int32 capacity = 2;
|
||||
string backend = 3;
|
||||
string version = 4;
|
||||
map<string, string> customLabels = 5;
|
||||
}
|
||||
|
||||
message RegisterAgentRequest {
|
||||
AgentInfo info = 1;
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -15,12 +15,10 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
|
@ -28,6 +26,10 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
//
|
||||
// Global Agents.
|
||||
//
|
||||
|
||||
// GetAgents
|
||||
//
|
||||
// @Summary List agents
|
||||
|
@ -50,14 +52,14 @@ func GetAgents(c *gin.Context) {
|
|||
// GetAgent
|
||||
//
|
||||
// @Summary Get an agent
|
||||
// @Router /agents/{agent} [get]
|
||||
// @Router /agents/{agent_id} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param agent path int true "the agent's id"
|
||||
func GetAgent(c *gin.Context) {
|
||||
agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64)
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
|
@ -74,14 +76,14 @@ func GetAgent(c *gin.Context) {
|
|||
// GetAgentTasks
|
||||
//
|
||||
// @Summary List agent tasks
|
||||
// @Router /agents/{agent}/tasks [get]
|
||||
// @Router /agents/{agent_id}/tasks [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Task
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param agent path int true "the agent's id"
|
||||
func GetAgentTasks(c *gin.Context) {
|
||||
agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64)
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
|
@ -107,7 +109,7 @@ func GetAgentTasks(c *gin.Context) {
|
|||
// PatchAgent
|
||||
//
|
||||
// @Summary Update an agent
|
||||
// @Router /agents/{agent} [patch]
|
||||
// @Router /agents/{agent_id} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
// @Tags Agents
|
||||
|
@ -124,7 +126,7 @@ func PatchAgent(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64)
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
|
@ -135,6 +137,8 @@ func PatchAgent(c *gin.Context) {
|
|||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Update allowed fields
|
||||
agent.Name = in.Name
|
||||
agent.NoSchedule = in.NoSchedule
|
||||
if agent.NoSchedule {
|
||||
|
@ -172,11 +176,10 @@ func PostAgent(c *gin.Context) {
|
|||
|
||||
agent := &model.Agent{
|
||||
Name: in.Name,
|
||||
NoSchedule: in.NoSchedule,
|
||||
OwnerID: user.ID,
|
||||
Token: base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32),
|
||||
),
|
||||
OrgID: model.IDNotSet,
|
||||
NoSchedule: in.NoSchedule,
|
||||
Token: model.GenerateNewAgentToken(),
|
||||
}
|
||||
if err = store.FromContext(c).AgentCreate(agent); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
|
@ -188,7 +191,7 @@ func PostAgent(c *gin.Context) {
|
|||
// DeleteAgent
|
||||
//
|
||||
// @Summary Delete an agent
|
||||
// @Router /agents/{agent} [delete]
|
||||
// @Router /agents/{agent_id} [delete]
|
||||
// @Produce plain
|
||||
// @Success 200
|
||||
// @Tags Agents
|
||||
|
@ -197,7 +200,7 @@ func PostAgent(c *gin.Context) {
|
|||
func DeleteAgent(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64)
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
|
@ -227,3 +230,179 @@ func DeleteAgent(c *gin.Context) {
|
|||
}
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
//
|
||||
// Org/User Agents.
|
||||
//
|
||||
|
||||
// PostOrgAgent
|
||||
//
|
||||
// @Summary Create a new organization-scoped agent
|
||||
// @Description Creates a new agent with a random token, scoped to the specified organization
|
||||
// @Router /orgs/{org_id}/agents [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path int true "the organization's id"
|
||||
// @Param agent body Agent true "the agent's data (only 'name' and 'no_schedule' are read)"
|
||||
func PostOrgAgent(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
user := session.User(c)
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Invalid organization ID")
|
||||
return
|
||||
}
|
||||
|
||||
in := new(model.Agent)
|
||||
err = c.Bind(in)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
agent := &model.Agent{
|
||||
Name: in.Name,
|
||||
OwnerID: user.ID,
|
||||
OrgID: orgID,
|
||||
NoSchedule: in.NoSchedule,
|
||||
Token: model.GenerateNewAgentToken(),
|
||||
}
|
||||
|
||||
if err = _store.AgentCreate(agent); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agent)
|
||||
}
|
||||
|
||||
// GetOrgAgents
|
||||
//
|
||||
// @Summary List agents for an organization
|
||||
// @Router /orgs/{org_id}/agents [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Agent
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path int true "the organization's id"
|
||||
// @Param page query int false "for response pagination, page offset number" default(1)
|
||||
// @Param perPage query int false "for response pagination, max items per page" default(50)
|
||||
func GetOrgAgents(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
org := session.Org(c)
|
||||
|
||||
agents, err := _store.AgentListForOrg(org.ID, session.Pagination(c))
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting agent list. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agents)
|
||||
}
|
||||
|
||||
// PatchOrgAgent
|
||||
//
|
||||
// @Summary Update an organization-scoped agent
|
||||
// @Router /orgs/{org_id}/agents/{agent_id} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path int true "the organization's id"
|
||||
// @Param agent_id path int true "the agent's id"
|
||||
// @Param agent body Agent true "the agent's updated data"
|
||||
func PatchOrgAgent(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
org := session.Org(c)
|
||||
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Invalid agent ID")
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := _store.AgentFind(agentID)
|
||||
if err != nil {
|
||||
c.String(http.StatusNotFound, "Agent not found")
|
||||
return
|
||||
}
|
||||
|
||||
if agent.OrgID != org.ID {
|
||||
c.String(http.StatusBadRequest, "Agent does not belong to this organization")
|
||||
return
|
||||
}
|
||||
|
||||
in := new(model.Agent)
|
||||
if err := c.Bind(in); err != nil {
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Update allowed fields
|
||||
agent.Name = in.Name
|
||||
agent.NoSchedule = in.NoSchedule
|
||||
if agent.NoSchedule {
|
||||
server.Config.Services.Queue.KickAgentWorkers(agent.ID)
|
||||
}
|
||||
|
||||
if err := _store.AgentUpdate(agent); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agent)
|
||||
}
|
||||
|
||||
// DeleteOrgAgent
|
||||
//
|
||||
// @Summary Delete an organization-scoped agent
|
||||
// @Router /orgs/{org_id}/agents/{agent_id} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path int true "the organization's id"
|
||||
// @Param agent_id path int true "the agent's id"
|
||||
func DeleteOrgAgent(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
org := session.Org(c)
|
||||
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Invalid agent ID")
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := _store.AgentFind(agentID)
|
||||
if err != nil {
|
||||
c.String(http.StatusNotFound, "Agent not found")
|
||||
return
|
||||
}
|
||||
|
||||
if agent.OrgID != org.ID {
|
||||
c.String(http.StatusBadRequest, "Agent does not belong to this organization")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the agent has any running tasks
|
||||
info := server.Config.Services.Queue.Info(c)
|
||||
for _, task := range info.Running {
|
||||
if task.AgentID == agent.ID {
|
||||
c.String(http.StatusConflict, "Agent has running tasks")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Kick workers to remove the agent from the queue
|
||||
server.Config.Services.Queue.KickAgentWorkers(agent.ID)
|
||||
|
||||
if err := _store.AgentDelete(agent); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error deleting agent. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ package api
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -58,20 +57,7 @@ func GetOrgs(c *gin.Context) {
|
|||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path string true "the organization's id"
|
||||
func GetOrg(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
org, err := _store.OrgGet(orgID)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
org := session.Org(c)
|
||||
c.JSON(http.StatusOK, org)
|
||||
}
|
||||
|
||||
|
@ -86,7 +72,7 @@ func GetOrg(c *gin.Context) {
|
|||
// @Param org_id path string true "the organization's id"
|
||||
func GetOrgPermissions(c *gin.Context) {
|
||||
user := session.User(c)
|
||||
_store := store.FromContext(c)
|
||||
org := session.Org(c)
|
||||
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromUser(user)
|
||||
if err != nil {
|
||||
|
@ -95,23 +81,11 @@ func GetOrgPermissions(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
c.JSON(http.StatusOK, &model.OrgPerm{})
|
||||
return
|
||||
}
|
||||
|
||||
org, err := _store.OrgGet(orgID)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting org %d. %s", orgID, err)
|
||||
return
|
||||
}
|
||||
|
||||
if (org.IsUser && org.Name == user.Login) || (user.Admin && !org.IsUser) {
|
||||
c.JSON(http.StatusOK, &model.OrgPerm{
|
||||
Member: true,
|
||||
|
@ -125,7 +99,7 @@ func GetOrgPermissions(c *gin.Context) {
|
|||
|
||||
perm, err := server.Config.Services.Membership.Get(c, _forge, user, org.Name)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting membership for %d. %s", orgID, err)
|
||||
c.String(http.StatusInternalServerError, "Error getting membership for %d. %s", org.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -200,15 +174,9 @@ func LookupOrg(c *gin.Context) {
|
|||
// @Param id path string true "the org's id"
|
||||
func DeleteOrg(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
org := session.Org(c)
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = _store.OrgDelete(orgID)
|
||||
if err != nil {
|
||||
if err := _store.OrgDelete(org.ID); err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ package api
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
|
@ -36,16 +35,11 @@ import (
|
|||
// @Param org_id path string true "the org's id"
|
||||
// @Param registry path string true "the registry's address"
|
||||
func GetOrgRegistry(c *gin.Context) {
|
||||
org := session.Org(c)
|
||||
addr := c.Param("registry")
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
registry, err := registryService.OrgRegistryFind(orgID, addr)
|
||||
registry, err := registryService.OrgRegistryFind(org.ID, addr)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
|
@ -65,16 +59,12 @@ func GetOrgRegistry(c *gin.Context) {
|
|||
// @Param page query int false "for response pagination, page offset number" default(1)
|
||||
// @Param perPage query int false "for response pagination, max items per page" default(50)
|
||||
func GetOrgRegistryList(c *gin.Context) {
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
org := session.Org(c)
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
list, err := registryService.OrgRegistryList(orgID, session.Pagination(c))
|
||||
list, err := registryService.OrgRegistryList(org.ID, session.Pagination(c))
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting registry list for %q. %s", orgID, err)
|
||||
c.String(http.StatusInternalServerError, "Error getting registry list for %q. %s", org.ID, err)
|
||||
return
|
||||
}
|
||||
// copy the registry detail to remove the sensitive
|
||||
|
@ -96,31 +86,27 @@ func GetOrgRegistryList(c *gin.Context) {
|
|||
// @Param org_id path string true "the org's id"
|
||||
// @Param registryData body Registry true "the new registry"
|
||||
func PostOrgRegistry(c *gin.Context) {
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
org := session.Org(c)
|
||||
|
||||
in := new(model.Registry)
|
||||
if err := c.Bind(in); err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org %q registry. %s", orgID, err)
|
||||
c.String(http.StatusBadRequest, "Error parsing org %q registry. %s", org.ID, err)
|
||||
return
|
||||
}
|
||||
registry := &model.Registry{
|
||||
OrgID: orgID,
|
||||
OrgID: org.ID,
|
||||
Address: in.Address,
|
||||
Username: in.Username,
|
||||
Password: in.Password,
|
||||
}
|
||||
if err := registry.Validate(); err != nil {
|
||||
c.String(http.StatusUnprocessableEntity, "Error inserting org %q registry. %s", orgID, err)
|
||||
c.String(http.StatusUnprocessableEntity, "Error inserting org %q registry. %s", org.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
if err := registryService.OrgRegistryCreate(orgID, registry); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error inserting org %q registry %q. %s", orgID, in.Address, err)
|
||||
if err := registryService.OrgRegistryCreate(org.ID, registry); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error inserting org %q registry %q. %s", org.ID, in.Address, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, registry.Copy())
|
||||
|
@ -138,22 +124,17 @@ func PostOrgRegistry(c *gin.Context) {
|
|||
// @Param registry path string true "the registry's name"
|
||||
// @Param registryData body Registry true "the update registry data"
|
||||
func PatchOrgRegistry(c *gin.Context) {
|
||||
org := session.Org(c)
|
||||
addr := c.Param("registry")
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
in := new(model.Registry)
|
||||
err = c.Bind(in)
|
||||
if err != nil {
|
||||
if err := c.Bind(in); err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing registry. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
registry, err := registryService.OrgRegistryFind(orgID, addr)
|
||||
registry, err := registryService.OrgRegistryFind(org.ID, addr)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
|
@ -169,12 +150,12 @@ func PatchOrgRegistry(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := registry.Validate(); err != nil {
|
||||
c.String(http.StatusUnprocessableEntity, "Error updating org %q registry. %s", orgID, err)
|
||||
c.String(http.StatusUnprocessableEntity, "Error updating org %q registry. %s", org.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := registryService.OrgRegistryUpdate(orgID, registry); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error updating org %q registry %q. %s", orgID, in.Address, err)
|
||||
if err := registryService.OrgRegistryUpdate(org.ID, registry); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error updating org %q registry %q. %s", org.ID, in.Address, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, registry.Copy())
|
||||
|
@ -191,15 +172,11 @@ func PatchOrgRegistry(c *gin.Context) {
|
|||
// @Param org_id path string true "the org's id"
|
||||
// @Param registry path string true "the registry's name"
|
||||
func DeleteOrgRegistry(c *gin.Context) {
|
||||
org := session.Org(c)
|
||||
addr := c.Param("registry")
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
registryService := server.Config.Services.Manager.RegistryService()
|
||||
if err := registryService.OrgRegistryDelete(orgID, addr); err != nil {
|
||||
if err := registryService.OrgRegistryDelete(org.ID, addr); err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ package api
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
|
@ -36,16 +35,11 @@ import (
|
|||
// @Param org_id path string true "the org's id"
|
||||
// @Param secret path string true "the secret's name"
|
||||
func GetOrgSecret(c *gin.Context) {
|
||||
org := session.Org(c)
|
||||
name := c.Param("secret")
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
secretService := server.Config.Services.Manager.SecretService()
|
||||
secret, err := secretService.OrgSecretFind(orgID, name)
|
||||
secret, err := secretService.OrgSecretFind(org.ID, name)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
|
@ -65,16 +59,12 @@ func GetOrgSecret(c *gin.Context) {
|
|||
// @Param page query int false "for response pagination, page offset number" default(1)
|
||||
// @Param perPage query int false "for response pagination, max items per page" default(50)
|
||||
func GetOrgSecretList(c *gin.Context) {
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
org := session.Org(c)
|
||||
|
||||
secretService := server.Config.Services.Manager.SecretService()
|
||||
list, err := secretService.OrgSecretList(orgID, session.Pagination(c))
|
||||
list, err := secretService.OrgSecretList(org.ID, session.Pagination(c))
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting secret list for %q. %s", orgID, err)
|
||||
c.String(http.StatusInternalServerError, "Error getting secret list for %q. %s", org.ID, err)
|
||||
return
|
||||
}
|
||||
// copy the secret detail to remove the sensitive
|
||||
|
@ -96,32 +86,28 @@ func GetOrgSecretList(c *gin.Context) {
|
|||
// @Param org_id path string true "the org's id"
|
||||
// @Param secretData body Secret true "the new secret"
|
||||
func PostOrgSecret(c *gin.Context) {
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
org := session.Org(c)
|
||||
|
||||
in := new(model.Secret)
|
||||
if err := c.Bind(in); err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org %q secret. %s", orgID, err)
|
||||
c.String(http.StatusBadRequest, "Error parsing org %q secret. %s", org.ID, err)
|
||||
return
|
||||
}
|
||||
secret := &model.Secret{
|
||||
OrgID: orgID,
|
||||
OrgID: org.ID,
|
||||
Name: in.Name,
|
||||
Value: in.Value,
|
||||
Events: in.Events,
|
||||
Images: in.Images,
|
||||
}
|
||||
if err := secret.Validate(); err != nil {
|
||||
c.String(http.StatusUnprocessableEntity, "Error inserting org %q secret. %s", orgID, err)
|
||||
c.String(http.StatusUnprocessableEntity, "Error inserting org %q secret. %s", org.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
secretService := server.Config.Services.Manager.SecretService()
|
||||
if err := secretService.OrgSecretCreate(orgID, secret); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error inserting org %q secret %q. %s", orgID, in.Name, err)
|
||||
if err := secretService.OrgSecretCreate(org.ID, secret); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error inserting org %q secret %q. %s", org.ID, in.Name, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, secret.Copy())
|
||||
|
@ -139,22 +125,17 @@ func PostOrgSecret(c *gin.Context) {
|
|||
// @Param secret path string true "the secret's name"
|
||||
// @Param secretData body Secret true "the update secret data"
|
||||
func PatchOrgSecret(c *gin.Context) {
|
||||
org := session.Org(c)
|
||||
name := c.Param("secret")
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
in := new(model.Secret)
|
||||
err = c.Bind(in)
|
||||
if err != nil {
|
||||
if err := c.Bind(in); err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing secret. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
secretService := server.Config.Services.Manager.SecretService()
|
||||
secret, err := secretService.OrgSecretFind(orgID, name)
|
||||
secret, err := secretService.OrgSecretFind(org.ID, name)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
|
@ -170,12 +151,12 @@ func PatchOrgSecret(c *gin.Context) {
|
|||
}
|
||||
|
||||
if err := secret.Validate(); err != nil {
|
||||
c.String(http.StatusUnprocessableEntity, "Error updating org %q secret. %s", orgID, err)
|
||||
c.String(http.StatusUnprocessableEntity, "Error updating org %q secret. %s", org.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := secretService.OrgSecretUpdate(orgID, secret); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error updating org %q secret %q. %s", orgID, in.Name, err)
|
||||
if err := secretService.OrgSecretUpdate(org.ID, secret); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error updating org %q secret %q. %s", org.ID, in.Name, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, secret.Copy())
|
||||
|
@ -192,15 +173,11 @@ func PatchOrgSecret(c *gin.Context) {
|
|||
// @Param org_id path string true "the org's id"
|
||||
// @Param secret path string true "the secret's name"
|
||||
func DeleteOrgSecret(c *gin.Context) {
|
||||
org := session.Org(c)
|
||||
name := c.Param("secret")
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
secretService := server.Config.Services.Manager.SecretService()
|
||||
if err := secretService.OrgSecretDelete(orgID, name); err != nil {
|
||||
if err := secretService.OrgSecretDelete(org.ID, name); err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -30,8 +30,10 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/pipeline"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store/types"
|
||||
)
|
||||
|
||||
// CreatePipeline
|
||||
|
@ -392,6 +394,47 @@ func GetPipelineConfig(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, configs)
|
||||
}
|
||||
|
||||
// GetPipelineMetadata
|
||||
//
|
||||
// @Summary Get metadata for a pipeline or a specific workflow, including previous pipeline info
|
||||
// @Router /repos/{repo_id}/pipelines/{number}/metadata [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} metadata.Metadata
|
||||
// @Tags Pipelines
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param repo_id path int true "the repository id"
|
||||
// @Param number path int true "the number of the pipeline"
|
||||
func GetPipelineMetadata(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
num, err := strconv.ParseInt(c.Param("number"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
_store := store.FromContext(c)
|
||||
currentPipeline, err := _store.GetPipelineNumber(repo, num)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
prevPipeline, err := _store.GetPipelineLastBefore(repo, currentPipeline.Branch, currentPipeline.ID)
|
||||
if err != nil && !errors.Is(err, types.RecordNotExist) {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
metadata := stepbuilder.MetadataFromStruct(forge, repo, currentPipeline, prevPipeline, nil, server.Config.Server.Host)
|
||||
c.JSON(http.StatusOK, metadata)
|
||||
}
|
||||
|
||||
// CancelPipeline
|
||||
//
|
||||
// @Summary Cancel a pipeline
|
||||
|
|
|
@ -1,129 +1,217 @@
|
|||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
forge_mocks "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
|
||||
mocks_manager "go.woodpecker-ci.org/woodpecker/v2/server/services/mocks"
|
||||
store_mocks "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store/types"
|
||||
)
|
||||
|
||||
var fakePipeline = &model.Pipeline{
|
||||
ID: 2,
|
||||
Number: 2,
|
||||
Status: model.StatusSuccess,
|
||||
}
|
||||
|
||||
func TestGetPipelines(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Pipeline", func() {
|
||||
g.It("should get pipelines", func() {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
t.Run("should get pipelines", func(t *testing.T) {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil)
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Set("store", mockStore)
|
||||
|
||||
GetPipelines(c)
|
||||
GetPipelines(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineList", mock.Anything, mock.Anything, mock.Anything)
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
mockStore.AssertCalled(t, "GetPipelineList", mock.Anything, mock.Anything, mock.Anything)
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should not parse pipeline filter", func() {
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest(http.MethodDelete, "/?before=2023-01-16&after=2023-01-15", nil)
|
||||
t.Run("should not parse pipeline filter", func(t *testing.T) {
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Request, _ = http.NewRequest(http.MethodDelete, "/?before=2023-01-16&after=2023-01-15", nil)
|
||||
|
||||
GetPipelines(c)
|
||||
GetPipelines(c)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, c.Writer.Status())
|
||||
})
|
||||
assert.Equal(t, http.StatusBadRequest, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should parse pipeline filter", func() {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
t.Run("should parse pipeline filter", func(t *testing.T) {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil)
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Request, _ = http.NewRequest(http.MethodDelete, "/?2023-01-16T15:00:00Z&after=2023-01-15T15:00:00Z", nil)
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Request, _ = http.NewRequest(http.MethodDelete, "/?2023-01-16T15:00:00Z&after=2023-01-15T15:00:00Z", nil)
|
||||
|
||||
GetPipelines(c)
|
||||
GetPipelines(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should parse pipeline filter with tz offset", func() {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
t.Run("should parse pipeline filter with tz offset", func(t *testing.T) {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil)
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Request, _ = http.NewRequest(http.MethodDelete, "/?before=2023-01-16T15:00:00%2B01:00&after=2023-01-15T15:00:00%2B01:00", nil)
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Request, _ = http.NewRequest(http.MethodDelete, "/?before=2023-01-16T15:00:00%2B01:00&after=2023-01-15T15:00:00%2B01:00", nil)
|
||||
|
||||
GetPipelines(c)
|
||||
GetPipelines(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeletePipeline(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Pipeline", func() {
|
||||
g.It("should delete pipeline", func() {
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(fakePipeline, nil)
|
||||
mockStore.On("DeletePipeline", mock.Anything).Return(nil)
|
||||
t.Run("should delete pipeline", func(t *testing.T) {
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(fakePipeline, nil)
|
||||
mockStore.On("DeletePipeline", mock.Anything).Return(nil)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "number", Value: "2"}}
|
||||
|
||||
DeletePipeline(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything)
|
||||
mockStore.AssertCalled(t, "DeletePipeline", mock.Anything)
|
||||
assert.Equal(t, http.StatusNoContent, c.Writer.Status())
|
||||
})
|
||||
|
||||
t.Run("should not delete without pipeline number", func(t *testing.T) {
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
|
||||
DeletePipeline(c)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, c.Writer.Status())
|
||||
})
|
||||
|
||||
t.Run("should not delete pending", func(t *testing.T) {
|
||||
fakePipeline := *fakePipeline
|
||||
fakePipeline.Status = model.StatusPending
|
||||
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(&fakePipeline, nil)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "number", Value: "2"}}
|
||||
|
||||
DeletePipeline(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything)
|
||||
mockStore.AssertNotCalled(t, "DeletePipeline", mock.Anything)
|
||||
assert.Equal(t, http.StatusUnprocessableEntity, c.Writer.Status())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetPipelineMetadata(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
prevPipeline := &model.Pipeline{
|
||||
ID: 1,
|
||||
Number: 1,
|
||||
Status: model.StatusFailure,
|
||||
}
|
||||
|
||||
fakeRepo := &model.Repo{ID: 1}
|
||||
|
||||
mockForge := forge_mocks.NewForge(t)
|
||||
mockForge.On("Name").Return("mock")
|
||||
mockForge.On("URL").Return("https://codeberg.org")
|
||||
|
||||
mockManager := mocks_manager.NewManager(t)
|
||||
mockManager.On("ForgeFromRepo", fakeRepo).Return(mockForge, nil)
|
||||
server.Config.Services.Manager = mockManager
|
||||
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineNumber", mock.Anything, int64(2)).Return(fakePipeline, nil)
|
||||
mockStore.On("GetPipelineLastBefore", mock.Anything, mock.Anything, int64(2)).Return(prevPipeline, nil)
|
||||
|
||||
t.Run("PipelineMetadata", func(t *testing.T) {
|
||||
t.Run("should get pipeline metadata", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Params = gin.Params{{Key: "number", Value: "2"}}
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "number", Value: "1"}}
|
||||
c.Set("forge", mockForge)
|
||||
c.Set("repo", fakeRepo)
|
||||
|
||||
DeletePipeline(c)
|
||||
GetPipelineMetadata(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything)
|
||||
mockStore.AssertCalled(t, "DeletePipeline", mock.Anything)
|
||||
assert.Equal(t, http.StatusNoContent, c.Writer.Status())
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
var response metadata.Metadata
|
||||
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, int64(1), response.Repo.ID)
|
||||
assert.Equal(t, int64(2), response.Curr.Number)
|
||||
assert.Equal(t, int64(1), response.Prev.Number)
|
||||
})
|
||||
|
||||
g.It("should not delete without pipeline number", func() {
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
t.Run("should return bad request for invalid pipeline number", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Params = gin.Params{{Key: "number", Value: "invalid"}}
|
||||
|
||||
DeletePipeline(c)
|
||||
GetPipelineMetadata(c)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, c.Writer.Status())
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
})
|
||||
|
||||
g.It("should not delete pending", func() {
|
||||
fakePipeline.Status = model.StatusPending
|
||||
t.Run("should return not found for non-existent pipeline", func(t *testing.T) {
|
||||
mockStore := store_mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineNumber", mock.Anything, int64(3)).Return((*model.Pipeline)(nil), types.RecordNotExist)
|
||||
|
||||
mockStore := mocks.NewStore(t)
|
||||
mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(fakePipeline, nil)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
c.Params = gin.Params{{Key: "number", Value: "3"}}
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "number", Value: "1"}}
|
||||
c.Set("repo", fakeRepo)
|
||||
|
||||
DeletePipeline(c)
|
||||
GetPipelineMetadata(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything)
|
||||
mockStore.AssertNotCalled(t, "DeletePipeline", mock.Anything)
|
||||
assert.Equal(t, http.StatusUnprocessableEntity, c.Writer.Status())
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -66,7 +66,6 @@ var Config = struct {
|
|||
DefaultCancelPreviousPipelineEvents []model.WebhookEvent
|
||||
DefaultClonePlugin string
|
||||
TrustedClonePlugins []string
|
||||
Limits model.ResourceLimit
|
||||
Volumes []string
|
||||
Networks []string
|
||||
PrivilegedPlugins []string
|
||||
|
|
|
@ -18,7 +18,7 @@ package github
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-github/v64/github"
|
||||
"github.com/google/go-github/v66/github"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/google/go-github/v64/github"
|
||||
"github.com/google/go-github/v66/github"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
|
|
@ -27,7 +27,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v64/github"
|
||||
"github.com/google/go-github/v66/github"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v64/github"
|
||||
"github.com/google/go-github/v66/github"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
|
|
|
@ -303,7 +303,9 @@ func extractFromPath(str string) (string, string, error) {
|
|||
if len(s) < minPathComponents {
|
||||
return "", "", fmt.Errorf("minimum match not found")
|
||||
}
|
||||
return s[0], s[1], nil
|
||||
owner := strings.Join(s[:len(s)-1], "/")
|
||||
name := s[len(s)-1]
|
||||
return owner, name, nil
|
||||
}
|
||||
|
||||
func convertLabels(from []*gitlab.EventLabel) []string {
|
||||
|
|
|
@ -300,3 +300,68 @@ func Test_GitLab(t *testing.T) {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtractFromPath(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
input string
|
||||
wantOwner string
|
||||
wantName string
|
||||
errContains string
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
name: "basic two components",
|
||||
input: "owner/repo",
|
||||
wantOwner: "owner",
|
||||
wantName: "repo",
|
||||
},
|
||||
{
|
||||
name: "three components",
|
||||
input: "owner/group/repo",
|
||||
wantOwner: "owner/group",
|
||||
wantName: "repo",
|
||||
},
|
||||
{
|
||||
name: "many components",
|
||||
input: "owner/group/subgroup/deep/repo",
|
||||
wantOwner: "owner/group/subgroup/deep",
|
||||
wantName: "repo",
|
||||
},
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
errContains: "minimum match not found",
|
||||
},
|
||||
{
|
||||
name: "single component",
|
||||
input: "onlyrepo",
|
||||
errContains: "minimum match not found",
|
||||
},
|
||||
{
|
||||
name: "trailing slash",
|
||||
input: "owner/repo/",
|
||||
wantOwner: "owner/repo",
|
||||
wantName: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
owner, name, err := extractFromPath(tc.input)
|
||||
|
||||
// Check error expectations
|
||||
if tc.errContains != "" {
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), tc.errContains)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, tc.wantOwner, owner)
|
||||
assert.EqualValues(t, tc.wantName, name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,13 +60,12 @@ func (s *WoodpeckerAuthServer) getAgent(agentID int64, agentToken string) (*mode
|
|||
// global agent secret auth
|
||||
if s.agentMasterToken != "" {
|
||||
if agentToken == s.agentMasterToken && agentID == -1 {
|
||||
agent := new(model.Agent)
|
||||
agent.Name = ""
|
||||
agent.OwnerID = -1 // system agent
|
||||
agent.Token = s.agentMasterToken
|
||||
agent.Backend = ""
|
||||
agent.Platform = ""
|
||||
agent.Capacity = -1
|
||||
agent := &model.Agent{
|
||||
OwnerID: model.IDNotSet,
|
||||
OrgID: model.IDNotSet,
|
||||
Token: s.agentMasterToken,
|
||||
Capacity: -1,
|
||||
}
|
||||
err := s.store.AgentCreate(agent)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("error creating system agent")
|
||||
|
|
|
@ -21,28 +21,32 @@ import (
|
|||
)
|
||||
|
||||
func createFilterFunc(agentFilter rpc.Filter) queue.FilterFn {
|
||||
return func(task *model.Task) bool {
|
||||
return func(task *model.Task) (bool, int) {
|
||||
score := 0
|
||||
for taskLabel, taskLabelValue := range task.Labels {
|
||||
// if a task label is empty it will be ignored
|
||||
if taskLabelValue == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// all task labels are required to be present for an agent to match
|
||||
agentLabelValue, ok := agentFilter.Labels[taskLabel]
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
return false, 0
|
||||
}
|
||||
|
||||
switch {
|
||||
// if agent label has a wildcard
|
||||
if agentLabelValue == "*" {
|
||||
continue
|
||||
}
|
||||
|
||||
if taskLabelValue != agentLabelValue {
|
||||
return false
|
||||
case agentLabelValue == "*":
|
||||
score++
|
||||
// if agent label has an exact match
|
||||
case agentLabelValue == taskLabelValue:
|
||||
score += 10
|
||||
// agent doesn't match
|
||||
default:
|
||||
return false, 0
|
||||
}
|
||||
}
|
||||
return true
|
||||
return true, score
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,68 +24,110 @@ import (
|
|||
)
|
||||
|
||||
func TestCreateFilterFunc(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
agentLabels map[string]string
|
||||
task model.Task
|
||||
exp bool
|
||||
agentFilter rpc.Filter
|
||||
task *model.Task
|
||||
wantMatched bool
|
||||
wantScore int
|
||||
}{
|
||||
{
|
||||
name: "agent with missing labels",
|
||||
agentLabels: map[string]string{"repo": "test/woodpecker"},
|
||||
task: model.Task{
|
||||
Labels: map[string]string{"platform": "linux/amd64", "repo": "test/woodpecker"},
|
||||
name: "Two exact matches",
|
||||
agentFilter: rpc.Filter{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "linux"},
|
||||
},
|
||||
exp: false,
|
||||
task: &model.Task{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "linux"},
|
||||
},
|
||||
wantMatched: true,
|
||||
wantScore: 20,
|
||||
},
|
||||
{
|
||||
name: "agent with wrong labels",
|
||||
agentLabels: map[string]string{"platform": "linux/arm64"},
|
||||
task: model.Task{
|
||||
Labels: map[string]string{"platform": "linux/amd64"},
|
||||
name: "Wildcard and exact match",
|
||||
agentFilter: rpc.Filter{
|
||||
Labels: map[string]string{"org-id": "*", "platform": "linux"},
|
||||
},
|
||||
exp: false,
|
||||
task: &model.Task{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "linux"},
|
||||
},
|
||||
wantMatched: true,
|
||||
wantScore: 11,
|
||||
},
|
||||
{
|
||||
name: "agent with correct labels",
|
||||
agentLabels: map[string]string{"platform": "linux/amd64", "location": "europe"},
|
||||
task: model.Task{
|
||||
Labels: map[string]string{"platform": "linux/amd64", "location": "europe"},
|
||||
name: "Partial match",
|
||||
agentFilter: rpc.Filter{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "linux"},
|
||||
},
|
||||
exp: true,
|
||||
task: &model.Task{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "windows"},
|
||||
},
|
||||
wantMatched: false,
|
||||
wantScore: 0,
|
||||
},
|
||||
{
|
||||
name: "agent with additional labels",
|
||||
agentLabels: map[string]string{"platform": "linux/amd64", "location": "europe"},
|
||||
task: model.Task{
|
||||
Labels: map[string]string{"platform": "linux/amd64"},
|
||||
name: "No match",
|
||||
agentFilter: rpc.Filter{
|
||||
Labels: map[string]string{"org-id": "456", "platform": "linux"},
|
||||
},
|
||||
exp: true,
|
||||
task: &model.Task{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "windows"},
|
||||
},
|
||||
wantMatched: false,
|
||||
wantScore: 0,
|
||||
},
|
||||
{
|
||||
name: "agent with wildcard label",
|
||||
agentLabels: map[string]string{"platform": "linux/amd64", "location": "*"},
|
||||
task: model.Task{
|
||||
Labels: map[string]string{"platform": "linux/amd64", "location": "america"},
|
||||
name: "Missing label",
|
||||
agentFilter: rpc.Filter{
|
||||
Labels: map[string]string{"platform": "linux"},
|
||||
},
|
||||
exp: true,
|
||||
task: &model.Task{
|
||||
Labels: map[string]string{"needed": "some"},
|
||||
},
|
||||
wantMatched: false,
|
||||
wantScore: 0,
|
||||
},
|
||||
{
|
||||
name: "agent with platform label and task without",
|
||||
agentLabels: map[string]string{"platform": "linux/amd64"},
|
||||
task: model.Task{
|
||||
Labels: map[string]string{"platform": ""},
|
||||
name: "Empty task labels",
|
||||
agentFilter: rpc.Filter{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "linux"},
|
||||
},
|
||||
exp: true,
|
||||
task: &model.Task{
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
wantMatched: true,
|
||||
wantScore: 0,
|
||||
},
|
||||
{
|
||||
name: "Agent with additional label",
|
||||
agentFilter: rpc.Filter{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "linux", "extra": "value"},
|
||||
},
|
||||
task: &model.Task{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "linux", "empty": ""},
|
||||
},
|
||||
wantMatched: true,
|
||||
wantScore: 20,
|
||||
},
|
||||
{
|
||||
name: "Two wildcard matches",
|
||||
agentFilter: rpc.Filter{
|
||||
Labels: map[string]string{"org-id": "*", "platform": "*"},
|
||||
},
|
||||
task: &model.Task{
|
||||
Labels: map[string]string{"org-id": "123", "platform": "linux"},
|
||||
},
|
||||
wantMatched: true,
|
||||
wantScore: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
fn := createFilterFunc(rpc.Filter{Labels: test.agentLabels})
|
||||
assert.EqualValues(t, test.exp, fn(&test.task))
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filterFunc := createFilterFunc(tt.agentFilter)
|
||||
gotMatched, gotScore := filterFunc(tt.task)
|
||||
|
||||
assert.Equal(t, tt.wantMatched, gotMatched, "Matched result")
|
||||
assert.Equal(t, tt.wantScore, gotScore, "Score")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,8 +57,6 @@ func (s *RPC) Next(c context.Context, agentFilter rpc.Filter) (*rpc.Workflow, er
|
|||
log.Debug().Msgf("agent connected: %s: polling", hostname)
|
||||
}
|
||||
|
||||
filterFn := createFilterFunc(agentFilter)
|
||||
|
||||
agent, err := s.getAgentFromContext(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -69,6 +67,20 @@ func (s *RPC) Next(c context.Context, agentFilter rpc.Filter) (*rpc.Workflow, er
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
agentServerLabels, err := agent.GetServerLabels()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// enforce labels from server by overwriting agent labels
|
||||
for k, v := range agentServerLabels {
|
||||
agentFilter.Labels[k] = v
|
||||
}
|
||||
|
||||
log.Trace().Msgf("Agent %s[%d] tries to pull task with labels: %v", agent.Name, agent.ID, agentFilter.Labels)
|
||||
|
||||
filterFn := createFilterFunc(agentFilter)
|
||||
|
||||
for {
|
||||
// poll blocks until a task is available or the context is canceled / worker is kicked
|
||||
task, err := s.queue.Poll(c, agent.ID, filterFn)
|
||||
|
@ -91,6 +103,15 @@ func (s *RPC) Next(c context.Context, agentFilter rpc.Filter) (*rpc.Workflow, er
|
|||
|
||||
// Wait blocks until the workflow with the given ID is done.
|
||||
func (s *RPC) Wait(c context.Context, workflowID string) error {
|
||||
agent, err := s.getAgentFromContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.checkAgentPermissionByWorkflow(c, agent, workflowID, nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.queue.Wait(c, workflowID)
|
||||
}
|
||||
|
||||
|
@ -106,11 +127,15 @@ func (s *RPC) Extend(c context.Context, workflowID string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
return s.queue.Extend(c, workflowID)
|
||||
if err := s.checkAgentPermissionByWorkflow(c, agent, workflowID, nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.queue.Extend(c, agent.ID, workflowID)
|
||||
}
|
||||
|
||||
// Update updates the state of a step.
|
||||
func (s *RPC) Update(_ context.Context, strWorkflowID string, state rpc.StepState) error {
|
||||
func (s *RPC) Update(c context.Context, strWorkflowID string, state rpc.StepState) error {
|
||||
workflowID, err := strconv.ParseInt(strWorkflowID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -128,6 +153,11 @@ func (s *RPC) Update(_ context.Context, strWorkflowID string, state rpc.StepStat
|
|||
return err
|
||||
}
|
||||
|
||||
agent, err := s.getAgentFromContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
step, err := s.store.StepByUUID(state.StepUUID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("cannot find step with uuid %s", state.StepUUID)
|
||||
|
@ -149,6 +179,11 @@ func (s *RPC) Update(_ context.Context, strWorkflowID string, state rpc.StepStat
|
|||
return err
|
||||
}
|
||||
|
||||
// check before agent can alter some state
|
||||
if err := s.checkAgentPermissionByWorkflow(c, agent, strWorkflowID, currentPipeline, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pipeline.UpdateStepStatus(s.store, step, state); err != nil {
|
||||
log.Error().Err(err).Msg("rpc.update: cannot update step")
|
||||
}
|
||||
|
@ -192,6 +227,7 @@ func (s *RPC) Init(c context.Context, strWorkflowID string, state rpc.WorkflowSt
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workflow.AgentID = agent.ID
|
||||
|
||||
currentPipeline, err := s.store.GetPipeline(workflow.PipelineID)
|
||||
|
@ -206,6 +242,11 @@ func (s *RPC) Init(c context.Context, strWorkflowID string, state rpc.WorkflowSt
|
|||
return err
|
||||
}
|
||||
|
||||
// check before agent can alter some state
|
||||
if err := s.checkAgentPermissionByWorkflow(c, agent, strWorkflowID, currentPipeline, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentPipeline.Status == model.StatusPending {
|
||||
if currentPipeline, err = pipeline.UpdateToStatusRunning(s.store, *currentPipeline, state.Started); err != nil {
|
||||
log.Error().Err(err).Msgf("init: cannot update pipeline %d state", currentPipeline.ID)
|
||||
|
@ -272,6 +313,16 @@ func (s *RPC) Done(c context.Context, strWorkflowID string, state rpc.WorkflowSt
|
|||
return err
|
||||
}
|
||||
|
||||
agent, err := s.getAgentFromContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check before agent can alter some state
|
||||
if err := s.checkAgentPermissionByWorkflow(c, agent, strWorkflowID, currentPipeline, repo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger := log.With().
|
||||
Str("repo_id", fmt.Sprint(repo.ID)).
|
||||
Str("pipeline_id", fmt.Sprint(currentPipeline.ID)).
|
||||
|
@ -328,10 +379,6 @@ func (s *RPC) Done(c context.Context, strWorkflowID string, state rpc.WorkflowSt
|
|||
s.pipelineTime.WithLabelValues(repo.FullName, currentPipeline.Branch, string(workflow.State), workflow.Name).Set(float64(workflow.Finished - workflow.Started))
|
||||
}
|
||||
|
||||
agent, err := s.getAgentFromContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.updateAgentLastWork(agent)
|
||||
}
|
||||
|
||||
|
@ -348,6 +395,17 @@ func (s *RPC) Log(c context.Context, stepUUID string, rpcLogEntries []*rpc.LogEn
|
|||
return err
|
||||
}
|
||||
|
||||
currentPipeline, err := s.store.GetPipeline(step.PipelineID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("cannot find pipeline with id %d", step.PipelineID)
|
||||
return err
|
||||
}
|
||||
|
||||
// check before agent can alter some state
|
||||
if err := s.checkAgentPermissionByWorkflow(c, agent, "", currentPipeline, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.updateAgentLastWork(agent)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -383,7 +441,7 @@ func (s *RPC) Log(c context.Context, stepUUID string, rpcLogEntries []*rpc.LogEn
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *RPC) RegisterAgent(ctx context.Context, platform, backend, version string, capacity int32) (int64, error) {
|
||||
func (s *RPC) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) {
|
||||
agent, err := s.getAgentFromContext(ctx)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
|
@ -395,10 +453,11 @@ func (s *RPC) RegisterAgent(ctx context.Context, platform, backend, version stri
|
|||
}
|
||||
}
|
||||
|
||||
agent.Backend = backend
|
||||
agent.Platform = platform
|
||||
agent.Capacity = capacity
|
||||
agent.Version = version
|
||||
agent.Backend = info.Backend
|
||||
agent.Platform = info.Platform
|
||||
agent.Capacity = int32(info.Capacity)
|
||||
agent.Version = info.Version
|
||||
agent.CustomLabels = info.CustomLabels
|
||||
|
||||
err = s.store.AgentUpdate(agent)
|
||||
if err != nil {
|
||||
|
@ -441,6 +500,44 @@ func (s *RPC) ReportHealth(ctx context.Context, status string) error {
|
|||
return s.store.AgentUpdate(agent)
|
||||
}
|
||||
|
||||
func (s *RPC) checkAgentPermissionByWorkflow(_ context.Context, agent *model.Agent, strWorkflowID string, pipeline *model.Pipeline, repo *model.Repo) error {
|
||||
var err error
|
||||
if repo == nil && pipeline == nil {
|
||||
workflowID, err := strconv.ParseInt(strWorkflowID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workflow, err := s.store.WorkflowLoad(workflowID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("cannot find workflow with id %d", workflowID)
|
||||
return err
|
||||
}
|
||||
|
||||
pipeline, err = s.store.GetPipeline(workflow.PipelineID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("cannot find pipeline with id %d", workflow.PipelineID)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if repo == nil {
|
||||
repo, err = s.store.GetRepo(pipeline.RepoID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("cannot find repo with id %d", pipeline.RepoID)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if agent.CanAccessRepo(repo) {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("agent '%d' is not allowed to interact with repo[%d] '%s'", agent.ID, repo.ID, repo.FullName)
|
||||
log.Error().Int64("repoId", repo.ID).Msg(msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
func (s *RPC) completeChildrenIfParentCompleted(completedWorkflow *model.Workflow) {
|
||||
for _, c := range completedWorkflow.Children {
|
||||
if c.Running() {
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/stretchr/testify/mock"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
|
||||
)
|
||||
|
@ -52,15 +53,19 @@ func TestRegisterAgent(t *testing.T) {
|
|||
|
||||
store.On("AgentFind", int64(1337)).Once().Return(storeAgent, nil)
|
||||
store.On("AgentUpdate", &updatedAgent).Once().Return(nil)
|
||||
rpc := RPC{
|
||||
grpc := RPC{
|
||||
store: store,
|
||||
}
|
||||
ctx := metadata.NewIncomingContext(
|
||||
context.Background(),
|
||||
metadata.Pairs("hostname", "hostname", "agent_id", "1337"),
|
||||
)
|
||||
capacity := int32(2)
|
||||
agentID, err := rpc.RegisterAgent(ctx, "platform", "backend", "version", capacity)
|
||||
agentID, err := grpc.RegisterAgent(ctx, rpc.AgentInfo{
|
||||
Version: "version",
|
||||
Platform: "platform",
|
||||
Backend: "backend",
|
||||
Capacity: 2,
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
@ -92,15 +97,19 @@ func TestRegisterAgent(t *testing.T) {
|
|||
|
||||
store.On("AgentFind", int64(1337)).Once().Return(storeAgent, nil)
|
||||
store.On("AgentUpdate", &updatedAgent).Once().Return(nil)
|
||||
rpc := RPC{
|
||||
grpc := RPC{
|
||||
store: store,
|
||||
}
|
||||
ctx := metadata.NewIncomingContext(
|
||||
context.Background(),
|
||||
metadata.Pairs("hostname", "newHostname", "agent_id", "1337"),
|
||||
)
|
||||
capacity := int32(2)
|
||||
agentID, err := rpc.RegisterAgent(ctx, "platform", "backend", "version", capacity)
|
||||
agentID, err := grpc.RegisterAgent(ctx, rpc.AgentInfo{
|
||||
Version: "version",
|
||||
Platform: "platform",
|
||||
Backend: "backend",
|
||||
Capacity: 2,
|
||||
})
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -172,7 +172,14 @@ func (s *WoodpeckerServer) Log(c context.Context, req *proto.LogRequest) (*proto
|
|||
|
||||
func (s *WoodpeckerServer) RegisterAgent(c context.Context, req *proto.RegisterAgentRequest) (*proto.RegisterAgentResponse, error) {
|
||||
res := new(proto.RegisterAgentResponse)
|
||||
agentID, err := s.peer.RegisterAgent(c, req.GetPlatform(), req.GetBackend(), req.GetVersion(), req.GetCapacity())
|
||||
agentInfo := req.GetInfo()
|
||||
agentID, err := s.peer.RegisterAgent(c, rpc.AgentInfo{
|
||||
Version: agentInfo.GetVersion(),
|
||||
Platform: agentInfo.GetPlatform(),
|
||||
Backend: agentInfo.GetBackend(),
|
||||
Capacity: int(agentInfo.GetCapacity()),
|
||||
CustomLabels: agentInfo.GetCustomLabels(),
|
||||
})
|
||||
res.AgentId = agentID
|
||||
return res, err
|
||||
}
|
||||
|
|
|
@ -14,27 +14,73 @@
|
|||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
)
|
||||
|
||||
type Agent struct {
|
||||
ID int64 `json:"id" xorm:"pk autoincr 'id'"`
|
||||
Created int64 `json:"created" xorm:"created"`
|
||||
Updated int64 `json:"updated" xorm:"updated"`
|
||||
Name string `json:"name" xorm:"name"`
|
||||
OwnerID int64 `json:"owner_id" xorm:"'owner_id'"`
|
||||
Token string `json:"token" xorm:"token"`
|
||||
LastContact int64 `json:"last_contact" xorm:"last_contact"`
|
||||
LastWork int64 `json:"last_work" xorm:"last_work"` // last time the agent did something, this value is used to determine if the agent is still doing work used by the autoscaler
|
||||
Platform string `json:"platform" xorm:"VARCHAR(100) 'platform'"`
|
||||
Backend string `json:"backend" xorm:"VARCHAR(100) 'backend'"`
|
||||
Capacity int32 `json:"capacity" xorm:"capacity"`
|
||||
Version string `json:"version" xorm:"'version'"`
|
||||
NoSchedule bool `json:"no_schedule" xorm:"no_schedule"`
|
||||
ID int64 `json:"id" xorm:"pk autoincr 'id'"`
|
||||
Created int64 `json:"created" xorm:"created"`
|
||||
Updated int64 `json:"updated" xorm:"updated"`
|
||||
Name string `json:"name" xorm:"name"`
|
||||
OwnerID int64 `json:"owner_id" xorm:"'owner_id'"`
|
||||
Token string `json:"token" xorm:"token"`
|
||||
LastContact int64 `json:"last_contact" xorm:"last_contact"`
|
||||
LastWork int64 `json:"last_work" xorm:"last_work"` // last time the agent did something, this value is used to determine if the agent is still doing work used by the autoscaler
|
||||
Platform string `json:"platform" xorm:"VARCHAR(100) 'platform'"`
|
||||
Backend string `json:"backend" xorm:"VARCHAR(100) 'backend'"`
|
||||
Capacity int32 `json:"capacity" xorm:"capacity"`
|
||||
Version string `json:"version" xorm:"'version'"`
|
||||
NoSchedule bool `json:"no_schedule" xorm:"no_schedule"`
|
||||
CustomLabels map[string]string `json:"custom_labels" xorm:"JSON 'custom_labels'"`
|
||||
// OrgID is counted as unset if set to -1, this is done to ensure a new(Agent) still enforce the OrgID check by default
|
||||
OrgID int64 `json:"org_id" xorm:"INDEX 'org_id'"`
|
||||
} // @name Agent
|
||||
|
||||
const (
|
||||
IDNotSet = -1
|
||||
agentFilterOrgID = "org-id"
|
||||
)
|
||||
|
||||
// TableName return database table name for xorm.
|
||||
func (Agent) TableName() string {
|
||||
return "agents"
|
||||
}
|
||||
|
||||
func (a *Agent) IsSystemAgent() bool {
|
||||
return a.OwnerID == -1
|
||||
return a.OwnerID == IDNotSet
|
||||
}
|
||||
|
||||
func GenerateNewAgentToken() string {
|
||||
return base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
|
||||
}
|
||||
|
||||
func (a *Agent) GetServerLabels() (map[string]string, error) {
|
||||
filters := make(map[string]string)
|
||||
|
||||
// enforce filters for user and organization agents
|
||||
if a.OrgID != IDNotSet {
|
||||
filters[agentFilterOrgID] = fmt.Sprintf("%d", a.OrgID)
|
||||
} else {
|
||||
filters[agentFilterOrgID] = "*"
|
||||
}
|
||||
|
||||
return filters, nil
|
||||
}
|
||||
|
||||
func (a *Agent) CanAccessRepo(repo *Repo) bool {
|
||||
// global agent
|
||||
if a.OrgID == IDNotSet {
|
||||
return true
|
||||
}
|
||||
|
||||
// agent has access to the organization
|
||||
if a.OrgID == repo.OrgID {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
90
server/model/agent_test.go
Normal file
90
server/model/agent_test.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGenerateNewAgentToken(t *testing.T) {
|
||||
token1 := GenerateNewAgentToken()
|
||||
token2 := GenerateNewAgentToken()
|
||||
|
||||
assert.NotEmpty(t, token1)
|
||||
assert.NotEmpty(t, token2)
|
||||
assert.NotEqual(t, token1, token2)
|
||||
assert.Len(t, token1, 56)
|
||||
}
|
||||
|
||||
func TestAgent_GetServerLabels(t *testing.T) {
|
||||
t.Run("EmptyAgent", func(t *testing.T) {
|
||||
agent := &Agent{}
|
||||
filters, err := agent.GetServerLabels()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]string{
|
||||
agentFilterOrgID: "0",
|
||||
}, filters)
|
||||
})
|
||||
|
||||
t.Run("GlobalAgent", func(t *testing.T) {
|
||||
agent := &Agent{
|
||||
OrgID: IDNotSet,
|
||||
}
|
||||
filters, err := agent.GetServerLabels()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]string{
|
||||
agentFilterOrgID: "*",
|
||||
}, filters)
|
||||
})
|
||||
|
||||
t.Run("OrgAgent", func(t *testing.T) {
|
||||
agent := &Agent{
|
||||
OrgID: 123,
|
||||
}
|
||||
filters, err := agent.GetServerLabels()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]string{
|
||||
agentFilterOrgID: "123",
|
||||
}, filters)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAgent_CanAccessRepo(t *testing.T) {
|
||||
repo := &Repo{ID: 123, OrgID: 12}
|
||||
otherRepo := &Repo{ID: 456, OrgID: 45}
|
||||
|
||||
t.Run("EmptyAgent", func(t *testing.T) {
|
||||
agent := &Agent{}
|
||||
assert.False(t, agent.CanAccessRepo(repo))
|
||||
})
|
||||
|
||||
t.Run("GlobalAgent", func(t *testing.T) {
|
||||
agent := &Agent{
|
||||
OrgID: IDNotSet,
|
||||
}
|
||||
|
||||
assert.True(t, agent.CanAccessRepo(repo))
|
||||
})
|
||||
|
||||
t.Run("OrgAgent", func(t *testing.T) {
|
||||
agent := &Agent{
|
||||
OrgID: 12,
|
||||
}
|
||||
assert.True(t, agent.CanAccessRepo(repo))
|
||||
assert.False(t, agent.CanAccessRepo(otherRepo))
|
||||
})
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright 2018 Drone.IO Inc.
|
||||
//
|
||||
// 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 model
|
||||
|
||||
// ResourceLimit is the resource limit to set on pipeline steps.
|
||||
type ResourceLimit struct {
|
||||
MemSwapLimit int64
|
||||
MemLimit int64
|
||||
ShmSize int64
|
||||
CPUQuota int64
|
||||
CPUShares int64
|
||||
CPUSet string
|
||||
}
|
|
@ -41,6 +41,18 @@ func (t *Task) String() string {
|
|||
return sb.String()
|
||||
}
|
||||
|
||||
func (t *Task) ApplyLabelsFromRepo(r *Repo) error {
|
||||
if r == nil {
|
||||
return fmt.Errorf("repo is nil but needed to get task labels")
|
||||
}
|
||||
if t.Labels == nil {
|
||||
t.Labels = make(map[string]string)
|
||||
}
|
||||
t.Labels["repo"] = r.FullName
|
||||
t.Labels[agentFilterOrgID] = fmt.Sprintf("%d", r.OrgID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShouldRun tells if a task should be run or skipped, based on dependencies.
|
||||
func (t *Task) ShouldRun() bool {
|
||||
if t.runsOnFailure() && t.runsOnSuccess() {
|
||||
|
|
87
server/model/task_test.go
Normal file
87
server/model/task_test.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTask_GetLabels(t *testing.T) {
|
||||
t.Run("Nil Repo", func(t *testing.T) {
|
||||
task := &Task{}
|
||||
err := task.ApplyLabelsFromRepo(nil)
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, task.Labels)
|
||||
assert.EqualError(t, err, "repo is nil but needed to get task labels")
|
||||
})
|
||||
|
||||
t.Run("Empty Repo", func(t *testing.T) {
|
||||
task := &Task{}
|
||||
repo := &Repo{}
|
||||
|
||||
err := task.ApplyLabelsFromRepo(repo)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, task.Labels)
|
||||
assert.Equal(t, map[string]string{
|
||||
"repo": "",
|
||||
agentFilterOrgID: "0",
|
||||
}, task.Labels)
|
||||
})
|
||||
|
||||
t.Run("Empty Labels", func(t *testing.T) {
|
||||
task := &Task{}
|
||||
repo := &Repo{
|
||||
FullName: "test/repo",
|
||||
ID: 123,
|
||||
OrgID: 456,
|
||||
}
|
||||
|
||||
err := task.ApplyLabelsFromRepo(repo)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, task.Labels)
|
||||
assert.Equal(t, map[string]string{
|
||||
"repo": "test/repo",
|
||||
agentFilterOrgID: "456",
|
||||
}, task.Labels)
|
||||
})
|
||||
|
||||
t.Run("Existing Labels", func(t *testing.T) {
|
||||
task := &Task{
|
||||
Labels: map[string]string{
|
||||
"existing": "label",
|
||||
},
|
||||
}
|
||||
repo := &Repo{
|
||||
FullName: "test/repo",
|
||||
ID: 123,
|
||||
OrgID: 456,
|
||||
}
|
||||
|
||||
err := task.ApplyLabelsFromRepo(repo)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, task.Labels)
|
||||
assert.Equal(t, map[string]string{
|
||||
"existing": "label",
|
||||
"repo": "test/repo",
|
||||
agentFilterOrgID: "456",
|
||||
}, task.Labels)
|
||||
})
|
||||
}
|
|
@ -38,7 +38,7 @@ func parsePipeline(forge forge.Forge, store store.Store, currentPipeline *model.
|
|||
}
|
||||
|
||||
// get the previous pipeline so that we can send status change notifications
|
||||
last, err := store.GetPipelineLastBefore(repo, currentPipeline.Branch, currentPipeline.ID)
|
||||
prev, err := store.GetPipelineLastBefore(repo, currentPipeline.Branch, currentPipeline.ID)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
log.Error().Err(err).Str("repo", repo.FullName).Msgf("error getting last pipeline before pipeline number '%d'", currentPipeline.Number)
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ func parsePipeline(forge forge.Forge, store store.Store, currentPipeline *model.
|
|||
b := stepbuilder.StepBuilder{
|
||||
Repo: repo,
|
||||
Curr: currentPipeline,
|
||||
Last: last,
|
||||
Prev: prev,
|
||||
Netrc: netrc,
|
||||
Secs: secs,
|
||||
Regs: regs,
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
|
@ -31,18 +32,19 @@ func queuePipeline(ctx context.Context, repo *model.Repo, pipelineItems []*stepb
|
|||
if item.Workflow.State == model.StatusSkipped {
|
||||
continue
|
||||
}
|
||||
task := new(model.Task)
|
||||
task.ID = fmt.Sprint(item.Workflow.ID)
|
||||
task.Labels = map[string]string{}
|
||||
for k, v := range item.Labels {
|
||||
task.Labels[k] = v
|
||||
task := &model.Task{
|
||||
ID: fmt.Sprint(item.Workflow.ID),
|
||||
Labels: make(map[string]string),
|
||||
}
|
||||
maps.Copy(task.Labels, item.Labels)
|
||||
err := task.ApplyLabelsFromRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
task.Labels["repo"] = repo.FullName
|
||||
task.Dependencies = taskIDs(item.DependsOn, pipelineItems)
|
||||
task.RunOn = item.RunsOn
|
||||
task.DepStatus = make(map[string]model.StatusValue)
|
||||
|
||||
var err error
|
||||
task.Data, err = json.Marshal(rpc.Workflow{
|
||||
ID: fmt.Sprint(item.Workflow.ID),
|
||||
Config: item.Config,
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
)
|
||||
|
||||
// MetadataFromStruct return the metadata from a pipeline will run with.
|
||||
func MetadataFromStruct(forge metadata.ServerForge, repo *model.Repo, pipeline, last *model.Pipeline, workflow *model.Workflow, sysURL string) metadata.Metadata {
|
||||
func MetadataFromStruct(forge metadata.ServerForge, repo *model.Repo, pipeline, prev *model.Pipeline, workflow *model.Workflow, sysURL string) metadata.Metadata {
|
||||
host := sysURL
|
||||
uri, err := url.Parse(sysURL)
|
||||
if err == nil {
|
||||
|
@ -78,7 +78,7 @@ func MetadataFromStruct(forge metadata.ServerForge, repo *model.Repo, pipeline,
|
|||
return metadata.Metadata{
|
||||
Repo: fRepo,
|
||||
Curr: metadataPipelineFromModelPipeline(pipeline, true),
|
||||
Prev: metadataPipelineFromModelPipeline(last, false),
|
||||
Prev: metadataPipelineFromModelPipeline(prev, false),
|
||||
Workflow: fWorkflow,
|
||||
Step: metadata.Step{},
|
||||
Sys: metadata.System{
|
||||
|
|
|
@ -33,7 +33,7 @@ func TestMetadataFromStruct(t *testing.T) {
|
|||
name string
|
||||
forge metadata.ServerForge
|
||||
repo *model.Repo
|
||||
pipeline, last *model.Pipeline
|
||||
pipeline, prev *model.Pipeline
|
||||
workflow *model.Workflow
|
||||
sysURL string
|
||||
expectedMetadata metadata.Metadata
|
||||
|
@ -63,7 +63,7 @@ func TestMetadataFromStruct(t *testing.T) {
|
|||
forge: forge,
|
||||
repo: &model.Repo{FullName: "testUser/testRepo", ForgeURL: "https://gitea.com/testUser/testRepo", Clone: "https://gitea.com/testUser/testRepo.git", CloneSSH: "git@gitea.com:testUser/testRepo.git", Branch: "main", IsSCMPrivate: true, SCMKind: "git"},
|
||||
pipeline: &model.Pipeline{Number: 3, ChangedFiles: []string{"test.go", "markdown file.md"}},
|
||||
last: &model.Pipeline{Number: 2},
|
||||
prev: &model.Pipeline{Number: 2},
|
||||
workflow: &model.Workflow{Name: "hello"},
|
||||
sysURL: "https://example.com",
|
||||
expectedMetadata: metadata.Metadata{
|
||||
|
@ -98,7 +98,7 @@ func TestMetadataFromStruct(t *testing.T) {
|
|||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
result := MetadataFromStruct(testCase.forge, testCase.repo, testCase.pipeline, testCase.last, testCase.workflow, testCase.sysURL)
|
||||
result := MetadataFromStruct(testCase.forge, testCase.repo, testCase.pipeline, testCase.prev, testCase.workflow, testCase.sysURL)
|
||||
assert.EqualValues(t, testCase.expectedMetadata, result)
|
||||
assert.EqualValues(t, testCase.expectedEnviron, result.Environ())
|
||||
})
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue