mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-06-02 13:39:55 +00:00
Merge branch 'main' into woodpecker-fix-log-tail-cpu-lock
This commit is contained in:
commit
6cf032f03b
|
@ -90,7 +90,11 @@
|
|||
"binutils",
|
||||
"nocolor",
|
||||
"logfile",
|
||||
"Keyfunc"
|
||||
"Keyfunc",
|
||||
"protoc",
|
||||
"PROTOC",
|
||||
"GOBIN",
|
||||
"GOPATH"
|
||||
],
|
||||
"ignorePaths": [
|
||||
"**/node_modules/**/*",
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -13,7 +13,7 @@
|
|||
*.so
|
||||
*.dylib
|
||||
vendor/
|
||||
__debug_bin
|
||||
__debug_bin*
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
|
2
.mockery.yaml
Normal file
2
.mockery.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
disable-version-string: true
|
|
@ -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.12.1-beta
|
||||
rev: v2.12.0
|
||||
hooks:
|
||||
- id: hadolint
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
|
|
|
@ -3,8 +3,9 @@ when:
|
|||
|
||||
variables:
|
||||
- &golang_image 'docker.io/golang:1.22.2'
|
||||
- &node_image 'docker.io/node:21-alpine'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.1'
|
||||
- &node_image 'docker.io/node:22-alpine'
|
||||
# TODO: switch back to upstream image after https://github.com/techknowlogick/xgo/pull/224 got merged
|
||||
- &xgo_image 'docker.io/pats22/xgo:go-1.22.1'
|
||||
- &xgo_version 'go-1.21.2'
|
||||
|
||||
steps:
|
||||
|
@ -90,11 +91,10 @@ steps:
|
|||
release:
|
||||
depends_on:
|
||||
- checksums
|
||||
image: docker.io/plugins/github-release
|
||||
secrets:
|
||||
- source: github_token
|
||||
target: github_release_api_key
|
||||
image: woodpeckerci/plugin-github-release:1.1.2
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: github_token
|
||||
files:
|
||||
- dist/*.tar.gz
|
||||
- dist/*.deb
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
variables:
|
||||
- &golang_image 'docker.io/golang:1.22.2'
|
||||
- &node_image 'docker.io/node:21-alpine'
|
||||
- &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.1'
|
||||
- &node_image 'docker.io/node:22-alpine'
|
||||
# TODO: switch back to upstream image after https://github.com/techknowlogick/xgo/pull/224 got merged
|
||||
- &xgo_image 'docker.io/pats22/xgo:go-1.22.1'
|
||||
- &xgo_version 'go-1.21.2'
|
||||
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:3.2.1'
|
||||
- &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'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
steps:
|
||||
release-helper:
|
||||
image: woodpeckerci/plugin-ready-release-go:1.1.0
|
||||
image: woodpeckerci/plugin-ready-release-go:1.1.1
|
||||
pull: true
|
||||
settings:
|
||||
release_branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
|
|
@ -13,7 +13,7 @@ steps:
|
|||
branch: renovate/*
|
||||
|
||||
- name: spellcheck
|
||||
image: docker.io/node:21-alpine
|
||||
image: docker.io/node:22-alpine
|
||||
depends_on: []
|
||||
commands:
|
||||
- corepack enable
|
||||
|
|
|
@ -6,7 +6,7 @@ when:
|
|||
- renovate/*
|
||||
|
||||
variables:
|
||||
- &node_image 'docker.io/node:21-alpine'
|
||||
- &node_image 'docker.io/node:22-alpine'
|
||||
- &when
|
||||
path:
|
||||
# related config files
|
||||
|
|
17
Makefile
17
Makefile
|
@ -58,8 +58,6 @@ ifeq (in_docker,$(firstword $(MAKECMDGOALS)))
|
|||
-e TARGETOS="$(TARGETOS)" \
|
||||
-e TARGETARCH="$(TARGETARCH)" \
|
||||
-e CGO_ENABLED="$(CGO_ENABLED)" \
|
||||
-e GOPATH=/tmp/go \
|
||||
-e HOME=/tmp/home \
|
||||
-v $(PWD):/build --rm woodpecker/make:local make $(MAKE_ARGS)
|
||||
else
|
||||
|
||||
|
@ -110,7 +108,7 @@ clean-all: clean ## Clean all artifacts
|
|||
rm -rf docs/docs/40-cli.md docs/swagger.json
|
||||
|
||||
.PHONY: generate
|
||||
generate: generate-swagger ## Run all code generations
|
||||
generate: install-tools generate-swagger ## Run all code generations
|
||||
go generate ./...
|
||||
|
||||
generate-swagger: install-tools ## Run swagger code generation
|
||||
|
@ -137,6 +135,15 @@ install-tools: ## Install development tools
|
|||
fi ; \
|
||||
hash addlicense > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install github.com/google/addlicense@latest; \
|
||||
fi ; \
|
||||
hash mockery > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install github.com/vektra/mockery/v2@latest; \
|
||||
fi ; \
|
||||
hash protoc-gen-go > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest; \
|
||||
fi ; \
|
||||
hash protoc-gen-go-grpc > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest; \
|
||||
fi
|
||||
|
||||
ui-dependencies: ## Install UI dependencies
|
||||
|
@ -299,6 +306,10 @@ bundle-cli: bundle-prepare ## Create bundles for cli
|
|||
.PHONY: bundle
|
||||
bundle: bundle-agent bundle-server bundle-cli ## Create all bundles
|
||||
|
||||
.PHONY: spellcheck
|
||||
spellcheck:
|
||||
pnpx cspell lint --no-progress --gitignore '{**,.*}/{*,.*}'
|
||||
|
||||
##@ Docs
|
||||
.PHONY: docs
|
||||
docs: ## Generate docs (currently only for the cli)
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
</p>
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="https://ci.woodpecker-ci.org/repos/3780" title="Build Status">
|
||||
<img src="https://ci.woodpecker-ci.org/api/badges/3780/status.svg" alt="Build Status">
|
||||
<a href="https://ci.woodpecker-ci.org/repos/3780" title="Pipeline Status">
|
||||
<img src="https://ci.woodpecker-ci.org/api/badges/3780/status.svg" alt="Pipeline Status">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/woodpecker-ci/woodpecker">
|
||||
<img src="https://codecov.io/gh/woodpecker-ci/woodpecker/branch/main/graph/badge.svg" alt="Code coverage">
|
||||
|
|
|
@ -75,14 +75,29 @@ func FormatFlag(tmpl string, hidden ...bool) *cli.StringFlag {
|
|||
}
|
||||
}
|
||||
|
||||
// OutputFlags returns a slice of cli.Flag containing output format options.
|
||||
func OutputFlags(def string) []cli.Flag {
|
||||
return []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Usage: "output format",
|
||||
Value: def,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "output-no-headers",
|
||||
Usage: "don't print headers",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var RepoFlag = &cli.StringFlag{
|
||||
Name: "repository",
|
||||
Aliases: []string{"repo"},
|
||||
Usage: "repository id or full-name (e.g. 134 or octocat/hello-world)",
|
||||
Usage: "repository id or full name (e.g. 134 or octocat/hello-world)",
|
||||
}
|
||||
|
||||
var OrgFlag = &cli.StringFlag{
|
||||
Name: "organization",
|
||||
Aliases: []string{"org"},
|
||||
Usage: "organization id or full-name (e.g. 123 or octocat)",
|
||||
Usage: "organization id or full name (e.g. 123 or octocat)",
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ func After(_ *cli.Context) error {
|
|||
if waitForUpdateCheck != nil {
|
||||
select {
|
||||
case <-waitForUpdateCheck.Done():
|
||||
// When the actual command already finished, we still wait 250ms for the update check to finish
|
||||
// When the actual command already finished, we still wait 500ms for the update check to finish
|
||||
case <-time.After(time.Millisecond * 500):
|
||||
log.Debug().Msg("Update check stopped due to timeout")
|
||||
cancelWaitForUpdate(errors.New("update check timeout"))
|
||||
|
|
|
@ -30,9 +30,12 @@ func Load(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if config == nil && !c.IsSet("server-url") && !c.IsSet("token") {
|
||||
log.Info().Msg("The woodpecker-cli is not yet set up. Please run `woodpecker-cli setup`")
|
||||
return errors.New("woodpecker-cli is not setup")
|
||||
if config == nil {
|
||||
config = &Config{
|
||||
LogLevel: "info",
|
||||
ServerURL: c.String("server-url"),
|
||||
Token: c.String("token"),
|
||||
}
|
||||
}
|
||||
|
||||
if !c.IsSet("server") {
|
||||
|
@ -56,6 +59,11 @@ func Load(c *cli.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
if config.ServerURL == "" || config.Token == "" {
|
||||
log.Info().Msg("The woodpecker-cli is not yet set up. Please run `woodpecker-cli setup` or provide the required environment variables / flags.")
|
||||
return errors.New("woodpecker-cli is not configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
var logPurgeCmd = &cli.Command{
|
||||
Name: "purge",
|
||||
Usage: "purge a log",
|
||||
ArgsUsage: "<repo-id|repo-full-name> <pipeline>",
|
||||
ArgsUsage: "<repo-id|repo-full-name> <pipeline> [step]",
|
||||
Action: logPurge,
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,21 @@ func logPurge(c *cli.Context) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
err = client.LogsPurge(repoID, number)
|
||||
stepArg := c.Args().Get(2) //nolint: gomnd
|
||||
// TODO: Add lookup by name: stepID, err := internal.ParseStep(client, repoID, stepIDOrName)
|
||||
var stepID int64
|
||||
if len(stepArg) != 0 {
|
||||
stepID, err = strconv.ParseInt(stepArg, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if stepID > 0 {
|
||||
err = client.StepLogsPurge(repoID, number, stepID)
|
||||
} else {
|
||||
err = client.LogsPurge(repoID, number)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
24
cli/output/output.go
Normal file
24
cli/output/output.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrOutputOptionRequired = errors.New("output option required")
|
||||
|
||||
func ParseOutputOptions(out string) (string, []string) {
|
||||
out, opt, found := strings.Cut(out, "=")
|
||||
|
||||
if !found {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
var optList []string
|
||||
|
||||
if opt != "" {
|
||||
optList = strings.Split(opt, ",")
|
||||
}
|
||||
|
||||
return out, optList
|
||||
}
|
203
cli/output/table.go
Normal file
203
cli/output/table.go
Normal file
|
@ -0,0 +1,203 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"unicode"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// NewTable creates a new Table.
|
||||
func NewTable(out io.Writer) *Table {
|
||||
padding := 2
|
||||
|
||||
return &Table{
|
||||
w: tabwriter.NewWriter(out, 0, 0, padding, ' ', 0),
|
||||
columns: map[string]bool{},
|
||||
fieldMapping: map[string]FieldFn{},
|
||||
fieldAlias: map[string]string{},
|
||||
allowedFields: map[string]bool{},
|
||||
}
|
||||
}
|
||||
|
||||
type FieldFn func(obj any) string
|
||||
|
||||
type writerFlusher interface {
|
||||
io.Writer
|
||||
Flush() error
|
||||
}
|
||||
|
||||
// Table is a generic way to format object as a table.
|
||||
type Table struct {
|
||||
w writerFlusher
|
||||
columns map[string]bool
|
||||
fieldMapping map[string]FieldFn
|
||||
fieldAlias map[string]string
|
||||
allowedFields map[string]bool
|
||||
}
|
||||
|
||||
// Columns returns a list of known output columns.
|
||||
func (o *Table) Columns() (cols []string) {
|
||||
for c := range o.columns {
|
||||
cols = append(cols, c)
|
||||
}
|
||||
sort.Strings(cols)
|
||||
return
|
||||
}
|
||||
|
||||
// AddFieldAlias overrides the field name to allow custom column headers.
|
||||
func (o *Table) AddFieldAlias(field, alias string) *Table {
|
||||
o.fieldAlias[field] = alias
|
||||
return o
|
||||
}
|
||||
|
||||
// AddFieldFn adds a function which handles the output of the specified field.
|
||||
func (o *Table) AddFieldFn(field string, fn FieldFn) *Table {
|
||||
o.fieldMapping[field] = fn
|
||||
o.allowedFields[field] = true
|
||||
o.columns[field] = true
|
||||
return o
|
||||
}
|
||||
|
||||
// AddAllowedFields reads all first level fieldnames of the struct and allows them to be used.
|
||||
func (o *Table) AddAllowedFields(obj any) (*Table, error) {
|
||||
v := reflect.ValueOf(obj)
|
||||
if v.Kind() != reflect.Struct {
|
||||
return o, fmt.Errorf("AddAllowedFields input must be a struct")
|
||||
}
|
||||
t := v.Type()
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
k := t.Field(i).Type.Kind()
|
||||
if k != reflect.Bool &&
|
||||
k != reflect.Float32 &&
|
||||
k != reflect.Float64 &&
|
||||
k != reflect.String &&
|
||||
k != reflect.Int &&
|
||||
k != reflect.Int64 {
|
||||
// only allow simple values
|
||||
// complex values need to be mapped via a FieldFn
|
||||
continue
|
||||
}
|
||||
o.allowedFields[strings.ToLower(t.Field(i).Name)] = true
|
||||
o.allowedFields[fieldName(t.Field(i).Name)] = true
|
||||
o.columns[fieldName(t.Field(i).Name)] = true
|
||||
}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// RemoveAllowedField removes fields from the allowed list.
|
||||
func (o *Table) RemoveAllowedField(fields ...string) *Table {
|
||||
for _, field := range fields {
|
||||
delete(o.allowedFields, field)
|
||||
delete(o.columns, field)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// ValidateColumns returns an error if invalid columns are specified.
|
||||
func (o *Table) ValidateColumns(cols []string) error {
|
||||
var invalidCols []string
|
||||
for _, col := range cols {
|
||||
if _, ok := o.allowedFields[strings.ToLower(col)]; !ok {
|
||||
invalidCols = append(invalidCols, col)
|
||||
}
|
||||
}
|
||||
if len(invalidCols) > 0 {
|
||||
return fmt.Errorf("invalid table columns: %s", strings.Join(invalidCols, ","))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeader writes the table header.
|
||||
func (o *Table) WriteHeader(columns []string) {
|
||||
var header []string
|
||||
for _, col := range columns {
|
||||
if alias, ok := o.fieldAlias[col]; ok {
|
||||
col = alias
|
||||
}
|
||||
header = append(header, strings.ReplaceAll(strings.ToUpper(col), "_", " "))
|
||||
}
|
||||
_, _ = fmt.Fprintln(o.w, strings.Join(header, "\t"))
|
||||
}
|
||||
|
||||
func (o *Table) Flush() error {
|
||||
return o.w.Flush()
|
||||
}
|
||||
|
||||
// Write writes a table line.
|
||||
func (o *Table) Write(columns []string, obj any) error {
|
||||
var data map[string]any
|
||||
|
||||
if err := mapstructure.Decode(obj, &data); err != nil {
|
||||
return fmt.Errorf("failed to decode object: %w", err)
|
||||
}
|
||||
|
||||
dataL := map[string]any{}
|
||||
for key, value := range data {
|
||||
dataL[strings.ToLower(key)] = value
|
||||
}
|
||||
|
||||
var out []string
|
||||
for _, col := range columns {
|
||||
colName := strings.ToLower(col)
|
||||
if alias, ok := o.fieldAlias[colName]; ok {
|
||||
if fn, ok := o.fieldMapping[alias]; ok {
|
||||
out = append(out, fn(obj))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if fn, ok := o.fieldMapping[colName]; ok {
|
||||
out = append(out, fn(obj))
|
||||
continue
|
||||
}
|
||||
if value, ok := dataL[strings.ReplaceAll(colName, "_", "")]; ok {
|
||||
if value == nil {
|
||||
out = append(out, NA(""))
|
||||
continue
|
||||
}
|
||||
if b, ok := value.(bool); ok {
|
||||
out = append(out, YesNo(b))
|
||||
continue
|
||||
}
|
||||
if s, ok := value.(string); ok {
|
||||
out = append(out, NA(s))
|
||||
continue
|
||||
}
|
||||
out = append(out, fmt.Sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintln(o.w, strings.Join(out, "\t"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NA(s string) string {
|
||||
if s == "" {
|
||||
return "-"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func YesNo(b bool) string {
|
||||
if b {
|
||||
return "yes"
|
||||
}
|
||||
return "no"
|
||||
}
|
||||
|
||||
func fieldName(name string) string {
|
||||
r := []rune(name)
|
||||
var out []rune
|
||||
for i := range r {
|
||||
if i > 0 && (unicode.IsUpper(r[i])) && (i+1 < len(r) && unicode.IsLower(r[i+1]) || unicode.IsLower(r[i-1])) {
|
||||
out = append(out, '_')
|
||||
}
|
||||
out = append(out, unicode.ToLower(r[i]))
|
||||
}
|
||||
return string(out)
|
||||
}
|
75
cli/output/table_test.go
Normal file
75
cli/output/table_test.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type writerFlusherStub struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (s writerFlusherStub) Flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type testFieldsStruct struct {
|
||||
Name string
|
||||
Number int
|
||||
}
|
||||
|
||||
func TestTableOutput(t *testing.T) {
|
||||
var wfs writerFlusherStub
|
||||
to := NewTable(os.Stdout)
|
||||
to.w = &wfs
|
||||
|
||||
t.Run("AddAllowedFields", func(t *testing.T) {
|
||||
_, _ = to.AddAllowedFields(testFieldsStruct{})
|
||||
if _, ok := to.allowedFields["name"]; !ok {
|
||||
t.Error("name should be a allowed field")
|
||||
}
|
||||
})
|
||||
t.Run("AddFieldAlias", func(t *testing.T) {
|
||||
to.AddFieldAlias("woodpecker_ci", "woodpecker ci")
|
||||
if alias, ok := to.fieldAlias["woodpecker_ci"]; !ok || alias != "woodpecker ci" {
|
||||
t.Errorf("woodpecker_ci alias should be 'woodpecker ci', is: %v", alias)
|
||||
}
|
||||
})
|
||||
t.Run("AddFieldOutputFn", func(t *testing.T) {
|
||||
to.AddFieldFn("woodpecker ci", FieldFn(func(_ any) string {
|
||||
return "WOODPECKER CI!!!"
|
||||
}))
|
||||
if _, ok := to.fieldMapping["woodpecker ci"]; !ok {
|
||||
t.Errorf("'woodpecker ci' field output fn should be set")
|
||||
}
|
||||
})
|
||||
t.Run("ValidateColumns", func(t *testing.T) {
|
||||
err := to.ValidateColumns([]string{"non-existent", "NAME"})
|
||||
if err == nil ||
|
||||
strings.Contains(err.Error(), "name") ||
|
||||
!strings.Contains(err.Error(), "non-existent") {
|
||||
t.Errorf("error should contain 'non-existent' but not 'name': %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("WriteHeader", func(t *testing.T) {
|
||||
to.WriteHeader([]string{"woodpecker_ci", "name"})
|
||||
if wfs.String() != "WOODPECKER CI\tNAME\n" {
|
||||
t.Errorf("written header should be 'WOODPECKER CI\\tNAME\\n', is: %q", wfs.String())
|
||||
}
|
||||
wfs.Reset()
|
||||
})
|
||||
t.Run("WriteLine", func(t *testing.T) {
|
||||
_ = to.Write([]string{"woodpecker_ci", "name", "number"}, &testFieldsStruct{"test123", 1000000000})
|
||||
if wfs.String() != "WOODPECKER CI!!!\ttest123\t1000000000\n" {
|
||||
t.Errorf("written line should be 'WOODPECKER CI!!!\\ttest123\\t1000000000\\n', is: %q", wfs.String())
|
||||
}
|
||||
wfs.Reset()
|
||||
})
|
||||
t.Run("Columns", func(t *testing.T) {
|
||||
if len(to.Columns()) != 3 {
|
||||
t.Errorf("unexpected number of columns: %v", to.Columns())
|
||||
}
|
||||
})
|
||||
}
|
|
@ -15,9 +15,7 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
|
@ -31,8 +29,7 @@ var pipelineCreateCmd = &cli.Command{
|
|||
Usage: "create new pipeline",
|
||||
ArgsUsage: "<repo-id|repo-full-name>",
|
||||
Action: pipelineCreate,
|
||||
Flags: []cli.Flag{
|
||||
common.FormatFlag(tmplPipelineList),
|
||||
Flags: append(common.OutputFlags("table"), []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "branch",
|
||||
Usage: "branch to create pipeline from",
|
||||
|
@ -42,7 +39,7 @@ var pipelineCreateCmd = &cli.Command{
|
|||
Name: "var",
|
||||
Usage: "key=value",
|
||||
},
|
||||
},
|
||||
}...),
|
||||
}
|
||||
|
||||
func pipelineCreate(c *cli.Context) error {
|
||||
|
@ -76,10 +73,5 @@ func pipelineCreate(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(c.String("format") + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tmpl.Execute(os.Stdout, pipeline)
|
||||
return pipelineOutput(c, []woodpecker.Pipeline{*pipeline})
|
||||
}
|
||||
|
|
|
@ -15,14 +15,13 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
var pipelineInfoCmd = &cli.Command{
|
||||
|
@ -30,7 +29,7 @@ var pipelineInfoCmd = &cli.Command{
|
|||
Usage: "show pipeline details",
|
||||
ArgsUsage: "<repo-id|repo-full-name> [pipeline]",
|
||||
Action: pipelineInfo,
|
||||
Flags: []cli.Flag{common.FormatFlag(tmplPipelineInfo)},
|
||||
Flags: common.OutputFlags("table"),
|
||||
}
|
||||
|
||||
func pipelineInfo(c *cli.Context) error {
|
||||
|
@ -65,20 +64,5 @@ func pipelineInfo(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(c.String("format"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpl.Execute(os.Stdout, pipeline)
|
||||
return pipelineOutput(c, []woodpecker.Pipeline{*pipeline})
|
||||
}
|
||||
|
||||
// template for pipeline information
|
||||
var tmplPipelineInfo = `Number: {{ .Number }}
|
||||
Status: {{ .Status }}
|
||||
Event: {{ .Event }}
|
||||
Commit: {{ .Commit }}
|
||||
Branch: {{ .Branch }}
|
||||
Ref: {{ .Ref }}
|
||||
Message: {{ .Message }}
|
||||
Author: {{ .Author }}
|
||||
`
|
||||
|
|
|
@ -15,13 +15,11 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
var pipelineLastCmd = &cli.Command{
|
||||
|
@ -29,14 +27,13 @@ var pipelineLastCmd = &cli.Command{
|
|||
Usage: "show latest pipeline details",
|
||||
ArgsUsage: "<repo-id|repo-full-name>",
|
||||
Action: pipelineLast,
|
||||
Flags: []cli.Flag{
|
||||
common.FormatFlag(tmplPipelineInfo),
|
||||
Flags: append(common.OutputFlags("table"), []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "branch",
|
||||
Usage: "branch name",
|
||||
Value: "main",
|
||||
},
|
||||
},
|
||||
}...),
|
||||
}
|
||||
|
||||
func pipelineLast(c *cli.Context) error {
|
||||
|
@ -55,9 +52,5 @@ func pipelineLast(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(c.String("format"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tmpl.Execute(os.Stdout, pipeline)
|
||||
return pipelineOutput(c, []woodpecker.Pipeline{*pipeline})
|
||||
}
|
||||
|
|
|
@ -15,13 +15,11 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/internal"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
//nolint:gomnd
|
||||
|
@ -29,9 +27,8 @@ var pipelineListCmd = &cli.Command{
|
|||
Name: "ls",
|
||||
Usage: "show pipeline history",
|
||||
ArgsUsage: "<repo-id|repo-full-name>",
|
||||
Action: pipelineList,
|
||||
Flags: []cli.Flag{
|
||||
common.FormatFlag(tmplPipelineList),
|
||||
Action: List,
|
||||
Flags: append(common.OutputFlags("table"), []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "branch",
|
||||
Usage: "branch filter",
|
||||
|
@ -49,28 +46,33 @@ var pipelineListCmd = &cli.Command{
|
|||
Usage: "limit the list size",
|
||||
Value: 25,
|
||||
},
|
||||
},
|
||||
}...),
|
||||
}
|
||||
|
||||
func pipelineList(c *cli.Context) error {
|
||||
repoIDOrFullName := c.Args().First()
|
||||
func List(c *cli.Context) error {
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
|
||||
resources, err := pipelineList(c, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pipelineOutput(c, resources)
|
||||
}
|
||||
|
||||
func pipelineList(c *cli.Context, client woodpecker.Client) ([]woodpecker.Pipeline, error) {
|
||||
resources := make([]woodpecker.Pipeline, 0)
|
||||
|
||||
repoIDOrFullName := c.Args().First()
|
||||
repoID, err := internal.ParseRepo(client, repoIDOrFullName)
|
||||
if err != nil {
|
||||
return resources, err
|
||||
}
|
||||
|
||||
pipelines, err := client.PipelineList(repoID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(c.String("format") + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
return resources, err
|
||||
}
|
||||
|
||||
branch := c.String("branch")
|
||||
|
@ -92,21 +94,9 @@ func pipelineList(c *cli.Context) error {
|
|||
if status != "" && pipeline.Status != status {
|
||||
continue
|
||||
}
|
||||
if err := tmpl.Execute(os.Stdout, pipeline); err != nil {
|
||||
return err
|
||||
}
|
||||
resources = append(resources, *pipeline)
|
||||
count++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// template for pipeline list information
|
||||
var tmplPipelineList = "\x1b[33mPipeline #{{ .Number }} \x1b[0m" + `
|
||||
Status: {{ .Status }}
|
||||
Event: {{ .Event }}
|
||||
Commit: {{ .Commit }}
|
||||
Branch: {{ .Branch }}
|
||||
Ref: {{ .Ref }}
|
||||
Author: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }}
|
||||
Message: {{ .Message }}
|
||||
`
|
||||
return resources, nil
|
||||
}
|
||||
|
|
132
cli/pipeline/list_test.go
Normal file
132
cli/pipeline/list_test.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker/mocks"
|
||||
)
|
||||
|
||||
func TestPipelineList(t *testing.T) {
|
||||
testtases := []struct {
|
||||
name string
|
||||
repoID int64
|
||||
repoErr error
|
||||
pipelines []*woodpecker.Pipeline
|
||||
pipelineErr error
|
||||
args []string
|
||||
expected []woodpecker.Pipeline
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
repoID: 1,
|
||||
pipelines: []*woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
|
||||
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
|
||||
},
|
||||
args: []string{"ls", "repo/name"},
|
||||
expected: []woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
|
||||
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter by branch",
|
||||
repoID: 1,
|
||||
pipelines: []*woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
|
||||
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
|
||||
},
|
||||
args: []string{"ls", "--branch", "main", "repo/name"},
|
||||
expected: []woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter by event",
|
||||
repoID: 1,
|
||||
pipelines: []*woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
|
||||
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
|
||||
},
|
||||
args: []string{"ls", "--event", "push", "repo/name"},
|
||||
expected: []woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filter by status",
|
||||
repoID: 1,
|
||||
pipelines: []*woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
|
||||
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
|
||||
},
|
||||
args: []string{"ls", "--status", "success", "repo/name"},
|
||||
expected: []woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "limit results",
|
||||
repoID: 1,
|
||||
pipelines: []*woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
|
||||
{ID: 3, Branch: "main", Event: "push", Status: "failure"},
|
||||
},
|
||||
args: []string{"ls", "--limit", "2", "repo/name"},
|
||||
expected: []woodpecker.Pipeline{
|
||||
{ID: 1, Branch: "main", Event: "push", Status: "success"},
|
||||
{ID: 2, Branch: "develop", Event: "pull_request", Status: "running"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "pipeline list error",
|
||||
repoID: 1,
|
||||
pipelineErr: errors.New("pipeline error"),
|
||||
args: []string{"ls", "repo/name"},
|
||||
wantErr: errors.New("pipeline error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testtases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockClient := mocks.NewClient(t)
|
||||
mockClient.On("PipelineList", mock.Anything).Return(tt.pipelines, tt.pipelineErr)
|
||||
mockClient.On("RepoLookup", mock.Anything).Return(&woodpecker.Repo{ID: tt.repoID}, nil)
|
||||
|
||||
app := &cli.App{Writer: io.Discard}
|
||||
c := cli.NewContext(app, nil, nil)
|
||||
|
||||
command := pipelineListCmd
|
||||
command.Action = func(c *cli.Context) error {
|
||||
pipelines, err := pipelineList(c, mockClient)
|
||||
if tt.wantErr != nil {
|
||||
assert.EqualError(t, err, tt.wantErr.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, tt.expected, pipelines)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_ = command.Run(c, tt.args...)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -15,7 +15,15 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/output"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
// Command exports the pipeline command set.
|
||||
|
@ -37,3 +45,53 @@ var Command = &cli.Command{
|
|||
pipelineCreateCmd,
|
||||
},
|
||||
}
|
||||
|
||||
func pipelineOutput(c *cli.Context, resources []woodpecker.Pipeline, fd ...io.Writer) error {
|
||||
outfmt, outopt := output.ParseOutputOptions(c.String("output"))
|
||||
noHeader := c.Bool("output-no-headers")
|
||||
|
||||
var out io.Writer
|
||||
switch len(fd) {
|
||||
case 0:
|
||||
out = os.Stdout
|
||||
case 1:
|
||||
out = fd[0]
|
||||
default:
|
||||
out = os.Stdout
|
||||
}
|
||||
|
||||
switch outfmt {
|
||||
case "go-template":
|
||||
if len(outopt) < 1 {
|
||||
return fmt.Errorf("%w: missing template", output.ErrOutputOptionRequired)
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(outopt[0] + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tmpl.Execute(out, resources); err != nil {
|
||||
return err
|
||||
}
|
||||
case "table":
|
||||
fallthrough
|
||||
default:
|
||||
table := output.NewTable(out)
|
||||
cols := []string{"Number", "Status", "Event", "Branch", "Commit", "Author"}
|
||||
|
||||
if len(outopt) > 0 {
|
||||
cols = outopt
|
||||
}
|
||||
if !noHeader {
|
||||
table.WriteHeader(cols)
|
||||
}
|
||||
for _, resource := range resources {
|
||||
if err := table.Write(cols, resource); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
table.Flush()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
86
cli/pipeline/pipeline_test.go
Normal file
86
cli/pipeline/pipeline_test.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/cli/common"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
func TestPipelineOutput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expected string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "table output with default columns",
|
||||
args: []string{},
|
||||
expected: "NUMBER STATUS EVENT BRANCH COMMIT AUTHOR\n1 success push main abcdef John Doe\n",
|
||||
},
|
||||
{
|
||||
name: "table output with custom columns",
|
||||
args: []string{"output", "--output", "table=Number,Status,Branch"},
|
||||
expected: "NUMBER STATUS BRANCH\n1 success main\n",
|
||||
},
|
||||
{
|
||||
name: "table output with no header",
|
||||
args: []string{"output", "--output-no-headers"},
|
||||
expected: "1 success push main abcdef John Doe\n",
|
||||
},
|
||||
{
|
||||
name: "go-template output",
|
||||
args: []string{"output", "--output", "go-template={{range . }}{{.Number}} {{.Status}} {{.Branch}}{{end}}"},
|
||||
expected: "1 success main\n",
|
||||
},
|
||||
{
|
||||
name: "invalid go-template",
|
||||
args: []string{"output", "--output", "go-template={{.InvalidField}}"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
pipelines := []woodpecker.Pipeline{
|
||||
{
|
||||
Number: 1,
|
||||
Status: "success",
|
||||
Event: "push",
|
||||
Branch: "main",
|
||||
Commit: "abcdef",
|
||||
Author: "John Doe",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
app := &cli.App{Writer: io.Discard}
|
||||
c := cli.NewContext(app, nil, nil)
|
||||
|
||||
command := &cli.Command{}
|
||||
command.Name = "output"
|
||||
command.Flags = common.OutputFlags("table")
|
||||
command.Action = func(c *cli.Context) error {
|
||||
var buf bytes.Buffer
|
||||
err := pipelineOutput(c, pipelines, &buf)
|
||||
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, buf.String())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_ = command.Run(c, tt.args...)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get agent list",
|
||||
"summary": "List agents",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -64,13 +64,14 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Creates a new agent with a random token",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Create a new agent with a random token so a new agent can connect to the server",
|
||||
"summary": "Create a new agent",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -108,7 +109,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get agent information",
|
||||
"summary": "Get an agent",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -173,7 +174,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Update agent information",
|
||||
"summary": "Update an agent",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -218,7 +219,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get agent tasks",
|
||||
"summary": "List agent tasks",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -283,7 +284,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Badges"
|
||||
],
|
||||
"summary": "Get status badge, SVG format",
|
||||
"summary": "Get status of pipeline as SVG badge",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
|
@ -744,7 +745,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Organizations"
|
||||
],
|
||||
"summary": "Lookup organization by full-name",
|
||||
"summary": "Lookup an organization by full name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -756,7 +757,7 @@ const docTemplate = `{
|
|||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the organizations full-name / slug",
|
||||
"description": "the organizations full name / slug",
|
||||
"name": "org_full_name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
|
@ -781,7 +782,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Orgs"
|
||||
],
|
||||
"summary": "Get all orgs",
|
||||
"summary": "List organizations",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -828,7 +829,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Orgs"
|
||||
],
|
||||
"summary": "Delete an org",
|
||||
"summary": "Delete an organization",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -861,7 +862,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Organization"
|
||||
],
|
||||
"summary": "Get organization by id",
|
||||
"summary": "Get an organization",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -900,7 +901,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Organization permissions"
|
||||
],
|
||||
"summary": "Get the permissions of the current user in the given organization",
|
||||
"summary": "Get the permissions of the currently authenticated user for the given organization",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -939,7 +940,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Organization secrets"
|
||||
],
|
||||
"summary": "Get the organization secret list",
|
||||
"summary": "List organization secrets",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -990,7 +991,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Organization secrets"
|
||||
],
|
||||
"summary": "Persist/create an organization secret",
|
||||
"summary": "Create an organization secret",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1035,7 +1036,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Organization secrets"
|
||||
],
|
||||
"summary": "Get the named organization secret",
|
||||
"summary": "Get a organization secret by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1076,7 +1077,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Organization secrets"
|
||||
],
|
||||
"summary": "Delete the named secret from an organization",
|
||||
"summary": "Delete an organization secret by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1114,7 +1115,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Organization secrets"
|
||||
],
|
||||
"summary": "Update an organization secret",
|
||||
"summary": "Update an organization secret by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1166,7 +1167,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipeline queues"
|
||||
],
|
||||
"summary": "List pipeline queues",
|
||||
"summary": "List pipelines in queue",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1257,7 +1258,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipeline queues"
|
||||
],
|
||||
"summary": "Pause a pipeline queue",
|
||||
"summary": "Pause the pipeline queue",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1283,7 +1284,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipeline queues"
|
||||
],
|
||||
"summary": "Resume a pipeline queue",
|
||||
"summary": "Resume the pipeline queue",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1303,13 +1304,14 @@ const docTemplate = `{
|
|||
},
|
||||
"/repos": {
|
||||
"get": {
|
||||
"description": "Returns a list of all repositories. Requires admin rights.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Repositories"
|
||||
],
|
||||
"summary": "List all repositories on the server. Requires admin rights.",
|
||||
"summary": "List all repositories on the server",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1395,7 +1397,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repositories"
|
||||
],
|
||||
"summary": "Get repository by full-name",
|
||||
"summary": "Lookup a repository by full name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1407,7 +1409,7 @@ const docTemplate = `{
|
|||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "the repository full-name / slug",
|
||||
"description": "the repository full name / slug",
|
||||
"name": "repo_full_name",
|
||||
"in": "path",
|
||||
"required": true
|
||||
|
@ -1425,13 +1427,14 @@ const docTemplate = `{
|
|||
},
|
||||
"/repos/repair": {
|
||||
"post": {
|
||||
"description": "Executes a repair process on all repositories. Requires admin rights.",
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Repositories"
|
||||
],
|
||||
"summary": "Repair all repositories on the server. Requires admin rights.",
|
||||
"summary": "Repair all repositories on the server",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1457,7 +1460,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repositories"
|
||||
],
|
||||
"summary": "Get repository information",
|
||||
"summary": "Get a repository",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1525,7 +1528,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repositories"
|
||||
],
|
||||
"summary": "Change a repository",
|
||||
"summary": "Update a repository",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1570,7 +1573,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repositories"
|
||||
],
|
||||
"summary": "Get repository branches",
|
||||
"summary": "Get branches of a repository",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1623,7 +1626,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repositories"
|
||||
],
|
||||
"summary": "Change a repository's owner, to the one holding the access token",
|
||||
"summary": "Change a repository's owner to the currently authenticated user",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1659,7 +1662,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository cron jobs"
|
||||
],
|
||||
"summary": "Get the cron job list",
|
||||
"summary": "List cron jobs",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1710,7 +1713,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository cron jobs"
|
||||
],
|
||||
"summary": "Persist/creat a cron job",
|
||||
"summary": "Create a cron job",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1755,7 +1758,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository cron jobs"
|
||||
],
|
||||
"summary": "Get a cron job by id",
|
||||
"summary": "Get a cron job",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1837,7 +1840,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository cron jobs"
|
||||
],
|
||||
"summary": "Delete a cron job by id",
|
||||
"summary": "Delete a cron job",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1927,7 +1930,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipeline logs"
|
||||
],
|
||||
"summary": "Deletes log",
|
||||
"summary": "Deletes all logs of a pipeline",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -1967,7 +1970,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipeline logs"
|
||||
],
|
||||
"summary": "Log information",
|
||||
"summary": "Get logs for a pipeline step",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2020,7 +2023,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipeline logs"
|
||||
],
|
||||
"summary": "Deletes step log",
|
||||
"summary": "Delete step logs of a pipeline",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2108,7 +2111,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repositories"
|
||||
],
|
||||
"summary": "Repository permission information",
|
||||
"summary": "Check current authenticated users access to the repository",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2138,13 +2141,14 @@ const docTemplate = `{
|
|||
},
|
||||
"/repos/{repo_id}/pipelines": {
|
||||
"get": {
|
||||
"description": "Get a list of pipelines for a repository.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Get pipelines, current running and past ones",
|
||||
"summary": "List repository pipelines",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2174,6 +2178,18 @@ const docTemplate = `{
|
|||
"description": "for response pagination, max items per page",
|
||||
"name": "perPage",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "only return pipelines before this RFC3339 date",
|
||||
"name": "before",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "only return pipelines after this RFC3339 date",
|
||||
"name": "after",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -2195,7 +2211,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Run/trigger a pipelines",
|
||||
"summary": "Trigger a manual pipeline",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2240,7 +2256,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Pipeline information by number",
|
||||
"summary": "Get a repositories pipeline",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2327,6 +2343,44 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Delete a pipeline",
|
||||
"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": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/pipelines/{number}/approve": {
|
||||
|
@ -2337,7 +2391,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Start pipelines in gated repos",
|
||||
"summary": "Approve and start a pipeline",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2380,7 +2434,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Cancels a pipeline",
|
||||
"summary": "Cancel a pipeline",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2420,7 +2474,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Pipeline configuration",
|
||||
"summary": "Get configuration files for a pipeline",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2466,7 +2520,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipelines"
|
||||
],
|
||||
"summary": "Decline pipelines in gated repos",
|
||||
"summary": "Decline a pipeline",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2509,7 +2563,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repositories"
|
||||
],
|
||||
"summary": "List active pull requests",
|
||||
"summary": "List active pull requests of a repository",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2562,7 +2616,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository registries"
|
||||
],
|
||||
"summary": "Get the registry list",
|
||||
"summary": "List registries",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2613,7 +2667,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository registries"
|
||||
],
|
||||
"summary": "Persist/create a registry",
|
||||
"summary": "Create a registry",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2658,7 +2712,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository registries"
|
||||
],
|
||||
"summary": "Get a named registry",
|
||||
"summary": "Get a registry by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2699,7 +2753,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository registries"
|
||||
],
|
||||
"summary": "Delete a named registry",
|
||||
"summary": "Delete a registry by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2737,7 +2791,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository registries"
|
||||
],
|
||||
"summary": "Update a named registry",
|
||||
"summary": "Update a registry by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2822,7 +2876,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository secrets"
|
||||
],
|
||||
"summary": "Get the secret list",
|
||||
"summary": "List repository secrets",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2873,7 +2927,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository secrets"
|
||||
],
|
||||
"summary": "Persist/create a secret",
|
||||
"summary": "Create a repository secret",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2918,7 +2972,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository secrets"
|
||||
],
|
||||
"summary": "Get a named secret",
|
||||
"summary": "Get a repository secret by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2959,7 +3013,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository secrets"
|
||||
],
|
||||
"summary": "Delete a named secret",
|
||||
"summary": "Delete a repository secret by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -2997,7 +3051,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Repository secrets"
|
||||
],
|
||||
"summary": "Update a named secret",
|
||||
"summary": "Update a repository secret by name",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -3049,7 +3103,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Secrets"
|
||||
],
|
||||
"summary": "Get the global secret list",
|
||||
"summary": "List global secrets",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -3093,7 +3147,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Secrets"
|
||||
],
|
||||
"summary": "Persist/create a global secret",
|
||||
"summary": "Create a global secret",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -3261,14 +3315,14 @@ const docTemplate = `{
|
|||
},
|
||||
"/stream/events": {
|
||||
"get": {
|
||||
"description": "event source streaming for compatibility with quic and http2",
|
||||
"description": "With quic and http2 support",
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Events"
|
||||
],
|
||||
"summary": "Event stream",
|
||||
"summary": "Stream events like pipeline updates",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
|
@ -3284,7 +3338,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Pipeline logs"
|
||||
],
|
||||
"summary": "Log stream",
|
||||
"summary": "Stream logs of a pipeline step",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
|
@ -3323,7 +3377,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Returns the currently authenticated user.",
|
||||
"summary": "Get the currently authenticated user",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -3346,14 +3400,14 @@ const docTemplate = `{
|
|||
},
|
||||
"/user/feed": {
|
||||
"get": {
|
||||
"description": "Feed entries can be used to display information on the latest builds.",
|
||||
"description": "The feed lists the most recent pipeline for the currently authenticated user.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "A feed entry for a build.",
|
||||
"summary": "Get the currently authenticaed users pipeline feed",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -3368,7 +3422,10 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Feed"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/Feed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3383,7 +3440,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "Get user's repos",
|
||||
"summary": "Get user's repositories",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -3473,7 +3530,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Get all users",
|
||||
"summary": "List users",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -3627,7 +3684,7 @@ const docTemplate = `{
|
|||
"tags": [
|
||||
"Users"
|
||||
],
|
||||
"summary": "Change a user",
|
||||
"summary": "Update a user",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
|
@ -3891,6 +3948,9 @@ const docTemplate = `{
|
|||
"Org": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"forge_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -4122,6 +4182,9 @@ const docTemplate = `{
|
|||
"default_branch": {
|
||||
"type": "string"
|
||||
},
|
||||
"forge_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"forge_remote_id": {
|
||||
"description": "ForgeRemoteID is the unique identifier for the repository on the forge.",
|
||||
"type": "string"
|
||||
|
@ -4418,6 +4481,9 @@ const docTemplate = `{
|
|||
"description": "Email is the email address for this user.\n\nrequired: true",
|
||||
"type": "string"
|
||||
},
|
||||
"forge_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "the id for this user.\n\nrequired: true",
|
||||
"type": "integer"
|
||||
|
|
|
@ -246,11 +246,6 @@ var flags = append([]cli.Flag{
|
|||
Usage: "Disable version check in admin web ui.",
|
||||
Name: "skip-version-check",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_ADDON_FORGE"},
|
||||
Name: "addon-forge",
|
||||
Usage: "forge addon",
|
||||
},
|
||||
//
|
||||
// backend options for pipeline compiler
|
||||
//
|
||||
|
@ -309,6 +304,35 @@ var flags = append([]cli.Flag{
|
|||
Usage: "set the cpus allowed to execute containers",
|
||||
},
|
||||
//
|
||||
&cli.StringFlag{
|
||||
Name: "forge-url",
|
||||
Usage: "url of the forge",
|
||||
EnvVars: []string{"WOODPECKER_FORGE_URL", "WOODPECKER_GITHUB_URL", "WOODPECKER_GITLAB_URL", "WOODPECKER_GITEA_URL", "WOODPECKER_BITBUCKET_URL"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "forge-oauth-client",
|
||||
Usage: "oauth2 client id",
|
||||
EnvVars: []string{"WOODPECKER_FORGE_CLIENT", "WOODPECKER_GITHUB_CLIENT", "WOODPECKER_GITLAB_CLIENT", "WOODPECKER_GITEA_CLIENT", "WOODPECKER_BITBUCKET_CLIENT", "WOODPECKER_BITBUCKET_DC_CLIENT_ID"},
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "forge-oauth-secret",
|
||||
Usage: "oauth2 client secret",
|
||||
EnvVars: []string{"WOODPECKER_FORGE_SECRET", "WOODPECKER_GITHUB_SECRET", "WOODPECKER_GITLAB_SECRET", "WOODPECKER_GITEA_SECRET", "WOODPECKER_BITBUCKET_SECRET", "WOODPECKER_BITBUCKET_DC_CLIENT_SECRET"},
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "forge-skip-verify",
|
||||
Usage: "skip ssl verification",
|
||||
EnvVars: []string{"WOODPECKER_FORGE_SKIP_VERIFY", "WOODPECKER_GITHUB_SKIP_VERIFY", "WOODPECKER_GITLAB_SKIP_VERIFY", "WOODPECKER_GITEA_SKIP_VERIFY", "WOODPECKER_BITBUCKET_SKIP_VERIFY"},
|
||||
},
|
||||
//
|
||||
// Addon
|
||||
//
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_ADDON_FORGE"},
|
||||
Name: "addon-forge",
|
||||
Usage: "path to forge addon executable",
|
||||
},
|
||||
//
|
||||
// GitHub
|
||||
//
|
||||
&cli.BoolFlag{
|
||||
|
@ -316,24 +340,6 @@ var flags = append([]cli.Flag{
|
|||
Name: "github",
|
||||
Usage: "github driver is enabled",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITHUB_URL"},
|
||||
Name: "github-server",
|
||||
Usage: "github server address",
|
||||
Value: "https://github.com",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITHUB_CLIENT"},
|
||||
Name: "github-client",
|
||||
Usage: "github oauth2 client id",
|
||||
FilePath: os.Getenv("WOODPECKER_GITHUB_CLIENT_FILE"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITHUB_SECRET"},
|
||||
Name: "github-secret",
|
||||
Usage: "github oauth2 client secret",
|
||||
FilePath: os.Getenv("WOODPECKER_GITHUB_SECRET_FILE"),
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITHUB_MERGE_REF"},
|
||||
Name: "github-merge-ref",
|
||||
|
@ -346,11 +352,6 @@ var flags = append([]cli.Flag{
|
|||
Usage: "github tokens should only get access to public repos",
|
||||
Value: false,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITHUB_SKIP_VERIFY"},
|
||||
Name: "github-skip-verify",
|
||||
Usage: "github skip ssl verification",
|
||||
},
|
||||
//
|
||||
// Gitea
|
||||
//
|
||||
|
@ -359,29 +360,6 @@ var flags = append([]cli.Flag{
|
|||
Name: "gitea",
|
||||
Usage: "gitea driver is enabled",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITEA_URL"},
|
||||
Name: "gitea-server",
|
||||
Usage: "gitea server address",
|
||||
Value: "https://try.gitea.io",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITEA_CLIENT"},
|
||||
Name: "gitea-client",
|
||||
Usage: "gitea oauth2 client id",
|
||||
FilePath: os.Getenv("WOODPECKER_GITEA_CLIENT_FILE"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITEA_SECRET"},
|
||||
Name: "gitea-secret",
|
||||
Usage: "gitea oauth2 client secret",
|
||||
FilePath: os.Getenv("WOODPECKER_GITEA_SECRET_FILE"),
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITEA_SKIP_VERIFY"},
|
||||
Name: "gitea-skip-verify",
|
||||
Usage: "gitea skip ssl verification",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_DEV_GITEA_OAUTH_URL"},
|
||||
Name: "gitea-oauth-server",
|
||||
|
@ -395,18 +373,6 @@ var flags = append([]cli.Flag{
|
|||
Name: "bitbucket",
|
||||
Usage: "bitbucket driver is enabled",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_BITBUCKET_CLIENT"},
|
||||
Name: "bitbucket-client",
|
||||
Usage: "bitbucket oauth2 client id",
|
||||
FilePath: os.Getenv("WOODPECKER_BITBUCKET_CLIENT_FILE"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_BITBUCKET_SECRET"},
|
||||
Name: "bitbucket-secret",
|
||||
Usage: "bitbucket oauth2 client secret",
|
||||
FilePath: os.Getenv("WOODPECKER_BITBUCKET_SECRET_FILE"),
|
||||
},
|
||||
//
|
||||
// Gitlab
|
||||
//
|
||||
|
@ -415,29 +381,6 @@ var flags = append([]cli.Flag{
|
|||
Name: "gitlab",
|
||||
Usage: "gitlab driver is enabled",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITLAB_URL"},
|
||||
Name: "gitlab-server",
|
||||
Usage: "gitlab server address",
|
||||
Value: "https://gitlab.com",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITLAB_CLIENT"},
|
||||
Name: "gitlab-client",
|
||||
Usage: "gitlab oauth2 client id",
|
||||
FilePath: os.Getenv("WOODPECKER_GITLAB_CLIENT_FILE"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITLAB_SECRET"},
|
||||
Name: "gitlab-secret",
|
||||
Usage: "gitlab oauth2 client secret",
|
||||
FilePath: os.Getenv("WOODPECKER_GITLAB_SECRET_FILE"),
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITLAB_SKIP_VERIFY"},
|
||||
Name: "gitlab-skip-verify",
|
||||
Usage: "gitlab skip ssl verification",
|
||||
},
|
||||
//
|
||||
// Bitbucket DataCenter/Server (previously Stash)
|
||||
//
|
||||
|
@ -446,23 +389,6 @@ var flags = append([]cli.Flag{
|
|||
Name: "bitbucket-dc",
|
||||
Usage: "Bitbucket DataCenter/Server driver is enabled",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_BITBUCKET_DC_URL"},
|
||||
Name: "bitbucket-dc-server",
|
||||
Usage: "Bitbucket DataCenter/Server server address",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_BITBUCKET_DC_CLIENT_ID"},
|
||||
Name: "bitbucket-dc-client-id",
|
||||
Usage: "Bitbucket DataCenter/Server OAuth 2.0 client id",
|
||||
FilePath: os.Getenv("WOODPECKER_BITBUCKET_DC_CLIENT_ID_FILE"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_BITBUCKET_DC_CLIENT_SECRET"},
|
||||
Name: "bitbucket-dc-client-secret",
|
||||
Usage: "Bitbucket DataCenter/Server OAuth 2.0 client secret",
|
||||
FilePath: os.Getenv("WOODPECKER_BITBUCKET_DC_CLIENT_SECRET_FILE"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_BITBUCKET_DC_GIT_USERNAME"},
|
||||
Name: "bitbucket-dc-git-username",
|
||||
|
|
|
@ -38,7 +38,7 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/cron"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/setup"
|
||||
woodpeckerGrpcServer "go.woodpecker-ci.org/woodpecker/v2/server/grpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/logging"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
|
@ -82,11 +82,6 @@ func run(c *cli.Context) error {
|
|||
)
|
||||
}
|
||||
|
||||
_forge, err := setupForge(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't setup forge: %w", err)
|
||||
}
|
||||
|
||||
_store, err := setupStore(c)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't setup store: %w", err)
|
||||
|
@ -97,7 +92,7 @@ func run(c *cli.Context) error {
|
|||
}
|
||||
}()
|
||||
|
||||
err = setupEvilGlobals(c, _store, _forge)
|
||||
err = setupEvilGlobals(c, _store)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't setup globals: %w", err)
|
||||
}
|
||||
|
@ -107,7 +102,7 @@ func run(c *cli.Context) error {
|
|||
setupMetrics(&g, _store)
|
||||
|
||||
g.Go(func() error {
|
||||
return cron.Start(c.Context, _store, _forge)
|
||||
return cron.Start(c.Context, _store)
|
||||
})
|
||||
|
||||
// start the grpc server
|
||||
|
@ -130,7 +125,6 @@ func run(c *cli.Context) error {
|
|||
)
|
||||
|
||||
woodpeckerServer := woodpeckerGrpcServer.NewWoodpeckerServer(
|
||||
_forge,
|
||||
server.Config.Services.Queue,
|
||||
server.Config.Services.Logs,
|
||||
server.Config.Services.Pubsub,
|
||||
|
@ -270,17 +264,13 @@ func run(c *cli.Context) error {
|
|||
return g.Wait()
|
||||
}
|
||||
|
||||
func setupEvilGlobals(c *cli.Context, s store.Store, f forge.Forge) error {
|
||||
// forge
|
||||
server.Config.Services.Forge = f
|
||||
|
||||
func setupEvilGlobals(c *cli.Context, s store.Store) error {
|
||||
// services
|
||||
server.Config.Services.Queue = setupQueue(c, s)
|
||||
server.Config.Services.Logs = logging.New()
|
||||
server.Config.Services.Pubsub = pubsub.New()
|
||||
server.Config.Services.Membership = setupMembershipService(c, f)
|
||||
|
||||
serviceMangager, err := services.NewManager(c, s)
|
||||
server.Config.Services.Membership = setupMembershipService(c, s)
|
||||
serviceMangager, err := services.NewManager(c, s, setup.Forge)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not setup service manager: %w", err)
|
||||
}
|
||||
|
|
|
@ -18,9 +18,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
@ -31,13 +29,6 @@ import (
|
|||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/cache"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/addon"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucketdatacenter"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/gitea"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/github"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/gitlab"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/queue"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store/datastore"
|
||||
|
@ -100,103 +91,8 @@ func setupQueue(c *cli.Context, s store.Store) queue.Queue {
|
|||
return queue.WithTaskStore(queue.New(c.Context), s)
|
||||
}
|
||||
|
||||
func setupMembershipService(_ *cli.Context, r forge.Forge) cache.MembershipService {
|
||||
return cache.NewMembershipService(r)
|
||||
}
|
||||
|
||||
// setupForge helper function to set up the forge from the CLI arguments.
|
||||
func setupForge(c *cli.Context) (forge.Forge, error) {
|
||||
switch {
|
||||
case c.String("addon-forge") != "":
|
||||
return addon.Load(c.String("addon-forge"))
|
||||
case c.Bool("github"):
|
||||
return setupGitHub(c)
|
||||
case c.Bool("gitlab"):
|
||||
return setupGitLab(c)
|
||||
case c.Bool("bitbucket"):
|
||||
return setupBitbucket(c)
|
||||
case c.Bool("bitbucket-dc"):
|
||||
return setupBitbucketDatacenter(c)
|
||||
case c.Bool("gitea"):
|
||||
return setupGitea(c)
|
||||
default:
|
||||
return nil, fmt.Errorf("version control system not configured")
|
||||
}
|
||||
}
|
||||
|
||||
// setupBitbucket helper function to setup the Bitbucket forge from the CLI arguments.
|
||||
func setupBitbucket(c *cli.Context) (forge.Forge, error) {
|
||||
opts := &bitbucket.Opts{
|
||||
Client: c.String("bitbucket-client"),
|
||||
Secret: c.String("bitbucket-secret"),
|
||||
}
|
||||
log.Trace().Msgf("forge (bitbucket) opts: %#v", opts)
|
||||
return bitbucket.New(opts)
|
||||
}
|
||||
|
||||
// setupGitea helper function to set up the Gitea forge from the CLI arguments.
|
||||
func setupGitea(c *cli.Context) (forge.Forge, error) {
|
||||
server, err := url.Parse(c.String("gitea-server"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oauth2Server := c.String("gitea-oauth-server")
|
||||
if oauth2Server != "" {
|
||||
oauth2URL, err := url.Parse(oauth2Server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oauth2Server = strings.TrimRight(oauth2URL.String(), "/")
|
||||
}
|
||||
opts := gitea.Opts{
|
||||
URL: strings.TrimRight(server.String(), "/"),
|
||||
OAuth2URL: oauth2Server,
|
||||
Client: c.String("gitea-client"),
|
||||
Secret: c.String("gitea-secret"),
|
||||
SkipVerify: c.Bool("gitea-skip-verify"),
|
||||
}
|
||||
if len(opts.URL) == 0 {
|
||||
return nil, fmt.Errorf("WOODPECKER_GITEA_URL must be set")
|
||||
}
|
||||
log.Trace().Msgf("forge (gitea) opts: %#v", opts)
|
||||
return gitea.New(opts)
|
||||
}
|
||||
|
||||
// setupBitbucketDatacenter helper function to setup the Bitbucket DataCenter/Server forge from the CLI arguments.
|
||||
func setupBitbucketDatacenter(c *cli.Context) (forge.Forge, error) {
|
||||
opts := bitbucketdatacenter.Opts{
|
||||
URL: c.String("bitbucket-dc-server"),
|
||||
Username: c.String("bitbucket-dc-git-username"),
|
||||
Password: c.String("bitbucket-dc-git-password"),
|
||||
ClientID: c.String("bitbucket-dc-client-id"),
|
||||
ClientSecret: c.String("bitbucket-dc-client-secret"),
|
||||
}
|
||||
log.Trace().Msgf("Forge (bitbucketdatacenter) opts: %#v", opts)
|
||||
return bitbucketdatacenter.New(opts)
|
||||
}
|
||||
|
||||
// setupGitLab helper function to setup the GitLab forge from the CLI arguments.
|
||||
func setupGitLab(c *cli.Context) (forge.Forge, error) {
|
||||
return gitlab.New(gitlab.Opts{
|
||||
URL: c.String("gitlab-server"),
|
||||
ClientID: c.String("gitlab-client"),
|
||||
ClientSecret: c.String("gitlab-secret"),
|
||||
SkipVerify: c.Bool("gitlab-skip-verify"),
|
||||
})
|
||||
}
|
||||
|
||||
// setupGitHub helper function to setup the GitHub forge from the CLI arguments.
|
||||
func setupGitHub(c *cli.Context) (forge.Forge, error) {
|
||||
opts := github.Opts{
|
||||
URL: c.String("github-server"),
|
||||
Client: c.String("github-client"),
|
||||
Secret: c.String("github-secret"),
|
||||
SkipVerify: c.Bool("github-skip-verify"),
|
||||
MergeRef: c.Bool("github-merge-ref"),
|
||||
OnlyPublic: c.Bool("github-public-only"),
|
||||
}
|
||||
log.Trace().Msgf("forge (github) opts: %#v", opts)
|
||||
return github.New(opts)
|
||||
func setupMembershipService(_ *cli.Context, _store store.Store) cache.MembershipService {
|
||||
return cache.NewMembershipService(_store)
|
||||
}
|
||||
|
||||
func setupMetrics(g *errgroup.Group, _store store.Store) {
|
||||
|
|
|
@ -1,28 +1,34 @@
|
|||
# docker build --rm -f docker/Dockerfile.make -t woodpecker/make:local .
|
||||
FROM docker.io/golang:1.22-alpine3.18 as golang_image
|
||||
FROM docker.io/node:21-alpine3.18
|
||||
FROM docker.io/golang:1.22-alpine3.19 as golang_image
|
||||
FROM docker.io/node:22-alpine3.19
|
||||
|
||||
# renovate: datasource=repology depName=alpine_3_18/make versioning=loose
|
||||
ENV MAKE_VERSION="4.4.1-r1"
|
||||
# renovate: datasource=repology depName=alpine_3_18/gcc versioning=loose
|
||||
ENV GCC_VERSION="12.2.1_git20220924-r10"
|
||||
# renovate: datasource=repology depName=alpine_3_18/binutils-gold versioning=loose
|
||||
ENV BINUTILS_GOLD_VERSION="2.40-r7"
|
||||
# renovate: datasource=repology depName=alpine_3_18/musl-dev versioning=loose
|
||||
ENV MUSL_DEV_VERSION="1.2.4-r2"
|
||||
# renovate: datasource=repology depName=alpine_3_19/make versioning=loose
|
||||
ENV MAKE_VERSION="4.4.1-r2"
|
||||
# renovate: datasource=repology depName=alpine_3_19/gcc versioning=loose
|
||||
ENV GCC_VERSION="13.2.1_git20231014-r0"
|
||||
# renovate: datasource=repology depName=alpine_3_19/binutils-gold versioning=loose
|
||||
ENV BINUTILS_GOLD_VERSION="2.41-r0"
|
||||
# renovate: datasource=repology depName=alpine_3_19/musl-dev versioning=loose
|
||||
ENV MUSL_DEV_VERSION="1.2.4_git20230717-r4"
|
||||
# renovate: datasource=repology depName=alpine_3_19/protoc versioning=loose
|
||||
ENV PROTOC_VERSION="24.4-r0"
|
||||
|
||||
RUN apk add --no-cache --update make=${MAKE_VERSION} gcc=${GCC_VERSION} binutils-gold=${BINUTILS_GOLD_VERSION} musl-dev=${MUSL_DEV_VERSION} && \
|
||||
RUN apk add --no-cache --update make=${MAKE_VERSION} gcc=${GCC_VERSION} binutils-gold=${BINUTILS_GOLD_VERSION} musl-dev=${MUSL_DEV_VERSION} protoc=${PROTOC_VERSION} && \
|
||||
corepack enable
|
||||
|
||||
# Build packages.
|
||||
COPY --from=golang_image /usr/local/go /usr/local/go
|
||||
COPY Makefile /
|
||||
ENV PATH=$PATH:/usr/local/go/bin
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
|
||||
# Cache tools
|
||||
RUN make install-tools && \
|
||||
mv /root/go/bin/* /usr/local/go/bin/ && \
|
||||
chmod 755 /usr/local/go/bin/*
|
||||
RUN GOBIN=/usr/local/go/bin make install-tools && \
|
||||
rm -rf /Makefile
|
||||
|
||||
ENV GOPATH=/tmp/go
|
||||
ENV HOME=/tmp/home
|
||||
ENV PATH=$PATH:/usr/local/go/bin:/tmp/go/bin
|
||||
|
||||
WORKDIR /build
|
||||
RUN chmod -R 777 /root
|
||||
|
|
|
@ -161,6 +161,9 @@ Only build steps can define commands. You cannot use commands with plugins or se
|
|||
|
||||
Allows you to specify the entrypoint for containers. Note that this must be a list of the command and its arguments (e.g. `["/bin/sh", "-c"]`).
|
||||
|
||||
If you define [`commands`](#commands), the default entrypoint will be `["/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"]`.
|
||||
You can also use a custom shell with `CI_SCRIPT` (Base64-encoded) if you set `commands`.
|
||||
|
||||
### `environment`
|
||||
|
||||
Woodpecker provides the ability to pass environment variables to individual steps.
|
||||
|
@ -359,20 +362,6 @@ when:
|
|||
- platform: [linux/*, windows/amd64]
|
||||
```
|
||||
|
||||
<!-- markdownlint-disable no-duplicate-heading -->
|
||||
|
||||
#### `environment`
|
||||
|
||||
<!-- markdownlint-enable no-duplicate-heading -->
|
||||
|
||||
Execute a step for deployment events matching the target deployment environment:
|
||||
|
||||
```yaml
|
||||
when:
|
||||
- environment: production
|
||||
- event: deployment
|
||||
```
|
||||
|
||||
#### `matrix`
|
||||
|
||||
Execute a step for a single matrix permutation:
|
||||
|
@ -758,7 +747,7 @@ Workflows that should run even on failure should set the `runs_on` tag. See [her
|
|||
Woodpecker gives the ability to configure privileged mode in the YAML. You can use this parameter to launch containers with escalated capabilities.
|
||||
|
||||
:::info
|
||||
Privileged mode is only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./71-project-settings.md#trusted) to enable trusted mode.
|
||||
Privileged mode is only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./75-project-settings.md#trusted) to enable trusted mode.
|
||||
:::
|
||||
|
||||
```diff
|
||||
|
|
|
@ -6,7 +6,7 @@ In case there is a single configuration in `.woodpecker.yaml` Woodpecker will cr
|
|||
|
||||
By placing the configurations in a folder which is by default named `.woodpecker/` Woodpecker will create a pipeline with multiple workflows each named by the file they are defined in. Only `.yml` and `.yaml` files will be used and files in any subfolders like `.woodpecker/sub-folder/test.yaml` will be ignored.
|
||||
|
||||
You can also set some custom path like `.my-ci/pipelines/` instead of `.woodpecker/` in the [project settings](./71-project-settings.md).
|
||||
You can also set some custom path like `.my-ci/pipelines/` instead of `.woodpecker/` in the [project settings](./75-project-settings.md).
|
||||
|
||||
## Benefits of using workflows
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Woodpecker gives the ability to define Docker volumes in the YAML. You can use this parameter to mount files or folders on the host machine into your containers.
|
||||
|
||||
:::note
|
||||
Volumes are only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./71-project-settings.md#trusted) to enable trusted mode.
|
||||
Volumes are only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./75-project-settings.md#trusted) to enable trusted mode.
|
||||
:::
|
||||
|
||||
```diff
|
||||
|
|
62
docs/docs/20-usage/72-linter.md
Normal file
62
docs/docs/20-usage/72-linter.md
Normal file
|
@ -0,0 +1,62 @@
|
|||
# Linter
|
||||
|
||||
Woodpecker automatically lints your workflow files for errors, deprecations and bad habits. Errors and warnings are shown in the UI for any pipelines.
|
||||
|
||||
![errors and warnings in UI](./linter-warnings-errors.png)
|
||||
|
||||
## Running the linter from CLI
|
||||
|
||||
You can run the linter also manually from the CLI:
|
||||
|
||||
```shell
|
||||
woodpecker-cli lint <workflow files>
|
||||
```
|
||||
|
||||
## Bad habit warnings
|
||||
|
||||
Woodpecker warns you if your configuration contains some bad habits.
|
||||
|
||||
### Event filter for all steps
|
||||
|
||||
All your items in `when` blocks should have an `event` filter, so no step runs on all events. This is recommended because if new events are added, your steps probably shouldn't run on those as well.
|
||||
|
||||
Examples of an **incorrect** config for this rule:
|
||||
|
||||
```yaml
|
||||
when:
|
||||
- branch: main
|
||||
- event: tag
|
||||
```
|
||||
|
||||
This will trigger the warning because the first item (`branch: main`) does not filter with an event.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: test
|
||||
when:
|
||||
branch: main
|
||||
|
||||
- name: deploy
|
||||
when:
|
||||
event: tag
|
||||
```
|
||||
|
||||
Examples of a **correct** config for this rule:
|
||||
|
||||
```yaml
|
||||
when:
|
||||
- branch: main
|
||||
event: push
|
||||
- event: tag
|
||||
```
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: test
|
||||
when:
|
||||
event: [tag, push]
|
||||
|
||||
- name: deploy
|
||||
when:
|
||||
- event: tag
|
||||
```
|
BIN
docs/docs/20-usage/linter-warnings-errors.png
Normal file
BIN
docs/docs/20-usage/linter-warnings-errors.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
|
@ -1,6 +1,6 @@
|
|||
# Addon forges
|
||||
|
||||
If the forge you're using does not comply with [Woodpecker's requirements](../../92-development/02-core-ideas.md#forge) or your setup is too specific to be added to Woodpecker's core, you can write your own forge using an addon forge.
|
||||
If the forge you're using does not comply with [Woodpecker's requirements](../../92-development/02-core-ideas.md#forges) or your setup is too specific to be added to Woodpecker's core, you can write your own forge using an addon forge.
|
||||
|
||||
:::warning
|
||||
Addon forges are still experimental. Their implementation can change and break at any time.
|
||||
|
|
|
@ -11,6 +11,7 @@ Some versions need some changes to the server configuration or the pipeline conf
|
|||
- Deprecated 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)
|
||||
- Deprecated alternative names for secrets, use `environment` with `from_secret`
|
||||
- Deprecated slice definition for env vars
|
||||
- Deprecated `environment` filter, use `when.evaluate`
|
||||
|
||||
## 2.0.0
|
||||
|
||||
|
@ -66,7 +67,7 @@ Some versions need some changes to the server configuration or the pipeline conf
|
|||
|
||||
Only projects created after updating will have an empty value by default. Existing projects will stick to the current pipeline path which is `.drone.yml` in most cases.
|
||||
|
||||
Read more about it at the [Project Settings](./20-usage/71-project-settings.md#pipeline-path)
|
||||
Read more about it at the [Project Settings](./20-usage/75-project-settings.md#pipeline-path)
|
||||
|
||||
- From version `0.15.0` ongoing there will be three types of docker images: `latest`, `next` and `x.x.x` with an alpine variant for each type like `latest-alpine`.
|
||||
If you used `latest` before to try pre-release features you should switch to `next` after this release.
|
||||
|
|
|
@ -10,6 +10,7 @@ const config: Config = {
|
|||
baseUrl: '/',
|
||||
onBrokenLinks: 'throw',
|
||||
onBrokenMarkdownLinks: 'throw',
|
||||
onBrokenAnchors: 'throw',
|
||||
onDuplicateRoutes: 'throw',
|
||||
organizationName: 'woodpecker-ci',
|
||||
projectName: 'woodpecker-ci.github.io',
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.1.0",
|
||||
"@docusaurus/tsconfig": "3.1.1",
|
||||
"@docusaurus/tsconfig": "3.2.1",
|
||||
"@docusaurus/types": "^3.1.0",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/react": "^18.2.67",
|
||||
|
@ -53,8 +53,8 @@
|
|||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"trim": "^0.0.3",
|
||||
"got": "^11.8.5"
|
||||
"trim": "^1.0.0",
|
||||
"got": "^14.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,10 +190,20 @@
|
|||
"docs": "https://codeberg.org/woodpecker-plugins/mastodon-post/raw/branch/main/docs.md",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"name": "Discord",
|
||||
"docs": "https://raw.githubusercontent.com/appleboy/drone-discord/master/DOCS.md",
|
||||
"verified": false
|
||||
},
|
||||
{
|
||||
"name": "Forge deployments",
|
||||
"docs": "https://raw.githubusercontent.com/woodpecker-ci/plugin-deployments/main/docs.md",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"name": "Twine",
|
||||
"docs": "https://gitea.elara.ws/music-kraken/plugin-twine/raw/branch/master/docs.md",
|
||||
"verified": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
12333
docs/pnpm-lock.yaml
12333
docs/pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -640,7 +640,7 @@ You can manually configure the clone step in your workflow for customization:
|
|||
|
||||
```diff
|
||||
+clone:
|
||||
+ git:
|
||||
+ - name: git
|
||||
+ image: woodpeckerci/plugin-git
|
||||
|
||||
steps:
|
||||
|
@ -666,7 +666,7 @@ Example configuration to use a custom clone plugin:
|
|||
|
||||
```diff
|
||||
clone:
|
||||
git:
|
||||
- name: git
|
||||
+ image: octocat/custom-git-plugin
|
||||
```
|
||||
|
||||
|
|
8
go.mod
8
go.mod
|
@ -57,11 +57,11 @@ require (
|
|||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/zalando/go-keyring v0.2.4
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/net v0.22.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/oauth2 v0.18.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/term v0.18.0
|
||||
golang.org/x/term v0.19.0
|
||||
golang.org/x/text v0.14.0
|
||||
google.golang.org/grpc v1.62.1
|
||||
google.golang.org/protobuf v1.33.0
|
||||
|
@ -170,7 +170,7 @@ require (
|
|||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/arch v0.6.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.16.1 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
|
|
16
go.sum
16
go.sum
|
@ -540,8 +540,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||
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.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
|
@ -573,8 +573,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||
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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
|
||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
|
||||
|
@ -619,16 +619,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
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=
|
||||
|
|
|
@ -18,21 +18,19 @@ import (
|
|||
"encoding/base64"
|
||||
)
|
||||
|
||||
func GenerateContainerConf(commands []string, goos string) (env map[string]string, entry []string, cmd string) {
|
||||
func GenerateContainerConf(commands []string, goos string) (env map[string]string, entry []string) {
|
||||
env = make(map[string]string)
|
||||
if goos == "windows" {
|
||||
env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptWindows(commands)))
|
||||
env["HOME"] = "c:\\root"
|
||||
env["SHELL"] = "powershell.exe"
|
||||
entry = []string{"powershell", "-noprofile", "-noninteractive", "-command"}
|
||||
cmd = "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"
|
||||
entry = []string{"powershell", "-noprofile", "-noninteractive", "-command", "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"}
|
||||
} else {
|
||||
env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptPosix(commands)))
|
||||
env["HOME"] = "/root"
|
||||
env["SHELL"] = "/bin/sh"
|
||||
entry = []string{"/bin/sh", "-c"}
|
||||
cmd = "echo $CI_SCRIPT | base64 -d | /bin/sh -e"
|
||||
entry = []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"}
|
||||
}
|
||||
|
||||
return env, entry, cmd
|
||||
return env, entry
|
||||
}
|
||||
|
|
|
@ -12,16 +12,14 @@ const (
|
|||
)
|
||||
|
||||
func TestGenerateContainerConf(t *testing.T) {
|
||||
gotEnv, gotEntry, gotCmd := GenerateContainerConf([]string{"echo hello world"}, "windows")
|
||||
gotEnv, gotEntry := GenerateContainerConf([]string{"echo hello world"}, "windows")
|
||||
assert.Equal(t, windowsScriptBase64, gotEnv["CI_SCRIPT"])
|
||||
assert.Equal(t, "c:\\root", gotEnv["HOME"])
|
||||
assert.Equal(t, "powershell.exe", gotEnv["SHELL"])
|
||||
assert.Equal(t, []string{"powershell", "-noprofile", "-noninteractive", "-command"}, gotEntry)
|
||||
assert.Equal(t, "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex", gotCmd)
|
||||
gotEnv, gotEntry, gotCmd = GenerateContainerConf([]string{"echo hello world"}, "linux")
|
||||
assert.Equal(t, []string{"powershell", "-noprofile", "-noninteractive", "-command", "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"}, gotEntry)
|
||||
gotEnv, gotEntry = GenerateContainerConf([]string{"echo hello world"}, "linux")
|
||||
assert.Equal(t, posixScriptBase64, gotEnv["CI_SCRIPT"])
|
||||
assert.Equal(t, "/root", gotEnv["HOME"])
|
||||
assert.Equal(t, "/bin/sh", gotEnv["SHELL"])
|
||||
assert.Equal(t, []string{"/bin/sh", "-c"}, gotEntry)
|
||||
assert.Equal(t, "echo $CI_SCRIPT | base64 -d | /bin/sh -e", gotCmd)
|
||||
assert.Equal(t, []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, gotEntry)
|
||||
}
|
||||
|
|
|
@ -45,16 +45,15 @@ func (e *docker) toConfig(step *types.Step) *container.Config {
|
|||
configEnv := make(map[string]string)
|
||||
maps.Copy(configEnv, step.Environment)
|
||||
|
||||
if len(step.Commands) != 0 {
|
||||
env, entry, cmd := common.GenerateContainerConf(step.Commands, e.info.OSType)
|
||||
if len(step.Commands) > 0 {
|
||||
env, entry := common.GenerateContainerConf(step.Commands, e.info.OSType)
|
||||
for k, v := range env {
|
||||
configEnv[k] = v
|
||||
}
|
||||
if len(step.Entrypoint) > 0 {
|
||||
entry = step.Entrypoint
|
||||
}
|
||||
config.Entrypoint = entry
|
||||
config.Cmd = []string{cmd}
|
||||
}
|
||||
if len(step.Entrypoint) > 0 {
|
||||
config.Entrypoint = step.Entrypoint
|
||||
}
|
||||
|
||||
if len(configEnv) != 0 {
|
||||
|
|
|
@ -105,8 +105,7 @@ func TestToConfigSmall(t *testing.T) {
|
|||
assert.EqualValues(t, &container.Config{
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Cmd: []string{"echo $CI_SCRIPT | base64 -d | /bin/sh -e"},
|
||||
Entrypoint: []string{"/bin/sh", "-c"},
|
||||
Entrypoint: []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"},
|
||||
Labels: map[string]string{
|
||||
"wp_step": "test",
|
||||
"wp_uuid": "09238932",
|
||||
|
@ -163,8 +162,7 @@ func TestToConfigFull(t *testing.T) {
|
|||
WorkingDir: "/src/abc",
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Cmd: []string{"echo $CI_SCRIPT | base64 -d | /bin/sh -e"},
|
||||
Entrypoint: []string{"/bin/sh", "-c"},
|
||||
Entrypoint: []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"},
|
||||
Labels: map[string]string{
|
||||
"wp_step": "test",
|
||||
"wp_uuid": "09238932",
|
||||
|
|
|
@ -147,15 +147,14 @@ func podContainer(step *types.Step, podName, goos string, options BackendOptions
|
|||
container.ImagePullPolicy = v1.PullAlways
|
||||
}
|
||||
|
||||
if len(step.Commands) != 0 {
|
||||
scriptEnv, command, args := common.GenerateContainerConf(step.Commands, goos)
|
||||
if len(step.Entrypoint) > 0 {
|
||||
command = step.Entrypoint
|
||||
}
|
||||
if len(step.Commands) > 0 {
|
||||
scriptEnv, command := common.GenerateContainerConf(step.Commands, goos)
|
||||
container.Command = command
|
||||
container.Args = []string{args}
|
||||
maps.Copy(step.Environment, scriptEnv)
|
||||
}
|
||||
if len(step.Entrypoint) > 0 {
|
||||
container.Command = step.Entrypoint
|
||||
}
|
||||
|
||||
container.Env = mapToEnvVars(step.Environment)
|
||||
|
||||
|
|
|
@ -90,9 +90,7 @@ func TestTinyPod(t *testing.T) {
|
|||
"image": "gradle:8.4.0-jdk21",
|
||||
"command": [
|
||||
"/bin/sh",
|
||||
"-c"
|
||||
],
|
||||
"args": [
|
||||
"-c",
|
||||
"echo $CI_SCRIPT | base64 -d | /bin/sh -e"
|
||||
],
|
||||
"workingDir": "/woodpecker/src",
|
||||
|
@ -183,9 +181,6 @@ func TestFullPod(t *testing.T) {
|
|||
"/bin/sh",
|
||||
"-c"
|
||||
],
|
||||
"args": [
|
||||
"echo $CI_SCRIPT | base64 -d | /bin/sh -e"
|
||||
],
|
||||
"workingDir": "/woodpecker/src",
|
||||
"ports": [
|
||||
{
|
||||
|
@ -415,3 +410,49 @@ func TestPodPrivilege(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, *pod.Spec.SecurityContext.RunAsNonRoot)
|
||||
}
|
||||
|
||||
func TestScratchPod(t *testing.T) {
|
||||
expected := `
|
||||
{
|
||||
"metadata": {
|
||||
"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
|
||||
"namespace": "woodpecker",
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"step": "curl-google"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
|
||||
"image": "quay.io/curl/curl",
|
||||
"command": [
|
||||
"/usr/bin/curl",
|
||||
"-v",
|
||||
"google.com"
|
||||
],
|
||||
"resources": {}
|
||||
}
|
||||
],
|
||||
"restartPolicy": "Never"
|
||||
},
|
||||
"status": {}
|
||||
}`
|
||||
|
||||
pod, err := mkPod(&types.Step{
|
||||
Name: "curl-google",
|
||||
Image: "quay.io/curl/curl",
|
||||
Entrypoint: []string{"/usr/bin/curl", "-v", "google.com"},
|
||||
}, &config{
|
||||
Namespace: "woodpecker",
|
||||
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
podJSON, err := json.Marshal(pod)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ja := jsonassert.New(t)
|
||||
t.Log(string(podJSON))
|
||||
ja.Assertf(string(podJSON), expected)
|
||||
}
|
||||
|
|
|
@ -19,6 +19,12 @@ type DeprecationErrorData struct {
|
|||
Docs string `json:"docs"`
|
||||
}
|
||||
|
||||
type BadHabitErrorData struct {
|
||||
File string `json:"file"`
|
||||
Field string `json:"field"`
|
||||
Docs string `json:"docs"`
|
||||
}
|
||||
|
||||
func GetLinterData(e *types.PipelineError) *LinterErrorData {
|
||||
if e.Type != types.PipelineErrorTypeLinter {
|
||||
return nil
|
||||
|
|
|
@ -305,7 +305,39 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
Field: fmt.Sprintf("steps.%s.secrets[%d]", step.Name, i),
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#event",
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/secrets#use-secrets-in-settings-and-environment",
|
||||
},
|
||||
IsWarning: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, c := range parsed.When.Constraints {
|
||||
if !c.Environment.IsEmpty() {
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "environment filters are deprecated, use evaluate with CI_PIPELINE_DEPLOY_TARGET",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
Field: fmt.Sprintf("when[%d].environment", i),
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate",
|
||||
},
|
||||
IsWarning: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, step := range parsed.Steps.ContainerList {
|
||||
for i, c := range step.When.Constraints {
|
||||
if !c.Environment.IsEmpty() {
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "environment filters are deprecated, use evaluate with CI_PIPELINE_DEPLOY_TARGET",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
Field: fmt.Sprintf("steps.%s.when[%d].environment", step.Name, i),
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate",
|
||||
},
|
||||
IsWarning: true,
|
||||
})
|
||||
|
@ -351,10 +383,11 @@ func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) {
|
|||
if field != "" {
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeBadHabit,
|
||||
Message: "Please set an event filter on all when branches",
|
||||
Data: errors.LinterErrorData{
|
||||
Message: "Please set an event filter for all steps or the whole workflow on all items of the when block",
|
||||
Data: errors.BadHabitErrorData{
|
||||
File: config.File,
|
||||
Field: field,
|
||||
Docs: "https://woodpecker-ci.org/docs/usage/linter#event-filter-for-all-steps",
|
||||
},
|
||||
IsWarning: true,
|
||||
})
|
||||
|
|
|
@ -189,11 +189,11 @@ func TestBadHabits(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
from: "steps: { build: { image: golang } }",
|
||||
want: "Please set an event filter on all when branches",
|
||||
want: "Please set an event filter for all steps or the whole workflow on all items of the when block",
|
||||
},
|
||||
{
|
||||
from: "when: [{branch: xyz}, {event: push}]\nsteps: { build: { image: golang } }",
|
||||
want: "Please set an event filter on all when branches",
|
||||
want: "Please set an event filter for all steps or the whole workflow on all items of the when block",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,14 @@ steps:
|
|||
image: golang
|
||||
commands: go test
|
||||
|
||||
entrypoint:
|
||||
image: alpine
|
||||
entrypoint: ['some_entry', '--some-flag']
|
||||
|
||||
singla-entrypoint:
|
||||
image: alpine
|
||||
entrypoint: some_entry
|
||||
|
||||
commands:
|
||||
privileged: true
|
||||
image: golang
|
||||
|
|
|
@ -371,6 +371,21 @@
|
|||
},
|
||||
"backend_options": {
|
||||
"$ref": "#/definitions/step_backend_options"
|
||||
},
|
||||
"entrypoint": {
|
||||
"description": "Defines container entrypoint.",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"minLength": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -16,8 +16,3 @@ package proto
|
|||
|
||||
//go:generate protoc --go_out=paths=source_relative:. woodpecker.proto
|
||||
//go:generate protoc --go-grpc_out=paths=source_relative:. woodpecker.proto
|
||||
|
||||
// install protoc: https://grpc.io/docs/protoc-installation/
|
||||
// and get needed binary's:
|
||||
// go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
// go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.32.0
|
||||
// protoc v4.25.1
|
||||
// protoc-gen-go v1.33.0
|
||||
// protoc v4.24.4
|
||||
// source: woodpecker.proto
|
||||
|
||||
package proto
|
||||
|
|
|
@ -1,22 +1,7 @@
|
|||
// Copyright 2021 Woodpecker Authors
|
||||
// Copyright 2011 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.
|
||||
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc v4.25.1
|
||||
// - protoc v4.24.4
|
||||
// source: woodpecker.proto
|
||||
|
||||
package proto
|
||||
|
@ -74,7 +59,7 @@ func NewWoodpeckerClient(cc grpc.ClientConnInterface) WoodpeckerClient {
|
|||
|
||||
func (c *woodpeckerClient) Version(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VersionResponse, error) {
|
||||
out := new(VersionResponse)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_Version_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/Version", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -83,7 +68,7 @@ func (c *woodpeckerClient) Version(ctx context.Context, in *Empty, opts ...grpc.
|
|||
|
||||
func (c *woodpeckerClient) Next(ctx context.Context, in *NextRequest, opts ...grpc.CallOption) (*NextResponse, error) {
|
||||
out := new(NextResponse)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_Next_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/Next", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -92,7 +77,7 @@ func (c *woodpeckerClient) Next(ctx context.Context, in *NextRequest, opts ...gr
|
|||
|
||||
func (c *woodpeckerClient) Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_Init_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/Init", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -101,7 +86,7 @@ func (c *woodpeckerClient) Init(ctx context.Context, in *InitRequest, opts ...gr
|
|||
|
||||
func (c *woodpeckerClient) Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_Wait_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/Wait", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -110,7 +95,7 @@ func (c *woodpeckerClient) Wait(ctx context.Context, in *WaitRequest, opts ...gr
|
|||
|
||||
func (c *woodpeckerClient) Done(ctx context.Context, in *DoneRequest, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_Done_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/Done", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -119,7 +104,7 @@ func (c *woodpeckerClient) Done(ctx context.Context, in *DoneRequest, opts ...gr
|
|||
|
||||
func (c *woodpeckerClient) Extend(ctx context.Context, in *ExtendRequest, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_Extend_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/Extend", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -128,7 +113,7 @@ func (c *woodpeckerClient) Extend(ctx context.Context, in *ExtendRequest, opts .
|
|||
|
||||
func (c *woodpeckerClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_Update_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/Update", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -137,7 +122,7 @@ func (c *woodpeckerClient) Update(ctx context.Context, in *UpdateRequest, opts .
|
|||
|
||||
func (c *woodpeckerClient) Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_Log_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/Log", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -146,7 +131,7 @@ func (c *woodpeckerClient) Log(ctx context.Context, in *LogRequest, opts ...grpc
|
|||
|
||||
func (c *woodpeckerClient) RegisterAgent(ctx context.Context, in *RegisterAgentRequest, opts ...grpc.CallOption) (*RegisterAgentResponse, error) {
|
||||
out := new(RegisterAgentResponse)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_RegisterAgent_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/RegisterAgent", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -164,7 +149,7 @@ func (c *woodpeckerClient) UnregisterAgent(ctx context.Context, in *Empty, opts
|
|||
|
||||
func (c *woodpeckerClient) ReportHealth(ctx context.Context, in *ReportHealthRequest, opts ...grpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := c.cc.Invoke(ctx, Woodpecker_ReportHealth_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.Woodpecker/ReportHealth", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -249,7 +234,7 @@ func _Woodpecker_Version_Handler(srv interface{}, ctx context.Context, dec func(
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_Version_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/Version",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).Version(ctx, req.(*Empty))
|
||||
|
@ -267,7 +252,7 @@ func _Woodpecker_Next_Handler(srv interface{}, ctx context.Context, dec func(int
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_Next_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/Next",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).Next(ctx, req.(*NextRequest))
|
||||
|
@ -285,7 +270,7 @@ func _Woodpecker_Init_Handler(srv interface{}, ctx context.Context, dec func(int
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_Init_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/Init",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).Init(ctx, req.(*InitRequest))
|
||||
|
@ -303,7 +288,7 @@ func _Woodpecker_Wait_Handler(srv interface{}, ctx context.Context, dec func(int
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_Wait_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/Wait",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).Wait(ctx, req.(*WaitRequest))
|
||||
|
@ -321,7 +306,7 @@ func _Woodpecker_Done_Handler(srv interface{}, ctx context.Context, dec func(int
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_Done_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/Done",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).Done(ctx, req.(*DoneRequest))
|
||||
|
@ -339,7 +324,7 @@ func _Woodpecker_Extend_Handler(srv interface{}, ctx context.Context, dec func(i
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_Extend_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/Extend",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).Extend(ctx, req.(*ExtendRequest))
|
||||
|
@ -357,7 +342,7 @@ func _Woodpecker_Update_Handler(srv interface{}, ctx context.Context, dec func(i
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_Update_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/Update",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).Update(ctx, req.(*UpdateRequest))
|
||||
|
@ -375,7 +360,7 @@ func _Woodpecker_Log_Handler(srv interface{}, ctx context.Context, dec func(inte
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_Log_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/Log",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).Log(ctx, req.(*LogRequest))
|
||||
|
@ -393,7 +378,7 @@ func _Woodpecker_RegisterAgent_Handler(srv interface{}, ctx context.Context, dec
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_RegisterAgent_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/RegisterAgent",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).RegisterAgent(ctx, req.(*RegisterAgentRequest))
|
||||
|
@ -429,7 +414,7 @@ func _Woodpecker_ReportHealth_Handler(srv interface{}, ctx context.Context, dec
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: Woodpecker_ReportHealth_FullMethodName,
|
||||
FullMethod: "/proto.Woodpecker/ReportHealth",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerServer).ReportHealth(ctx, req.(*ReportHealthRequest))
|
||||
|
@ -493,10 +478,6 @@ var Woodpecker_ServiceDesc = grpc.ServiceDesc{
|
|||
Metadata: "woodpecker.proto",
|
||||
}
|
||||
|
||||
const (
|
||||
WoodpeckerAuth_Auth_FullMethodName = "/proto.WoodpeckerAuth/Auth"
|
||||
)
|
||||
|
||||
// WoodpeckerAuthClient is the client API for WoodpeckerAuth service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
|
@ -514,7 +495,7 @@ func NewWoodpeckerAuthClient(cc grpc.ClientConnInterface) WoodpeckerAuthClient {
|
|||
|
||||
func (c *woodpeckerAuthClient) Auth(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthResponse, error) {
|
||||
out := new(AuthResponse)
|
||||
err := c.cc.Invoke(ctx, WoodpeckerAuth_Auth_FullMethodName, in, out, opts...)
|
||||
err := c.cc.Invoke(ctx, "/proto.WoodpeckerAuth/Auth", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -559,7 +540,7 @@ func _WoodpeckerAuth_Auth_Handler(srv interface{}, ctx context.Context, dec func
|
|||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: WoodpeckerAuth_Auth_FullMethodName,
|
||||
FullMethod: "/proto.WoodpeckerAuth/Auth",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(WoodpeckerAuthServer).Auth(ctx, req.(*AuthRequest))
|
||||
|
|
|
@ -30,7 +30,7 @@ import (
|
|||
|
||||
// GetAgents
|
||||
//
|
||||
// @Summary Get agent list
|
||||
// @Summary List agents
|
||||
// @Router /agents [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Agent
|
||||
|
@ -49,7 +49,7 @@ func GetAgents(c *gin.Context) {
|
|||
|
||||
// GetAgent
|
||||
//
|
||||
// @Summary Get agent information
|
||||
// @Summary Get an agent
|
||||
// @Router /agents/{agent} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
|
@ -73,7 +73,7 @@ func GetAgent(c *gin.Context) {
|
|||
|
||||
// GetAgentTasks
|
||||
//
|
||||
// @Summary Get agent tasks
|
||||
// @Summary List agent tasks
|
||||
// @Router /agents/{agent}/tasks [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Task
|
||||
|
@ -106,7 +106,7 @@ func GetAgentTasks(c *gin.Context) {
|
|||
|
||||
// PatchAgent
|
||||
//
|
||||
// @Summary Update agent information
|
||||
// @Summary Update an agent
|
||||
// @Router /agents/{agent} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
|
@ -152,7 +152,8 @@ func PatchAgent(c *gin.Context) {
|
|||
|
||||
// PostAgent
|
||||
//
|
||||
// @Summary Create a new agent with a random token so a new agent can connect to the server
|
||||
// @Summary Create a new agent
|
||||
// @Description Creates a new agent with a random token
|
||||
// @Router /agents [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
|
|
|
@ -37,7 +37,7 @@ import (
|
|||
|
||||
// GetBadge
|
||||
//
|
||||
// @Summary Get status badge, SVG format
|
||||
// @Summary Get status of pipeline as SVG badge
|
||||
// @Router /badges/{repo_id}/status.svg [get]
|
||||
// @Produce image/svg+xml
|
||||
// @Success 200
|
||||
|
@ -120,7 +120,7 @@ func GetCC(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
pipelines, err := _store.GetPipelineList(repo, &model.ListOptions{Page: 1, PerPage: 1})
|
||||
pipelines, err := _store.GetPipelineList(repo, &model.ListOptions{Page: 1, PerPage: 1}, nil)
|
||||
if err != nil && !errors.Is(err, types.RecordNotExist) {
|
||||
log.Warn().Err(err).Msg("could not get pipeline list")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
cronScheduler "go.woodpecker-ci.org/woodpecker/v2/server/cron"
|
||||
|
@ -31,7 +32,7 @@ import (
|
|||
|
||||
// GetCron
|
||||
//
|
||||
// @Summary Get a cron job by id
|
||||
// @Summary Get a cron job
|
||||
// @Router /repos/{repo_id}/cron/{cron} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Cron
|
||||
|
@ -80,7 +81,7 @@ func RunCron(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
repo, newPipeline, err := cronScheduler.CreatePipeline(c, _store, server.Config.Services.Forge, cron)
|
||||
repo, newPipeline, err := cronScheduler.CreatePipeline(c, _store, cron)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error creating pipeline for cron %q. %s", id, err)
|
||||
return
|
||||
|
@ -97,7 +98,7 @@ func RunCron(c *gin.Context) {
|
|||
|
||||
// PostCron
|
||||
//
|
||||
// @Summary Persist/creat a cron job
|
||||
// @Summary Create a cron job
|
||||
// @Router /repos/{repo_id}/cron [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Cron
|
||||
|
@ -109,7 +110,12 @@ func PostCron(c *gin.Context) {
|
|||
repo := session.Repo(c)
|
||||
user := session.User(c)
|
||||
_store := store.FromContext(c)
|
||||
forge := server.Config.Services.Forge
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from repo")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
in := new(model.Cron)
|
||||
if err := c.Bind(in); err != nil {
|
||||
|
@ -137,7 +143,7 @@ func PostCron(c *gin.Context) {
|
|||
|
||||
if in.Branch != "" {
|
||||
// check if branch exists on forge
|
||||
_, err := forge.BranchHead(c, user, repo, in.Branch)
|
||||
_, err := _forge.BranchHead(c, user, repo, in.Branch)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error inserting cron. branch not resolved: %s", err)
|
||||
return
|
||||
|
@ -166,7 +172,12 @@ func PatchCron(c *gin.Context) {
|
|||
repo := session.Repo(c)
|
||||
user := session.User(c)
|
||||
_store := store.FromContext(c)
|
||||
forge := server.Config.Services.Forge
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from repo")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(c.Param("cron"), 10, 64)
|
||||
if err != nil {
|
||||
|
@ -188,7 +199,7 @@ func PatchCron(c *gin.Context) {
|
|||
}
|
||||
if in.Branch != "" {
|
||||
// check if branch exists on forge
|
||||
_, err := forge.BranchHead(c, user, repo, in.Branch)
|
||||
_, err := _forge.BranchHead(c, user, repo, in.Branch)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error inserting cron. branch not resolved: %s", err)
|
||||
return
|
||||
|
@ -222,7 +233,7 @@ func PatchCron(c *gin.Context) {
|
|||
|
||||
// GetCronList
|
||||
//
|
||||
// @Summary Get the cron job list
|
||||
// @Summary List cron jobs
|
||||
// @Router /repos/{repo_id}/cron [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Cron
|
||||
|
@ -243,7 +254,7 @@ func GetCronList(c *gin.Context) {
|
|||
|
||||
// DeleteCron
|
||||
//
|
||||
// @Summary Delete a cron job by id
|
||||
// @Summary Delete a cron job
|
||||
// @Router /repos/{repo_id}/cron/{cron} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
// GetGlobalSecretList
|
||||
//
|
||||
// @Summary Get the global secret list
|
||||
// @Summary List global secrets
|
||||
// @Router /secrets [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Secret
|
||||
|
@ -71,7 +71,7 @@ func GetGlobalSecret(c *gin.Context) {
|
|||
|
||||
// PostGlobalSecret
|
||||
//
|
||||
// @Summary Persist/create a global secret
|
||||
// @Summary Create a global secret
|
||||
// @Router /secrets [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Secret
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
|
@ -54,7 +55,23 @@ func handleDBError(c *gin.Context, err error) {
|
|||
// If the forge has a refresh token, the current access token may be stale.
|
||||
// Therefore, we should refresh prior to dispatching the job.
|
||||
func refreshUserToken(c *gin.Context, user *model.User) {
|
||||
_forge := server.Config.Services.Forge
|
||||
_store := store.FromContext(c)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromUser(user)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from user")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
forge.Refresh(c, _forge, _store, user)
|
||||
}
|
||||
|
||||
// pipelineDeleteAllowed checks if the given pipeline can be deleted based on its status.
|
||||
// It returns a bool indicating if delete is allowed, and the pipeline's status.
|
||||
func pipelineDeleteAllowed(pl *model.Pipeline) bool {
|
||||
switch pl.Status {
|
||||
case model.StatusRunning, model.StatusPending, model.StatusBlocked:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ func GetQueueInfo(c *gin.Context) {
|
|||
|
||||
// PauseQueue
|
||||
//
|
||||
// @Summary Pause a pipeline queue
|
||||
// @Summary Pause the pipeline queue
|
||||
// @Router /queue/pause [post]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
|
@ -65,7 +65,7 @@ func PauseQueue(c *gin.Context) {
|
|||
|
||||
// ResumeQueue
|
||||
//
|
||||
// @Summary Resume a pipeline queue
|
||||
// @Summary Resume the pipeline queue
|
||||
// @Router /queue/resume [post]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
|
@ -104,7 +104,13 @@ func BlockTilQueueHasRunningItem(c *gin.Context) {
|
|||
// @Param hook body object true "the webhook payload; forge is automatically detected"
|
||||
func PostHook(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
_forge := server.Config.Services.Forge
|
||||
|
||||
_forge, err := server.Config.Services.Manager.ForgeMain() // TODO: get the forge for the specific repo somehow
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get main forge")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// 1. Parse webhook
|
||||
|
|
|
@ -43,7 +43,12 @@ func HandleLogin(c *gin.Context) {
|
|||
|
||||
func HandleAuth(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
_forge := server.Config.Services.Forge
|
||||
_forge, err := server.Config.Services.Manager.ForgeMain()
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
forgeID := int64(1) // TODO: replace with forge id when multiple forges are supported
|
||||
|
||||
// when dealing with redirects, we may need to adjust the content type. I
|
||||
// cannot, however, remember why, so need to revisit this line.
|
||||
|
@ -68,12 +73,12 @@ func HandleAuth(c *gin.Context) {
|
|||
|
||||
// get the user from the database
|
||||
u, err := _store.GetUserRemoteID(tmpuser.ForgeRemoteID, tmpuser.Login)
|
||||
if err != nil {
|
||||
if !errors.Is(err, types.RecordNotExist) {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if err != nil && !errors.Is(err, types.RecordNotExist) {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
if errors.Is(err, types.RecordNotExist) {
|
||||
// if self-registration is disabled we should return a not authorized error
|
||||
if !server.Config.Permissions.Open && !server.Config.Permissions.Admins.IsAdmin(tmpuser) {
|
||||
log.Error().Msgf("cannot register %s. registration closed", tmpuser.Login)
|
||||
|
@ -100,6 +105,7 @@ func HandleAuth(c *gin.Context) {
|
|||
Secret: tmpuser.Secret,
|
||||
Email: tmpuser.Email,
|
||||
Avatar: tmpuser.Avatar,
|
||||
ForgeID: forgeID,
|
||||
Hash: base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32),
|
||||
),
|
||||
|
@ -129,6 +135,7 @@ func HandleAuth(c *gin.Context) {
|
|||
Name: u.Login,
|
||||
IsUser: true,
|
||||
Private: false,
|
||||
ForgeID: u.ForgeID,
|
||||
}
|
||||
if err := _store.OrgCreate(org); err != nil {
|
||||
log.Error().Err(err).Msgf("on user creation, could create org for user")
|
||||
|
@ -228,14 +235,21 @@ func GetLogout(c *gin.Context) {
|
|||
func GetLoginToken(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
_forge, err := server.Config.Services.Manager.ForgeMain() // TODO: get selected forge from auth request
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get main forge")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
in := &tokenPayload{}
|
||||
err := c.Bind(in)
|
||||
err = c.Bind(in)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
login, err := server.Config.Services.Forge.Auth(c, in.Access, in.Refresh)
|
||||
login, err := _forge.Auth(c, in.Access, in.Refresh)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusUnauthorized, err)
|
||||
return
|
||||
|
|
|
@ -30,7 +30,7 @@ import (
|
|||
|
||||
// GetOrg
|
||||
//
|
||||
// @Summary Get organization by id
|
||||
// @Summary Get an organization
|
||||
// @Router /orgs/{org_id} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Org
|
||||
|
@ -57,7 +57,7 @@ func GetOrg(c *gin.Context) {
|
|||
|
||||
// GetOrgPermissions
|
||||
//
|
||||
// @Summary Get the permissions of the current user in the given organization
|
||||
// @Summary Get the permissions of the currently authenticated user for the given organization
|
||||
// @Router /orgs/{org_id}/permissions [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} OrgPerm
|
||||
|
@ -68,6 +68,13 @@ func GetOrgPermissions(c *gin.Context) {
|
|||
user := session.User(c)
|
||||
_store := store.FromContext(c)
|
||||
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromUser(user)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from user")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
|
@ -96,7 +103,7 @@ func GetOrgPermissions(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
perm, err := server.Config.Services.Membership.Get(c, user, org.Name)
|
||||
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)
|
||||
return
|
||||
|
@ -107,15 +114,22 @@ func GetOrgPermissions(c *gin.Context) {
|
|||
|
||||
// LookupOrg
|
||||
//
|
||||
// @Summary Lookup organization by full-name
|
||||
// @Summary Lookup an organization by full name
|
||||
// @Router /org/lookup/{org_full_name} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Org
|
||||
// @Tags Organizations
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_full_name path string true "the organizations full-name / slug"
|
||||
// @Param org_full_name path string true "the organizations full name / slug"
|
||||
func LookupOrg(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
user := session.User(c)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromUser(user)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from user")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
orgFullName := strings.TrimLeft(c.Param("org_full_name"), "/")
|
||||
|
||||
|
@ -137,7 +151,7 @@ func LookupOrg(c *gin.Context) {
|
|||
c.AbortWithStatus(http.StatusNotFound)
|
||||
return
|
||||
} else if !user.Admin {
|
||||
perm, err := server.Config.Services.Membership.Get(c, user, org.Name)
|
||||
perm, err := server.Config.Services.Membership.Get(c, _forge, user, org.Name)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to check membership")
|
||||
c.Status(http.StatusInternalServerError)
|
||||
|
|
|
@ -27,7 +27,7 @@ import (
|
|||
|
||||
// GetOrgSecret
|
||||
//
|
||||
// @Summary Get the named organization secret
|
||||
// @Summary Get a organization secret by name
|
||||
// @Router /orgs/{org_id}/secrets/{secret} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Secret
|
||||
|
@ -55,7 +55,7 @@ func GetOrgSecret(c *gin.Context) {
|
|||
|
||||
// GetOrgSecretList
|
||||
//
|
||||
// @Summary Get the organization secret list
|
||||
// @Summary List organization secrets
|
||||
// @Router /orgs/{org_id}/secrets [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Secret
|
||||
|
@ -87,7 +87,7 @@ func GetOrgSecretList(c *gin.Context) {
|
|||
|
||||
// PostOrgSecret
|
||||
//
|
||||
// @Summary Persist/create an organization secret
|
||||
// @Summary Create an organization secret
|
||||
// @Router /orgs/{org_id}/secrets [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Secret
|
||||
|
@ -129,7 +129,7 @@ func PostOrgSecret(c *gin.Context) {
|
|||
|
||||
// PatchOrgSecret
|
||||
//
|
||||
// @Summary Update an organization secret
|
||||
// @Summary Update an organization secret by name
|
||||
// @Router /orgs/{org_id}/secrets/{secret} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Secret
|
||||
|
@ -183,7 +183,7 @@ func PatchOrgSecret(c *gin.Context) {
|
|||
|
||||
// DeleteOrgSecret
|
||||
//
|
||||
// @Summary Delete the named secret from an organization
|
||||
// @Summary Delete an organization secret by name
|
||||
// @Router /orgs/{org_id}/secrets/{secret} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
// GetOrgs
|
||||
//
|
||||
// @Summary Get all orgs
|
||||
// @Summary List organizations
|
||||
// @Description Returns all registered orgs in the system. Requires admin rights.
|
||||
// @Router /orgs [get]
|
||||
// @Produce json
|
||||
|
@ -46,7 +46,7 @@ func GetOrgs(c *gin.Context) {
|
|||
|
||||
// DeleteOrg
|
||||
//
|
||||
// @Summary Delete an org
|
||||
// @Summary Delete an organization
|
||||
// @Description Deletes the given org. Requires admin rights.
|
||||
// @Router /orgs/{id} [delete]
|
||||
// @Produce plain
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
|
@ -37,7 +38,7 @@ import (
|
|||
|
||||
// CreatePipeline
|
||||
//
|
||||
// @Summary Run/trigger a pipelines
|
||||
// @Summary Trigger a manual pipeline
|
||||
// @Router /repos/{repo_id}/pipelines [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Pipeline
|
||||
|
@ -48,10 +49,16 @@ import (
|
|||
func CreatePipeline(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
repo := session.Repo(c)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from repo")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// parse create options
|
||||
var opts model.PipelineOptions
|
||||
err := json.NewDecoder(c.Request.Body).Decode(&opts)
|
||||
err = json.NewDecoder(c.Request.Body).Decode(&opts)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
|
@ -59,7 +66,7 @@ func CreatePipeline(c *gin.Context) {
|
|||
|
||||
user := session.User(c)
|
||||
|
||||
lastCommit, _ := server.Config.Services.Forge.BranchHead(c, user, repo, opts.Branch)
|
||||
lastCommit, _ := _forge.BranchHead(c, user, repo, opts.Branch)
|
||||
|
||||
tmpPipeline := createTmpPipeline(model.EventManual, lastCommit, user, &opts)
|
||||
|
||||
|
@ -93,7 +100,8 @@ func createTmpPipeline(event model.WebhookEvent, commit *model.Commit, user *mod
|
|||
|
||||
// GetPipelines
|
||||
//
|
||||
// @Summary Get pipelines, current running and past ones
|
||||
// @Summary List repository pipelines
|
||||
// @Description Get a list of pipelines for a repository.
|
||||
// @Router /repos/{repo_id}/pipelines [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Pipeline
|
||||
|
@ -102,10 +110,34 @@ func createTmpPipeline(event model.WebhookEvent, commit *model.Commit, user *mod
|
|||
// @Param repo_id path int true "the repository 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)
|
||||
// @Param before query string false "only return pipelines before this RFC3339 date"
|
||||
// @Param after query string false "only return pipelines after this RFC3339 date"
|
||||
func GetPipelines(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
before := c.Query("before")
|
||||
after := c.Query("after")
|
||||
|
||||
pipelines, err := store.FromContext(c).GetPipelineList(repo, session.Pagination(c))
|
||||
filter := new(model.PipelineFilter)
|
||||
|
||||
if before != "" {
|
||||
beforeDt, err := time.Parse(time.RFC3339, before)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
filter.Before = beforeDt.Unix()
|
||||
}
|
||||
|
||||
if after != "" {
|
||||
afterDt, err := time.Parse(time.RFC3339, after)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
filter.After = afterDt.Unix()
|
||||
}
|
||||
|
||||
pipelines, err := store.FromContext(c).GetPipelineList(repo, session.Pagination(c), filter)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
|
@ -113,9 +145,49 @@ func GetPipelines(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, pipelines)
|
||||
}
|
||||
|
||||
// DeletePipeline
|
||||
//
|
||||
// @Summary Delete a pipeline
|
||||
// @Router /repos/{repo_id}/pipelines/{number} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @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 DeletePipeline(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
repo := session.Repo(c)
|
||||
num, err := strconv.ParseInt(c.Param("number"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
pl, err := _store.GetPipelineNumber(repo, num)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if ok := pipelineDeleteAllowed(pl); !ok {
|
||||
c.String(http.StatusUnprocessableEntity, "Cannot delete pipeline with status %s", pl.Status)
|
||||
return
|
||||
}
|
||||
|
||||
err = store.FromContext(c).DeletePipeline(pl)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error deleting pipeline. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetPipeline
|
||||
//
|
||||
// @Summary Pipeline information by number
|
||||
// @Summary Get a repositories pipeline
|
||||
// @Router /repos/{repo_id}/pipelines/{number} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Pipeline
|
||||
|
@ -170,7 +242,7 @@ func GetPipelineLast(c *gin.Context) {
|
|||
|
||||
// GetStepLogs
|
||||
//
|
||||
// @Summary Log information
|
||||
// @Summary Get logs for a pipeline step
|
||||
// @Router /repos/{repo_id}/logs/{number}/{stepID} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} LogEntry
|
||||
|
@ -226,7 +298,7 @@ func GetStepLogs(c *gin.Context) {
|
|||
|
||||
// DeleteStepLogs
|
||||
//
|
||||
// @Summary Deletes step log
|
||||
// @Summary Delete step logs of a pipeline
|
||||
// @Router /repos/{repo_id}/logs/{number}/{stepId} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
|
@ -286,7 +358,7 @@ func DeleteStepLogs(c *gin.Context) {
|
|||
|
||||
// GetPipelineConfig
|
||||
//
|
||||
// @Summary Pipeline configuration
|
||||
// @Summary Get configuration files for a pipeline
|
||||
// @Router /repos/{repo_id}/pipelines/{number}/config [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Config
|
||||
|
@ -320,7 +392,7 @@ func GetPipelineConfig(c *gin.Context) {
|
|||
|
||||
// CancelPipeline
|
||||
//
|
||||
// @Summary Cancels a pipeline
|
||||
// @Summary Cancel a pipeline
|
||||
// @Router /repos/{repo_id}/pipelines/{number}/cancel [post]
|
||||
// @Produce plain
|
||||
// @Success 200
|
||||
|
@ -332,6 +404,13 @@ func CancelPipeline(c *gin.Context) {
|
|||
_store := store.FromContext(c)
|
||||
repo := session.Repo(c)
|
||||
user := session.User(c)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from repo")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
num, _ := strconv.ParseInt(c.Params.ByName("number"), 10, 64)
|
||||
|
||||
pl, err := _store.GetPipelineNumber(repo, num)
|
||||
|
@ -340,7 +419,7 @@ func CancelPipeline(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := pipeline.Cancel(c, _store, repo, user, pl); err != nil {
|
||||
if err := pipeline.Cancel(c, _forge, _store, repo, user, pl); err != nil {
|
||||
handlePipelineErr(c, err)
|
||||
} else {
|
||||
c.Status(http.StatusNoContent)
|
||||
|
@ -349,7 +428,7 @@ func CancelPipeline(c *gin.Context) {
|
|||
|
||||
// PostApproval
|
||||
//
|
||||
// @Summary Start pipelines in gated repos
|
||||
// @Summary Approve and start a pipeline
|
||||
// @Router /repos/{repo_id}/pipelines/{number}/approve [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Pipeline
|
||||
|
@ -381,7 +460,7 @@ func PostApproval(c *gin.Context) {
|
|||
|
||||
// PostDecline
|
||||
//
|
||||
// @Summary Decline pipelines in gated repos
|
||||
// @Summary Decline a pipeline
|
||||
// @Router /repos/{repo_id}/pipelines/{number}/decline [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Pipeline
|
||||
|
@ -413,7 +492,7 @@ func PostDecline(c *gin.Context) {
|
|||
|
||||
// GetPipelineQueue
|
||||
//
|
||||
// @Summary List pipeline queues
|
||||
// @Summary List pipelines in queue
|
||||
// @Router /pipelines [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Feed
|
||||
|
@ -510,7 +589,7 @@ func PostPipeline(c *gin.Context) {
|
|||
|
||||
// DeletePipelineLogs
|
||||
//
|
||||
// @Summary Deletes log
|
||||
// @Summary Deletes all logs of a pipeline
|
||||
// @Router /repos/{repo_id}/logs/{number} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
|
@ -536,9 +615,8 @@ func DeletePipelineLogs(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
switch pl.Status {
|
||||
case model.StatusRunning, model.StatusPending:
|
||||
c.String(http.StatusUnprocessableEntity, "Cannot delete logs for a pending or running pipeline")
|
||||
if ok := pipelineDeleteAllowed(pl); !ok {
|
||||
c.String(http.StatusUnprocessableEntity, "Cannot delete logs for pipeline with status %s", pl.Status)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -548,7 +626,7 @@ func DeletePipelineLogs(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "There was a problem deleting your logs. %s", err)
|
||||
c.String(http.StatusInternalServerError, "Error deleting pipeline logs. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
129
server/api/pipeline_test.go
Normal file
129
server/api/pipeline_test.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"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/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
|
||||
)
|
||||
|
||||
var fakePipeline = &model.Pipeline{
|
||||
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}
|
||||
|
||||
mockStore := 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)
|
||||
|
||||
GetPipelines(c)
|
||||
|
||||
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("DELETE", "/?before=2023-01-16&after=2023-01-15", nil)
|
||||
|
||||
GetPipelines(c)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should parse pipeline filter", func() {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
|
||||
mockStore := 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("DELETE", "/?2023-01-16T15:00:00Z&after=2023-01-15T15:00:00Z", nil)
|
||||
|
||||
GetPipelines(c)
|
||||
|
||||
assert.Equal(t, http.StatusOK, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should parse pipeline filter with tz offset", func() {
|
||||
pipelines := []*model.Pipeline{fakePipeline}
|
||||
|
||||
mockStore := 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("DELETE", "/?before=2023-01-16T15:00:00%2B01:00&after=2023-01-15T15:00:00%2B01:00", nil)
|
||||
|
||||
GetPipelines(c)
|
||||
|
||||
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)
|
||||
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Set("store", mockStore)
|
||||
c.Params = gin.Params{{Key: "number", Value: "1"}}
|
||||
|
||||
DeletePipeline(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything)
|
||||
mockStore.AssertCalled(t, "DeletePipeline", mock.Anything)
|
||||
assert.Equal(t, http.StatusNoContent, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should not delete without pipeline number", func() {
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
|
||||
DeletePipeline(c)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, c.Writer.Status())
|
||||
})
|
||||
|
||||
g.It("should not delete pending", func() {
|
||||
fakePipeline.Status = model.StatusPending
|
||||
|
||||
mockStore := 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: "1"}}
|
||||
|
||||
DeletePipeline(c)
|
||||
|
||||
mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything)
|
||||
mockStore.AssertNotCalled(t, "DeletePipeline", mock.Anything)
|
||||
assert.Equal(t, http.StatusUnprocessableEntity, c.Writer.Status())
|
||||
})
|
||||
})
|
||||
}
|
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
// GetRegistry
|
||||
//
|
||||
// @Summary Get a named registry
|
||||
// @Summary Get a registry by name
|
||||
// @Router /repos/{repo_id}/registry/{registry} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
|
@ -49,7 +49,7 @@ func GetRegistry(c *gin.Context) {
|
|||
|
||||
// PostRegistry
|
||||
//
|
||||
// @Summary Persist/create a registry
|
||||
// @Summary Create a registry
|
||||
// @Router /repos/{repo_id}/registry [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
|
@ -86,7 +86,7 @@ func PostRegistry(c *gin.Context) {
|
|||
|
||||
// PatchRegistry
|
||||
//
|
||||
// @Summary Update a named registry
|
||||
// @Summary Update a registry by name
|
||||
// @Router /repos/{repo_id}/registry/{registry} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Registry
|
||||
|
@ -134,7 +134,7 @@ func PatchRegistry(c *gin.Context) {
|
|||
|
||||
// GetRegistryList
|
||||
//
|
||||
// @Summary Get the registry list
|
||||
// @Summary List registries
|
||||
// @Router /repos/{repo_id}/registry [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Registry
|
||||
|
@ -161,7 +161,7 @@ func GetRegistryList(c *gin.Context) {
|
|||
|
||||
// DeleteRegistry
|
||||
//
|
||||
// @Summary Delete a named registry
|
||||
// @Summary Delete a registry by name
|
||||
// @Router /repos/{repo_id}/registry/{registry} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
|
|
|
@ -45,9 +45,14 @@ import (
|
|||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param forge_remote_id query string true "the id of a repository at the forge"
|
||||
func PostRepo(c *gin.Context) {
|
||||
forge := server.Config.Services.Forge
|
||||
_store := store.FromContext(c)
|
||||
user := session.User(c)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromUser(user)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from user")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
forgeRemoteID := model.ForgeRemoteID(c.Query("forge_remote_id"))
|
||||
if !forgeRemoteID.IsValid() {
|
||||
|
@ -67,7 +72,7 @@ func PostRepo(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
from, err := forge.Repo(c, user, forgeRemoteID, "", "")
|
||||
from, err := _forge.Repo(c, user, forgeRemoteID, "", "")
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Could not fetch repository from forge.")
|
||||
return
|
||||
|
@ -138,7 +143,7 @@ func PostRepo(c *gin.Context) {
|
|||
|
||||
// create an org if it doesn't exist yet
|
||||
if errors.Is(err, types.RecordNotExist) {
|
||||
org, err = forge.Org(c, user, repo.Owner)
|
||||
org, err = _forge.Org(c, user, repo.Owner)
|
||||
if err != nil {
|
||||
msg := "could not fetch organization from forge."
|
||||
log.Error().Err(err).Msg(msg)
|
||||
|
@ -146,6 +151,7 @@ func PostRepo(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
org.ForgeID = user.ForgeID
|
||||
err = _store.OrgCreate(org)
|
||||
if err != nil {
|
||||
msg := "could not create organization in store."
|
||||
|
@ -157,7 +163,7 @@ func PostRepo(c *gin.Context) {
|
|||
|
||||
repo.OrgID = org.ID
|
||||
|
||||
err = forge.Activate(c, user, repo, hookURL)
|
||||
err = _forge.Activate(c, user, repo, hookURL)
|
||||
if err != nil {
|
||||
msg := "could not create webhook in forge."
|
||||
log.Error().Err(err).Msg(msg)
|
||||
|
@ -168,6 +174,7 @@ func PostRepo(c *gin.Context) {
|
|||
if enabledOnce {
|
||||
err = _store.UpdateRepo(repo)
|
||||
} else {
|
||||
repo.ForgeID = user.ForgeID // TODO: allow to use other connected forges of the user
|
||||
err = _store.CreateRepo(repo)
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -192,7 +199,7 @@ func PostRepo(c *gin.Context) {
|
|||
|
||||
// PatchRepo
|
||||
//
|
||||
// @Summary Change a repository
|
||||
// @Summary Update a repository
|
||||
// @Router /repos/{repo_id} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Repo
|
||||
|
@ -266,7 +273,7 @@ func PatchRepo(c *gin.Context) {
|
|||
|
||||
// ChownRepo
|
||||
//
|
||||
// @Summary Change a repository's owner, to the one holding the access token
|
||||
// @Summary Change a repository's owner to the currently authenticated user
|
||||
// @Router /repos/{repo_id}/chown [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Repo
|
||||
|
@ -289,20 +296,20 @@ func ChownRepo(c *gin.Context) {
|
|||
|
||||
// LookupRepo
|
||||
//
|
||||
// @Summary Get repository by full-name
|
||||
// @Summary Lookup a repository by full name
|
||||
// @Router /repos/lookup/{repo_full_name} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Repo
|
||||
// @Tags Repositories
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param repo_full_name path string true "the repository full-name / slug"
|
||||
// @Param repo_full_name path string true "the repository full name / slug"
|
||||
func LookupRepo(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, session.Repo(c))
|
||||
}
|
||||
|
||||
// GetRepo
|
||||
//
|
||||
// @Summary Get repository information
|
||||
// @Summary Get a repository
|
||||
// @Router /repos/{repo_id} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Repo
|
||||
|
@ -315,7 +322,7 @@ func GetRepo(c *gin.Context) {
|
|||
|
||||
// GetRepoPermissions
|
||||
//
|
||||
// @Summary Repository permission information
|
||||
// @Summary Check current authenticated users access to the repository
|
||||
// @Description The repository permission, according to the used access token.
|
||||
// @Router /repos/{repo_id}/permissions [get]
|
||||
// @Produce json
|
||||
|
@ -330,7 +337,7 @@ func GetRepoPermissions(c *gin.Context) {
|
|||
|
||||
// GetRepoBranches
|
||||
//
|
||||
// @Summary Get repository branches
|
||||
// @Summary Get branches of a repository
|
||||
// @Router /repos/{repo_id}/branches [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} string
|
||||
|
@ -342,9 +349,14 @@ func GetRepoPermissions(c *gin.Context) {
|
|||
func GetRepoBranches(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
user := session.User(c)
|
||||
f := server.Config.Services.Forge
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from repo")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
branches, err := f.Branches(c, user, repo, session.Pagination(c))
|
||||
branches, err := _forge.Branches(c, user, repo, session.Pagination(c))
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
|
@ -355,7 +367,7 @@ func GetRepoBranches(c *gin.Context) {
|
|||
|
||||
// GetRepoPullRequests
|
||||
//
|
||||
// @Summary List active pull requests
|
||||
// @Summary List active pull requests of a repository
|
||||
// @Router /repos/{repo_id}/pull_requests [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} PullRequest
|
||||
|
@ -367,9 +379,14 @@ func GetRepoBranches(c *gin.Context) {
|
|||
func GetRepoPullRequests(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
user := session.User(c)
|
||||
f := server.Config.Services.Forge
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from repo")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
prs, err := f.PullRequests(c, user, repo, session.Pagination(c))
|
||||
prs, err := _forge.PullRequests(c, user, repo, session.Pagination(c))
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
|
@ -390,9 +407,14 @@ func GetRepoPullRequests(c *gin.Context) {
|
|||
func DeleteRepo(c *gin.Context) {
|
||||
remove, _ := strconv.ParseBool(c.Query("remove"))
|
||||
_store := store.FromContext(c)
|
||||
|
||||
repo := session.Repo(c)
|
||||
user := session.User(c)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from repo")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
repo.IsActive = false
|
||||
repo.UserID = 0
|
||||
|
@ -409,7 +431,7 @@ func DeleteRepo(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
if err := server.Config.Services.Forge.Deactivate(c, user, repo, server.Config.Server.WebhookHost); err != nil {
|
||||
if err := _forge.Deactivate(c, user, repo, server.Config.Server.WebhookHost); err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
@ -445,10 +467,15 @@ func RepairRepo(c *gin.Context) {
|
|||
// @Param repo_id path int true "the repository id"
|
||||
// @Param to query string true "the username to move the repository to"
|
||||
func MoveRepo(c *gin.Context) {
|
||||
forge := server.Config.Services.Forge
|
||||
_store := store.FromContext(c)
|
||||
repo := session.Repo(c)
|
||||
user := session.User(c)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from repo")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
to, exists := c.GetQuery("to")
|
||||
if !exists {
|
||||
|
@ -463,7 +490,7 @@ func MoveRepo(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
from, err := forge.Repo(c, user, "", owner, name)
|
||||
from, err := _forge.Repo(c, user, "", owner, name)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||
return
|
||||
|
@ -508,10 +535,10 @@ func MoveRepo(c *gin.Context) {
|
|||
sig,
|
||||
)
|
||||
|
||||
if err := forge.Deactivate(c, user, repo, host); err != nil {
|
||||
if err := _forge.Deactivate(c, user, repo, host); err != nil {
|
||||
log.Trace().Err(err).Msgf("deactivate repo '%s' for move to activate later, got an error", repo.FullName)
|
||||
}
|
||||
if err := forge.Activate(c, user, repo, hookURL); err != nil {
|
||||
if err := _forge.Activate(c, user, repo, hookURL); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -520,7 +547,8 @@ func MoveRepo(c *gin.Context) {
|
|||
|
||||
// GetAllRepos
|
||||
//
|
||||
// @Summary List all repositories on the server. Requires admin rights.
|
||||
// @Summary List all repositories on the server
|
||||
// @Description Returns a list of all repositories. Requires admin rights.
|
||||
// @Router /repos [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Repo
|
||||
|
@ -545,7 +573,8 @@ func GetAllRepos(c *gin.Context) {
|
|||
|
||||
// RepairAllRepos
|
||||
//
|
||||
// @Summary Repair all repositories on the server. Requires admin rights.
|
||||
// @Summary Repair all repositories on the server
|
||||
// @Description Executes a repair process on all repositories. Requires admin rights.
|
||||
// @Router /repos/repair [post]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
|
@ -571,8 +600,13 @@ func RepairAllRepos(c *gin.Context) {
|
|||
}
|
||||
|
||||
func repairRepo(c *gin.Context, repo *model.Repo, withPerms, skipOnErr bool) {
|
||||
forge := server.Config.Services.Forge
|
||||
_store := store.FromContext(c)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from repo")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := _store.GetUser(repo.UserID)
|
||||
if err != nil {
|
||||
|
@ -603,7 +637,7 @@ func repairRepo(c *gin.Context, repo *model.Repo, withPerms, skipOnErr bool) {
|
|||
sig,
|
||||
)
|
||||
|
||||
from, err := forge.Repo(c, user, repo.ForgeRemoteID, repo.Owner, repo.Name)
|
||||
from, err := _forge.Repo(c, user, repo.ForgeRemoteID, repo.Owner, repo.Name)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("get repo '%s/%s' from forge", repo.Owner, repo.Name)
|
||||
if !skipOnErr {
|
||||
|
@ -636,10 +670,10 @@ func repairRepo(c *gin.Context, repo *model.Repo, withPerms, skipOnErr bool) {
|
|||
}
|
||||
}
|
||||
|
||||
if err := forge.Deactivate(c, user, repo, host); err != nil {
|
||||
if err := _forge.Deactivate(c, user, repo, host); err != nil {
|
||||
log.Trace().Err(err).Msgf("deactivate repo '%s' to repair failed", repo.FullName)
|
||||
}
|
||||
if err := forge.Activate(c, user, repo, hookURL); err != nil {
|
||||
if err := _forge.Activate(c, user, repo, hookURL); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
|
||||
// GetSecret
|
||||
//
|
||||
// @Summary Get a named secret
|
||||
// @Summary Get a repository secret by name
|
||||
// @Router /repos/{repo_id}/secrets/{secretName} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Secret
|
||||
|
@ -49,7 +49,7 @@ func GetSecret(c *gin.Context) {
|
|||
|
||||
// PostSecret
|
||||
//
|
||||
// @Summary Persist/create a secret
|
||||
// @Summary Create a repository secret
|
||||
// @Router /repos/{repo_id}/secrets [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Secret
|
||||
|
@ -87,7 +87,7 @@ func PostSecret(c *gin.Context) {
|
|||
|
||||
// PatchSecret
|
||||
//
|
||||
// @Summary Update a named secret
|
||||
// @Summary Update a repository secret by name
|
||||
// @Router /repos/{repo_id}/secrets/{secretName} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Secret
|
||||
|
@ -138,7 +138,7 @@ func PatchSecret(c *gin.Context) {
|
|||
|
||||
// GetSecretList
|
||||
//
|
||||
// @Summary Get the secret list
|
||||
// @Summary List repository secrets
|
||||
// @Router /repos/{repo_id}/secrets [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Secret
|
||||
|
@ -165,7 +165,7 @@ func GetSecretList(c *gin.Context) {
|
|||
|
||||
// DeleteSecret
|
||||
//
|
||||
// @Summary Delete a named secret
|
||||
// @Summary Delete a repository secret by name
|
||||
// @Router /repos/{repo_id}/secrets/{secretName} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
|
|
|
@ -36,8 +36,8 @@ import (
|
|||
|
||||
// EventStreamSSE
|
||||
//
|
||||
// @Summary Event stream
|
||||
// @Description event source streaming for compatibility with quic and http2
|
||||
// @Summary Stream events like pipeline updates
|
||||
// @Description With quic and http2 support
|
||||
// @Router /stream/events [get]
|
||||
// @Produce plain
|
||||
// @Success 200
|
||||
|
@ -124,7 +124,7 @@ func EventStreamSSE(c *gin.Context) {
|
|||
|
||||
// LogStreamSSE
|
||||
//
|
||||
// @Summary Log stream
|
||||
// @Summary Stream logs of a pipeline step
|
||||
// @Router /stream/logs/{repo_id}/{pipeline}/{stepID} [get]
|
||||
// @Produce plain
|
||||
// @Success 200
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
|
@ -31,7 +32,7 @@ import (
|
|||
|
||||
// GetSelf
|
||||
//
|
||||
// @Summary Returns the currently authenticated user.
|
||||
// @Summary Get the currently authenticated user
|
||||
// @Router /user [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} User
|
||||
|
@ -43,11 +44,11 @@ func GetSelf(c *gin.Context) {
|
|||
|
||||
// GetFeed
|
||||
//
|
||||
// @Summary A feed entry for a build.
|
||||
// @Description Feed entries can be used to display information on the latest builds.
|
||||
// @Summary Get the currently authenticaed users pipeline feed
|
||||
// @Description The feed lists the most recent pipeline for the currently authenticated user.
|
||||
// @Router /user/feed [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Feed
|
||||
// @Success 200 {array} Feed
|
||||
// @Tags User
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
func GetFeed(c *gin.Context) {
|
||||
|
@ -76,7 +77,7 @@ func GetFeed(c *gin.Context) {
|
|||
|
||||
// GetRepos
|
||||
//
|
||||
// @Summary Get user's repos
|
||||
// @Summary Get user's repositories
|
||||
// @Description Retrieve the currently authenticated User's Repository list
|
||||
// @Router /user/repos [get]
|
||||
// @Produce json
|
||||
|
@ -86,9 +87,14 @@ func GetFeed(c *gin.Context) {
|
|||
// @Param all query bool false "query all repos, including inactive ones"
|
||||
func GetRepos(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
_forge := server.Config.Services.Forge
|
||||
|
||||
user := session.User(c)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromUser(user)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get forge from user")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
all, _ := strconv.ParseBool(c.Query("all"))
|
||||
|
||||
if all {
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
|
||||
// GetUsers
|
||||
//
|
||||
// @Summary Get all users
|
||||
// @Summary List users
|
||||
// @Description Returns all registered, active users in the system. Requires admin rights.
|
||||
// @Router /users [get]
|
||||
// @Produce json
|
||||
|
@ -67,7 +67,7 @@ func GetUser(c *gin.Context) {
|
|||
|
||||
// PatchUser
|
||||
//
|
||||
// @Summary Change a user
|
||||
// @Summary Update a user
|
||||
// @Description Changes the data of an existing user. Requires admin rights.
|
||||
// @Router /users/{login} [patch]
|
||||
// @Produce json
|
||||
|
@ -132,6 +132,8 @@ func PostUser(c *gin.Context) {
|
|||
Hash: base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32),
|
||||
),
|
||||
ForgeID: 1, // TODO: replace with forge id when multiple forges are supported
|
||||
ForgeRemoteID: model.ForgeRemoteID("0"), // TODO: search for the user in the forge and get the remote id
|
||||
}
|
||||
if err = user.Validate(); err != nil {
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
|
|
16
server/cache/membership.go
vendored
16
server/cache/membership.go
vendored
|
@ -23,39 +23,39 @@ import (
|
|||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
// MembershipService is a service to check for user membership.
|
||||
type MembershipService interface {
|
||||
// Get returns if the user is a member of the organization.
|
||||
Get(ctx context.Context, u *model.User, org string) (*model.OrgPerm, error)
|
||||
Get(ctx context.Context, _forge forge.Forge, u *model.User, org string) (*model.OrgPerm, error)
|
||||
}
|
||||
|
||||
type membershipCache struct {
|
||||
forge forge.Forge
|
||||
cache *ttlcache.Cache[string, *model.OrgPerm]
|
||||
store store.Store
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
// NewMembershipService creates a new membership service.
|
||||
func NewMembershipService(f forge.Forge) MembershipService {
|
||||
//nolint:gomnd
|
||||
func NewMembershipService(_store store.Store) MembershipService {
|
||||
return &membershipCache{
|
||||
ttl: 10 * time.Minute,
|
||||
forge: f,
|
||||
ttl: 10 * time.Minute, //nolint: gomnd
|
||||
store: _store,
|
||||
cache: ttlcache.New(ttlcache.WithDisableTouchOnHit[string, *model.OrgPerm]()),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns if the user is a member of the organization.
|
||||
func (c *membershipCache) Get(ctx context.Context, u *model.User, org string) (*model.OrgPerm, error) {
|
||||
func (c *membershipCache) Get(ctx context.Context, _forge forge.Forge, u *model.User, org string) (*model.OrgPerm, error) {
|
||||
key := fmt.Sprintf("%s-%s", u.ForgeRemoteID, org)
|
||||
item := c.cache.Get(key)
|
||||
if item != nil && !item.IsExpired() {
|
||||
return item.Value(), nil
|
||||
}
|
||||
|
||||
perm, err := c.forge.OrgMembership(ctx, u, org)
|
||||
perm, err := _forge.OrgMembership(ctx, u, org)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
"time"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/cache"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/logging"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/pubsub"
|
||||
|
@ -35,9 +34,8 @@ var Config = struct {
|
|||
Pubsub *pubsub.Publisher
|
||||
Queue queue.Queue
|
||||
Logs logging.Log
|
||||
Forge forge.Forge
|
||||
Membership cache.MembershipService
|
||||
Manager *services.Manager
|
||||
Manager services.Manager
|
||||
}
|
||||
Server struct {
|
||||
Key string
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/robfig/cron"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/pipeline"
|
||||
|
@ -37,7 +38,7 @@ const (
|
|||
)
|
||||
|
||||
// Start starts the cron scheduler loop
|
||||
func Start(ctx context.Context, store store.Store, forge forge.Forge) error {
|
||||
func Start(ctx context.Context, store store.Store) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
@ -54,7 +55,7 @@ func Start(ctx context.Context, store store.Store, forge forge.Forge) error {
|
|||
}
|
||||
|
||||
for _, cron := range crons {
|
||||
if err := runCron(ctx, store, forge, cron, now); err != nil {
|
||||
if err := runCron(ctx, store, cron, now); err != nil {
|
||||
log.Error().Err(err).Int64("cronID", cron.ID).Msg("run cron failed")
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +78,7 @@ func CalcNewNext(schedule string, now time.Time) (time.Time, error) {
|
|||
return c.Next(now), nil
|
||||
}
|
||||
|
||||
func runCron(ctx context.Context, store store.Store, forge forge.Forge, cron *model.Cron, now time.Time) error {
|
||||
func runCron(ctx context.Context, store store.Store, cron *model.Cron, now time.Time) error {
|
||||
log.Trace().Msgf("cron: run id[%d]", cron.ID)
|
||||
|
||||
newNext, err := CalcNewNext(cron.Schedule, now)
|
||||
|
@ -95,7 +96,7 @@ func runCron(ctx context.Context, store store.Store, forge forge.Forge, cron *mo
|
|||
return nil
|
||||
}
|
||||
|
||||
repo, newPipeline, err := CreatePipeline(ctx, store, forge, cron)
|
||||
repo, newPipeline, err := CreatePipeline(ctx, store, cron)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -104,12 +105,17 @@ func runCron(ctx context.Context, store store.Store, forge forge.Forge, cron *mo
|
|||
return err
|
||||
}
|
||||
|
||||
func CreatePipeline(ctx context.Context, store store.Store, f forge.Forge, cron *model.Cron) (*model.Repo, *model.Pipeline, error) {
|
||||
func CreatePipeline(ctx context.Context, store store.Store, cron *model.Cron) (*model.Repo, *model.Pipeline, error) {
|
||||
repo, err := store.GetRepo(cron.RepoID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if cron.Branch == "" {
|
||||
// fallback to the repos default branch
|
||||
cron.Branch = repo.Branch
|
||||
|
@ -123,9 +129,9 @@ func CreatePipeline(ctx context.Context, store store.Store, f forge.Forge, cron
|
|||
// If the forge has a refresh token, the current access token
|
||||
// may be stale. Therefore, we should refresh prior to dispatching
|
||||
// the pipeline.
|
||||
forge.Refresh(ctx, f, store, creator)
|
||||
forge.Refresh(ctx, _forge, store, creator)
|
||||
|
||||
commit, err := f.BranchHead(ctx, creator, repo, cron.Branch)
|
||||
commit, err := _forge.BranchHead(ctx, creator, repo, cron.Branch)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -22,13 +22,16 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
mocks_forge "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
mocks_manager "go.woodpecker-ci.org/woodpecker/v2/server/services/mocks"
|
||||
mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
|
||||
)
|
||||
|
||||
func TestCreateBuild(t *testing.T) {
|
||||
forge := mocks_forge.NewForge(t)
|
||||
func TestCreatePipeline(t *testing.T) {
|
||||
_manager := mocks_manager.NewManager(t)
|
||||
_forge := mocks_forge.NewForge(t)
|
||||
store := mocks_store.NewStore(t)
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -47,12 +50,14 @@ func TestCreateBuild(t *testing.T) {
|
|||
// mock things
|
||||
store.On("GetRepo", mock.Anything).Return(repo1, nil)
|
||||
store.On("GetUser", mock.Anything).Return(creator, nil)
|
||||
forge.On("BranchHead", mock.Anything, creator, repo1, "default").Return(&model.Commit{
|
||||
_forge.On("BranchHead", mock.Anything, creator, repo1, "default").Return(&model.Commit{
|
||||
ForgeURL: "https://example.com/sha1",
|
||||
SHA: "sha1",
|
||||
}, nil)
|
||||
_manager.On("ForgeFromRepo", repo1).Return(_forge, nil)
|
||||
server.Config.Services.Manager = _manager
|
||||
|
||||
_, pipeline, err := CreatePipeline(ctx, store, forge, &model.Cron{
|
||||
_, pipeline, err := CreatePipeline(ctx, store, &model.Cron{
|
||||
Name: "test",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -217,14 +217,14 @@ func Test_bitbucket(t *testing.T) {
|
|||
|
||||
g.Describe("When requesting repo directory contents", func() {
|
||||
g.It("Should return the details", func() {
|
||||
files, err := c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "/dir")
|
||||
files, err := c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "dir")
|
||||
g.Assert(err).IsNil()
|
||||
g.Assert(len(files)).Equal(3)
|
||||
g.Assert(files[0].Name).Equal("README.md")
|
||||
g.Assert(string(files[0].Data)).Equal("dummy payload")
|
||||
})
|
||||
g.It("Should handle not found errors", func() {
|
||||
_, err := c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "dir_not_found/")
|
||||
_, err := c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "dir_not_found")
|
||||
g.Assert(err).IsNotNil()
|
||||
g.Assert(errors.Is(err, &types.ErrConfigNotFound{})).IsTrue()
|
||||
})
|
||||
|
|
|
@ -114,7 +114,7 @@ func getRepoFile(c *gin.Context) {
|
|||
switch c.Param("file") {
|
||||
case "dir":
|
||||
c.String(http.StatusOK, repoDirPayload)
|
||||
case "dir_not_found/":
|
||||
case "dir_not_found":
|
||||
c.String(http.StatusNotFound, "")
|
||||
case "file_not_found":
|
||||
c.String(http.StatusNotFound, "")
|
||||
|
|
|
@ -52,7 +52,7 @@ const (
|
|||
pathOrgPerms = "%s/2.0/workspaces/%s/permissions?%s"
|
||||
pathPullRequests = "%s/2.0/repositories/%s/%s/pullrequests?%s"
|
||||
pathBranchCommits = "%s/2.0/repositories/%s/%s/commits/%s"
|
||||
pathDir = "%s/2.0/repositories/%s/%s/src/%s%s"
|
||||
pathDir = "%s/2.0/repositories/%s/%s/src/%s/%s"
|
||||
pageSize = 100
|
||||
)
|
||||
|
||||
|
|
|
@ -262,8 +262,14 @@ func (c *client) File(ctx context.Context, u *model.User, r *model.Repo, p *mode
|
|||
return nil, fmt.Errorf("unable to create bitbucket client: %w", err)
|
||||
}
|
||||
|
||||
b, _, err := bc.Projects.GetTextFileContent(ctx, r.Owner, r.Name, f, p.Commit)
|
||||
b, resp, err := bc.Projects.GetTextFileContent(ctx, r.Owner, r.Name, f, p.Commit)
|
||||
if err != nil {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
// requested directory might not exist
|
||||
return nil, &forge_types.ErrConfigNotFound{
|
||||
Configs: []string{f},
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
|
@ -281,7 +287,10 @@ func (c *client) Dir(ctx context.Context, u *model.User, r *model.Repo, p *model
|
|||
list, resp, err := bc.Projects.ListFiles(ctx, r.Owner, r.Name, path, opts)
|
||||
if err != nil {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
break // requested directory might not exist
|
||||
// requested directory might not exist
|
||||
return nil, &forge_types.ErrConfigNotFound{
|
||||
Configs: []string{path},
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
|
||||
package forge
|
||||
|
||||
//go:generate go install github.com/vektra/mockery/v2@latest
|
||||
//go:generate mockery --name Forge --output mocks --case underscore
|
||||
|
||||
import (
|
||||
|
|
|
@ -127,13 +127,14 @@ func pipelineFromTag(hook *pushHook) *model.Pipeline {
|
|||
hook.Repo.HTMLURL,
|
||||
fixMalformedAvatar(hook.Sender.AvatarURL),
|
||||
)
|
||||
ref := strings.TrimPrefix(hook.Ref, "refs/tags/")
|
||||
|
||||
return &model.Pipeline{
|
||||
Event: model.EventTag,
|
||||
Commit: hook.Sha,
|
||||
Ref: fmt.Sprintf("refs/tags/%s", hook.Ref),
|
||||
ForgeURL: fmt.Sprintf("%s/src/tag/%s", hook.Repo.HTMLURL, hook.Ref),
|
||||
Message: fmt.Sprintf("created tag %s", hook.Ref),
|
||||
Ref: fmt.Sprintf("refs/tags/%s", ref),
|
||||
ForgeURL: fmt.Sprintf("%s/src/tag/%s", hook.Repo.HTMLURL, ref),
|
||||
Message: fmt.Sprintf("created tag %s", ref),
|
||||
Avatar: avatar,
|
||||
Author: hook.Sender.UserName,
|
||||
Sender: hook.Sender.UserName,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Code generated by mockery v2.42.1. DO NOT EDIT.
|
||||
// Code generated by mockery. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
|
|
134
server/forge/setup/setup.go
Normal file
134
server/forge/setup/setup.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/addon"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucketdatacenter"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/gitea"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/github"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/gitlab"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
||||
func Forge(forge *model.Forge) (forge.Forge, error) {
|
||||
switch forge.Type {
|
||||
case model.ForgeTypeAddon:
|
||||
return setupAddon(forge)
|
||||
case model.ForgeTypeGithub:
|
||||
return setupGitHub(forge)
|
||||
case model.ForgeTypeGitlab:
|
||||
return setupGitLab(forge)
|
||||
case model.ForgeTypeBitbucket:
|
||||
return setupBitbucket(forge)
|
||||
case model.ForgeTypeGitea:
|
||||
return setupGitea(forge)
|
||||
case model.ForgeTypeBitbucketDatacenter:
|
||||
return setupBitbucketDatacenter(forge)
|
||||
default:
|
||||
return nil, fmt.Errorf("forge not configured")
|
||||
}
|
||||
}
|
||||
|
||||
func setupBitbucket(forge *model.Forge) (forge.Forge, error) {
|
||||
opts := &bitbucket.Opts{
|
||||
Client: forge.Client,
|
||||
Secret: forge.ClientSecret,
|
||||
}
|
||||
log.Trace().Msgf("Forge (bitbucket) opts: %#v", opts)
|
||||
return bitbucket.New(opts)
|
||||
}
|
||||
|
||||
func setupGitea(forge *model.Forge) (forge.Forge, error) {
|
||||
server, err := url.Parse(forge.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oauthURL, ok := forge.AdditionalOptions["oauth-server"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing oauth-server")
|
||||
}
|
||||
|
||||
opts := gitea.Opts{
|
||||
URL: strings.TrimRight(server.String(), "/"),
|
||||
Client: forge.Client,
|
||||
Secret: forge.ClientSecret,
|
||||
SkipVerify: forge.SkipVerify,
|
||||
OAuth2URL: oauthURL,
|
||||
}
|
||||
if len(opts.URL) == 0 {
|
||||
return nil, fmt.Errorf("WOODPECKER_GITEA_URL must be set")
|
||||
}
|
||||
log.Trace().Msgf("Forge (gitea) opts: %#v", opts)
|
||||
return gitea.New(opts)
|
||||
}
|
||||
|
||||
func setupGitLab(forge *model.Forge) (forge.Forge, error) {
|
||||
return gitlab.New(gitlab.Opts{
|
||||
URL: forge.URL,
|
||||
ClientID: forge.Client,
|
||||
ClientSecret: forge.ClientSecret,
|
||||
SkipVerify: forge.SkipVerify,
|
||||
})
|
||||
}
|
||||
|
||||
func setupGitHub(forge *model.Forge) (forge.Forge, error) {
|
||||
mergeRef, ok := forge.AdditionalOptions["merge-ref"].(bool)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing merge-ref")
|
||||
}
|
||||
|
||||
publicOnly, ok := forge.AdditionalOptions["public-only"].(bool)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing public-only")
|
||||
}
|
||||
|
||||
opts := github.Opts{
|
||||
URL: forge.URL,
|
||||
Client: forge.Client,
|
||||
Secret: forge.ClientSecret,
|
||||
SkipVerify: forge.SkipVerify,
|
||||
MergeRef: mergeRef,
|
||||
OnlyPublic: publicOnly,
|
||||
}
|
||||
log.Trace().Msgf("Forge (github) opts: %#v", opts)
|
||||
return github.New(opts)
|
||||
}
|
||||
|
||||
func setupBitbucketDatacenter(forge *model.Forge) (forge.Forge, error) {
|
||||
gitUsername, ok := forge.AdditionalOptions["git-username"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing git-username")
|
||||
}
|
||||
gitPassword, ok := forge.AdditionalOptions["git-password"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing git-password")
|
||||
}
|
||||
|
||||
opts := bitbucketdatacenter.Opts{
|
||||
URL: forge.URL,
|
||||
ClientID: forge.Client,
|
||||
ClientSecret: forge.ClientSecret,
|
||||
Username: gitUsername,
|
||||
Password: gitPassword,
|
||||
}
|
||||
log.Trace().Msgf("Forge (bitbucketdatacenter) opts: %#v", opts)
|
||||
return bitbucketdatacenter.New(opts)
|
||||
}
|
||||
|
||||
func setupAddon(forge *model.Forge) (forge.Forge, error) {
|
||||
executable, ok := forge.AdditionalOptions["executable"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing git-username")
|
||||
}
|
||||
|
||||
log.Trace().Msgf("Forge (addon) executable: %#v", executable)
|
||||
return addon.Load(executable)
|
||||
}
|
|
@ -31,6 +31,7 @@ import (
|
|||
grpcMetadata "google.golang.org/grpc/metadata"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/logging"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
|
@ -41,7 +42,6 @@ import (
|
|||
)
|
||||
|
||||
type RPC struct {
|
||||
forge forge.Forge
|
||||
queue queue.Queue
|
||||
pubsub *pubsub.Publisher
|
||||
logger logging.Log
|
||||
|
@ -418,11 +418,17 @@ func (s *RPC) updateForgeStatus(ctx context.Context, repo *model.Repo, pipeline
|
|||
return
|
||||
}
|
||||
|
||||
forge.Refresh(ctx, s.forge, s.store, user)
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("can not get forge for repo '%s'", repo.FullName)
|
||||
return
|
||||
}
|
||||
|
||||
forge.Refresh(ctx, _forge, s.store, user)
|
||||
|
||||
// only do status updates for parent steps
|
||||
if workflow != nil {
|
||||
err = s.forge.Status(ctx, user, repo, pipeline, workflow)
|
||||
err = _forge.Status(ctx, user, repo, pipeline, workflow)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("error setting commit status for %s/%d", repo.FullName, pipeline.Number)
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import (
|
|||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/logging"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/pubsub"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/queue"
|
||||
|
@ -37,7 +36,7 @@ type WoodpeckerServer struct {
|
|||
peer RPC
|
||||
}
|
||||
|
||||
func NewWoodpeckerServer(forge forge.Forge, queue queue.Queue, logger logging.Log, pubsub *pubsub.Publisher, store store.Store) proto.WoodpeckerServer {
|
||||
func NewWoodpeckerServer(queue queue.Queue, logger logging.Log, pubsub *pubsub.Publisher, store store.Store) proto.WoodpeckerServer {
|
||||
pipelineTime := promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Namespace: "woodpecker",
|
||||
Name: "pipeline_time",
|
||||
|
@ -49,7 +48,6 @@ func NewWoodpeckerServer(forge forge.Forge, queue queue.Queue, logger logging.Lo
|
|||
Help: "Pipeline count.",
|
||||
}, []string{"repo", "branch", "status", "pipeline"})
|
||||
peer := RPC{
|
||||
forge: forge,
|
||||
store: store,
|
||||
queue: queue,
|
||||
pubsub: pubsub,
|
||||
|
|
36
server/model/forge.go
Normal file
36
server/model/forge.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
// 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
|
||||
|
||||
type ForgeType string
|
||||
|
||||
const (
|
||||
ForgeTypeGithub ForgeType = "github"
|
||||
ForgeTypeGitlab ForgeType = "gitlab"
|
||||
ForgeTypeGitea ForgeType = "gitea"
|
||||
ForgeTypeBitbucket ForgeType = "bitbucket"
|
||||
ForgeTypeBitbucketDatacenter ForgeType = "bitbucket-dc"
|
||||
ForgeTypeAddon ForgeType = "addon"
|
||||
)
|
||||
|
||||
type Forge struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
Type ForgeType `xorm:"VARCHAR(250)"`
|
||||
URL string `xorm:"VARCHAR(500) 'url'"`
|
||||
Client string `xorm:"VARCHAR(250)"`
|
||||
ClientSecret string `xorm:"VARCHAR(250)"`
|
||||
SkipVerify bool `xorm:"bool"`
|
||||
AdditionalOptions map[string]any `xorm:"json"`
|
||||
}
|
|
@ -16,11 +16,12 @@ package model
|
|||
|
||||
// Org represents an organization.
|
||||
type Org struct {
|
||||
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'id'"`
|
||||
Name string `json:"name" xorm:"UNIQUE 'name'"`
|
||||
IsUser bool `json:"is_user" xorm:"is_user"`
|
||||
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'id'"`
|
||||
ForgeID int64 `json:"forge_id,omitempty" xorm:"forge_id"`
|
||||
Name string `json:"name" xorm:"UNIQUE 'name'"`
|
||||
IsUser bool `json:"is_user" xorm:"is_user"`
|
||||
// if name lookup has to check for membership or not
|
||||
Private bool `json:"-" xorm:"private"`
|
||||
Private bool `json:"-" xorm:"private"`
|
||||
} // @name Org
|
||||
|
||||
// TableName return database table name for xorm
|
||||
|
|
|
@ -53,6 +53,11 @@ type Pipeline struct {
|
|||
IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"`
|
||||
} // @name Pipeline
|
||||
|
||||
type PipelineFilter struct {
|
||||
Before int64
|
||||
After int64
|
||||
}
|
||||
|
||||
// TableName return database table name for xorm
|
||||
func (Pipeline) TableName() string {
|
||||
return "pipelines"
|
||||
|
|
|
@ -22,8 +22,9 @@ import (
|
|||
|
||||
// Repo represents a repository.
|
||||
type Repo struct {
|
||||
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'repo_id'"`
|
||||
UserID int64 `json:"-" xorm:"repo_user_id"`
|
||||
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'repo_id'"`
|
||||
UserID int64 `json:"-" xorm:"repo_user_id"`
|
||||
ForgeID int64 `json:"forge_id,omitempty" xorm:"forge_id"`
|
||||
// ForgeRemoteID is the unique identifier for the repository on the forge.
|
||||
ForgeRemoteID ForgeRemoteID `json:"forge_remote_id" xorm:"forge_remote_id"`
|
||||
OrgID int64 `json:"org_id" xorm:"repo_org_id"`
|
||||
|
|
|
@ -34,6 +34,8 @@ type User struct {
|
|||
// required: true
|
||||
ID int64 `json:"id" xorm:"pk autoincr 'user_id'"`
|
||||
|
||||
ForgeID int64 `json:"forge_id,omitempty" xorm:"forge_id"`
|
||||
|
||||
ForgeRemoteID ForgeRemoteID `json:"-" xorm:"forge_remote_id"`
|
||||
|
||||
// Login is the username for this user.
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
|
@ -32,6 +33,13 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe
|
|||
return nil, ErrBadRequest{Msg: fmt.Sprintf("cannot approve a pipeline with status %s", currentPipeline.Status)}
|
||||
}
|
||||
|
||||
forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName)
|
||||
log.Error().Err(err).Str("repo", repo.FullName).Msg(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
// fetch the pipeline file from the database
|
||||
configs, err := store.ConfigsForPipeline(currentPipeline.ID)
|
||||
if err != nil {
|
||||
|
@ -72,7 +80,7 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe
|
|||
}
|
||||
}
|
||||
|
||||
currentPipeline, pipelineItems, err := createPipelineItems(ctx, store, currentPipeline, user, repo, yamls, nil)
|
||||
currentPipeline, pipelineItems, err := createPipelineItems(ctx, forge, store, currentPipeline, user, repo, yamls, nil)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failure to createPipelineItems for %s", repo.FullName)
|
||||
log.Error().Err(err).Msg(msg)
|
||||
|
@ -86,9 +94,9 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe
|
|||
return nil, err
|
||||
}
|
||||
|
||||
publishPipeline(ctx, currentPipeline, repo, user)
|
||||
publishPipeline(ctx, forge, currentPipeline, repo, user)
|
||||
|
||||
currentPipeline, err = start(ctx, store, currentPipeline, user, repo, pipelineItems)
|
||||
currentPipeline, err = start(ctx, forge, store, currentPipeline, user, repo, pipelineItems)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failure to start pipeline for %s: %v", repo.FullName, err)
|
||||
log.Error().Err(err).Msg(msg)
|
||||
|
|
|
@ -21,13 +21,14 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/queue"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
// Cancel the pipeline and returns the status.
|
||||
func Cancel(ctx context.Context, store store.Store, repo *model.Repo, user *model.User, pipeline *model.Pipeline) error {
|
||||
func Cancel(ctx context.Context, _forge forge.Forge, store store.Store, repo *model.Repo, user *model.User, pipeline *model.Pipeline) error {
|
||||
if pipeline.Status != model.StatusRunning && pipeline.Status != model.StatusPending && pipeline.Status != model.StatusBlocked {
|
||||
return &ErrBadRequest{Msg: "Cannot cancel a non-running or non-pending or non-blocked pipeline"}
|
||||
}
|
||||
|
@ -88,7 +89,7 @@ func Cancel(ctx context.Context, store store.Store, repo *model.Repo, user *mode
|
|||
return err
|
||||
}
|
||||
|
||||
updatePipelineStatus(ctx, killedPipeline, repo, user)
|
||||
updatePipelineStatus(ctx, _forge, killedPipeline, repo, user)
|
||||
|
||||
if killedPipeline.Workflows, err = store.WorkflowGetTree(killedPipeline); err != nil {
|
||||
return err
|
||||
|
@ -100,6 +101,7 @@ func Cancel(ctx context.Context, store store.Store, repo *model.Repo, user *mode
|
|||
|
||||
func cancelPreviousPipelines(
|
||||
ctx context.Context,
|
||||
_forge forge.Forge,
|
||||
_store store.Store,
|
||||
pipeline *model.Pipeline,
|
||||
repo *model.Repo,
|
||||
|
@ -150,7 +152,7 @@ func cancelPreviousPipelines(
|
|||
continue
|
||||
}
|
||||
|
||||
if err = Cancel(ctx, _store, repo, user, active); err != nil {
|
||||
if err = Cancel(ctx, _forge, _store, repo, user, active); err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Str("ref", active.Ref).
|
||||
|
|
|
@ -34,7 +34,6 @@ var skipPipelineRegex = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
|
|||
|
||||
// Create a new pipeline and start it
|
||||
func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline *model.Pipeline) (*model.Pipeline, error) {
|
||||
_forge := server.Config.Services.Forge
|
||||
repoUser, err := _store.GetUser(repo.UserID)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failure to find repo owner via id '%d'", repo.UserID)
|
||||
|
@ -54,6 +53,13 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
|
|||
}
|
||||
}
|
||||
|
||||
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName)
|
||||
log.Error().Err(err).Str("repo", repo.FullName).Msg(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
// If the forge has a refresh token, the current access token
|
||||
// may be stale. Therefore, we should refresh prior to dispatching
|
||||
// the pipeline.
|
||||
|
@ -82,13 +88,13 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
|
|||
return nil, ErrFiltered
|
||||
} else if configFetchErr != nil {
|
||||
log.Debug().Str("repo", repo.FullName).Err(configFetchErr).Msgf("error while fetching config '%s' in '%s' with user: '%s'", repo.Config, pipeline.Ref, repoUser.Login)
|
||||
return nil, updatePipelineWithErr(ctx, _store, pipeline, repo, repoUser, fmt.Errorf("pipeline definition not found in %s", repo.FullName))
|
||||
return nil, updatePipelineWithErr(ctx, _forge, _store, pipeline, repo, repoUser, fmt.Errorf("pipeline definition not found in %s", repo.FullName))
|
||||
}
|
||||
|
||||
pipelineItems, parseErr := parsePipeline(_store, pipeline, repoUser, repo, forgeYamlConfigs, nil)
|
||||
pipelineItems, parseErr := parsePipeline(_forge, _store, pipeline, repoUser, repo, forgeYamlConfigs, nil)
|
||||
if pipeline_errors.HasBlockingErrors(parseErr) {
|
||||
log.Debug().Str("repo", repo.FullName).Err(parseErr).Msg("failed to parse yaml")
|
||||
return nil, updatePipelineWithErr(ctx, _store, pipeline, repo, repoUser, parseErr)
|
||||
return nil, updatePipelineWithErr(ctx, _forge, _store, pipeline, repo, repoUser, parseErr)
|
||||
} else if parseErr != nil {
|
||||
pipeline.Errors = pipeline_errors.GetPipelineErrors(parseErr)
|
||||
}
|
||||
|
@ -122,7 +128,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
|
|||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
if err := prepareStart(ctx, _store, pipeline, repoUser, repo); err != nil {
|
||||
if err := prepareStart(ctx, _forge, _store, pipeline, repoUser, repo); err != nil {
|
||||
log.Error().Err(err).Str("repo", repo.FullName).Msgf("error preparing pipeline for %s#%d", repo.FullName, pipeline.Number)
|
||||
return nil, err
|
||||
}
|
||||
|
@ -131,11 +137,11 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
|
|||
return pipeline, nil
|
||||
}
|
||||
|
||||
if err := updatePipelinePending(ctx, _store, pipeline, repo, repoUser); err != nil {
|
||||
if err := updatePipelinePending(ctx, _forge, _store, pipeline, repo, repoUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pipeline, err = start(ctx, _store, pipeline, repoUser, repo, pipelineItems)
|
||||
pipeline, err = start(ctx, _forge, _store, pipeline, repoUser, repo, pipelineItems)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failed to start pipeline for %s", repo.FullName)
|
||||
log.Error().Err(err).Msg(msg)
|
||||
|
@ -145,7 +151,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
|
|||
return pipeline, nil
|
||||
}
|
||||
|
||||
func updatePipelineWithErr(ctx context.Context, _store store.Store, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User, err error) error {
|
||||
func updatePipelineWithErr(ctx context.Context, _forge forge.Forge, _store store.Store, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User, err error) error {
|
||||
_pipeline, err := UpdateToStatusError(_store, *pipeline, err)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -153,12 +159,12 @@ func updatePipelineWithErr(ctx context.Context, _store store.Store, pipeline *mo
|
|||
// update value in ref
|
||||
*pipeline = *_pipeline
|
||||
|
||||
publishPipeline(ctx, pipeline, repo, repoUser)
|
||||
publishPipeline(ctx, _forge, pipeline, repo, repoUser)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePipelinePending(ctx context.Context, _store store.Store, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User) error {
|
||||
func updatePipelinePending(ctx context.Context, _forge forge.Forge, _store store.Store, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User) error {
|
||||
_pipeline, err := UpdateToStatusPending(_store, *pipeline, "")
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -166,7 +172,7 @@ func updatePipelinePending(ctx context.Context, _store store.Store, pipeline *mo
|
|||
// update value in ref
|
||||
*pipeline = *_pipeline
|
||||
|
||||
publishPipeline(ctx, pipeline, repo, repoUser)
|
||||
publishPipeline(ctx, _forge, pipeline, repo, repoUser)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -20,17 +20,25 @@ import (
|
|||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
// Decline updates the status to declined for blocked pipelines because of a gated repo
|
||||
func Decline(ctx context.Context, store store.Store, pipeline *model.Pipeline, user *model.User, repo *model.Repo) (*model.Pipeline, error) {
|
||||
forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName)
|
||||
log.Error().Err(err).Str("repo", repo.FullName).Msg(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
if pipeline.Status != model.StatusBlocked {
|
||||
return nil, fmt.Errorf("cannot decline a pipeline with status %s", pipeline.Status)
|
||||
}
|
||||
|
||||
pipeline, err := UpdateToStatusDeclined(store, *pipeline, user.Login)
|
||||
pipeline, err = UpdateToStatusDeclined(store, *pipeline, user.Login)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error updating pipeline. %w", err)
|
||||
}
|
||||
|
@ -53,7 +61,7 @@ func Decline(ctx context.Context, store store.Store, pipeline *model.Pipeline, u
|
|||
}
|
||||
}
|
||||
|
||||
updatePipelineStatus(ctx, pipeline, repo, user)
|
||||
updatePipelineStatus(ctx, forge, pipeline, repo, user)
|
||||
|
||||
publishToTopic(pipeline, repo)
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue