mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-09-27 22:12:00 +00:00
Merge remote-tracking branch 'upstream/main' into pr/6543/3539
This commit is contained in:
commit
5dc778e80d
145 changed files with 3396 additions and 1502 deletions
19
.cspell.json
19
.cspell.json
|
@ -79,7 +79,22 @@
|
|||
"HEALTHCHECK",
|
||||
"devx",
|
||||
"gomod",
|
||||
"laszlocph"
|
||||
"laszlocph",
|
||||
"charmbracelet",
|
||||
"GODEBUG",
|
||||
"netdns",
|
||||
"BUILDPLATFORM",
|
||||
"repology",
|
||||
"WORKDIR",
|
||||
"corepack",
|
||||
"binutils",
|
||||
"nocolor",
|
||||
"logfile",
|
||||
"Keyfunc",
|
||||
"protoc",
|
||||
"PROTOC",
|
||||
"GOBIN",
|
||||
"GOPATH"
|
||||
],
|
||||
"ignorePaths": [
|
||||
"**/node_modules/**/*",
|
||||
|
@ -103,10 +118,8 @@
|
|||
"agent/",
|
||||
"cli/",
|
||||
"cmd/",
|
||||
"docker/",
|
||||
"docs/",
|
||||
"pipeline/",
|
||||
"shared/",
|
||||
"server/"
|
||||
],
|
||||
"enableFiletypes": ["dockercompose"]
|
||||
|
|
16
.github/renovate.json
vendored
16
.github/renovate.json
vendored
|
@ -2,18 +2,28 @@
|
|||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>woodpecker-ci/renovate-config"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchCurrentVersion": "<1.0.0",
|
||||
"matchPackageNames": ["github.com/distribution/reference"],
|
||||
"matchUpdateTypes": ["major", "minor"],
|
||||
"dependencyDashboardApproval": true
|
||||
},
|
||||
{
|
||||
"matchPackageNames": ["github.com/charmbracelet/huh/spinner"],
|
||||
"enabled": false
|
||||
},
|
||||
{
|
||||
"matchManagers": ["docker-compose"],
|
||||
"matchFileNames": ["docker-compose.gitpod.yml"],
|
||||
"matchFileNames": ["docker-compose.gitpod.yaml"],
|
||||
"addLabels": ["devx"]
|
||||
},
|
||||
{
|
||||
"groupName": "golang (lang)",
|
||||
"groupName": "golang-lang",
|
||||
"matchPackagePatterns": ["^golang$", "xgo"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
},
|
||||
{
|
||||
"groupName": "golang (packages)",
|
||||
"groupName": "golang-packages",
|
||||
"matchManagers": ["gomod"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
},
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -41,6 +41,7 @@ extras/
|
|||
/build/
|
||||
/dist/
|
||||
/data/
|
||||
datastore/migration/testfiles/
|
||||
|
||||
docs/venv
|
||||
|
||||
|
|
2
.mockery.yaml
Normal file
2
.mockery.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
disable-version-string: true
|
|
@ -5,12 +5,12 @@ repos:
|
|||
- id: check-hooks-apply
|
||||
- id: check-useless-excludes
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.57.1
|
||||
rev: v1.57.2
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
|
@ -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
|
||||
|
|
|
@ -2,7 +2,7 @@ when:
|
|||
event: tag
|
||||
|
||||
variables:
|
||||
- &golang_image 'docker.io/golang:1.22.1'
|
||||
- &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'
|
||||
- &xgo_version 'go-1.21.2'
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
variables:
|
||||
- &golang_image 'docker.io/golang:1.22.1'
|
||||
- &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'
|
||||
- &xgo_version 'go-1.21.2'
|
||||
- &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:3.2.0'
|
||||
- &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'
|
||||
- &platforms_server 'linux/arm/v7,linux/arm64/v8,linux/amd64,linux/ppc64le,linux/riscv64'
|
||||
- &platforms_preview 'linux/amd64'
|
||||
|
|
13
Makefile
13
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
|
||||
|
|
|
@ -53,6 +53,7 @@ func (c *client) Close() error {
|
|||
|
||||
func (c *client) newBackOff() backoff.BackOff {
|
||||
b := backoff.NewExponentialBackOff()
|
||||
b.MaxElapsedTime = 0
|
||||
b.MaxInterval = 10 * time.Second //nolint: gomnd
|
||||
b.InitialInterval = 10 * time.Millisecond //nolint: gomnd
|
||||
return b
|
||||
|
|
|
@ -50,7 +50,7 @@ func NewRunner(workEngine rpc.Peer, f rpc.Filter, h string, state *State, backen
|
|||
}
|
||||
}
|
||||
|
||||
func (r *Runner) Run(runnerCtx context.Context) error {
|
||||
func (r *Runner) Run(runnerCtx context.Context) error { //nolint:contextcheck
|
||||
log.Debug().Msg("request next execution")
|
||||
|
||||
meta, _ := metadata.FromOutgoingContext(runnerCtx)
|
||||
|
|
|
@ -37,7 +37,7 @@ func Before(c *cli.Context) error {
|
|||
|
||||
log.Debug().Msg("Checking for updates ...")
|
||||
|
||||
newVersion, err := update.CheckForUpdate(waitForUpdateCheck, true)
|
||||
newVersion, err := update.CheckForUpdate(waitForUpdateCheck, false)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Failed to check for updates")
|
||||
return
|
||||
|
@ -57,8 +57,8 @@ 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
|
||||
case <-time.After(time.Millisecond * 250):
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ func lintFile(_ *cli.Context, file string) error {
|
|||
hasErrors = true
|
||||
}
|
||||
|
||||
if data := err.GetLinterData(); data != nil {
|
||||
if data := pipeline_errors.GetLinterData(err); data != nil {
|
||||
line = fmt.Sprintf("%s %s\t%s", line, output.String(data.Field).Bold(), err.Message)
|
||||
} else {
|
||||
line = fmt.Sprintf("%s %s", line, err.Message)
|
||||
|
|
|
@ -13,8 +13,10 @@ import (
|
|||
|
||||
// Command exports the setup command.
|
||||
var Command = &cli.Command{
|
||||
Name: "setup",
|
||||
Usage: "setup the woodpecker-cli for the first time",
|
||||
Name: "setup",
|
||||
Usage: "setup the woodpecker-cli for the first time",
|
||||
Args: true,
|
||||
ArgsUsage: "[server-url]",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "server-url",
|
||||
|
@ -45,6 +47,9 @@ func setup(c *cli.Context) error {
|
|||
}
|
||||
|
||||
serverURL := c.String("server-url")
|
||||
if serverURL == "" {
|
||||
serverURL = c.Args().First()
|
||||
}
|
||||
|
||||
if serverURL == "" {
|
||||
serverURL, err = ui.Ask("Enter the URL of the woodpecker server", "https://ci.woodpecker-ci.org", true)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/huh/spinner"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
@ -37,13 +38,27 @@ func receiveTokenFromUI(c context.Context, serverURL string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
spinnerCtx, spinnerDone := context.WithCancelCause(c)
|
||||
go func() {
|
||||
err = spinner.New().
|
||||
Title("Waiting for token ...").
|
||||
Context(spinnerCtx).
|
||||
Run()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// wait for token to be received or timeout
|
||||
select {
|
||||
case token := <-tokenReceived:
|
||||
spinnerDone(nil)
|
||||
return token, nil
|
||||
case <-c.Done():
|
||||
spinnerDone(nil)
|
||||
return "", c.Err()
|
||||
case <-time.After(5 * time.Minute):
|
||||
spinnerDone(nil)
|
||||
return "", errors.New("timed out waiting for token")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,79 +1,26 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
type askModel struct {
|
||||
prompt string
|
||||
required bool
|
||||
textInput textinput.Model
|
||||
err error
|
||||
}
|
||||
|
||||
func (m askModel) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
func (m askModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.Type {
|
||||
case tea.KeyEnter:
|
||||
if !m.required || (m.required && strings.TrimSpace(m.textInput.Value()) != "") {
|
||||
return m, tea.Quit
|
||||
}
|
||||
case tea.KeyCtrlC, tea.KeyEsc:
|
||||
return m, tea.Quit
|
||||
}
|
||||
default:
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
m.textInput, cmd = m.textInput.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m askModel) View() string {
|
||||
return fmt.Sprintf(
|
||||
"%s\n\n%s\n\n%s",
|
||||
m.prompt,
|
||||
m.textInput.View(),
|
||||
"(esc to quit)",
|
||||
) + "\n"
|
||||
}
|
||||
|
||||
func Ask(prompt, placeholder string, required bool) (string, error) {
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = placeholder
|
||||
ti.Focus()
|
||||
ti.CharLimit = 156
|
||||
ti.Width = 40
|
||||
|
||||
p := tea.NewProgram(askModel{
|
||||
prompt: prompt,
|
||||
textInput: ti,
|
||||
required: required,
|
||||
err: nil,
|
||||
})
|
||||
|
||||
_m, err := p.Run()
|
||||
var input string
|
||||
err := huh.NewInput().
|
||||
Title(prompt).
|
||||
Value(&input).
|
||||
Placeholder(placeholder).Validate(func(s string) error {
|
||||
if required && strings.TrimSpace(s) == "" {
|
||||
return errors.New("required")
|
||||
}
|
||||
return nil
|
||||
}).Run()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
m, ok := _m.(askModel)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unexpected model: %T", _m)
|
||||
}
|
||||
|
||||
text := strings.TrimSpace(m.textInput.Value())
|
||||
|
||||
return text, nil
|
||||
return strings.TrimSpace(input), nil
|
||||
}
|
||||
|
|
|
@ -1,71 +1,19 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
type confirmModel struct {
|
||||
confirmed bool
|
||||
prompt string
|
||||
err error
|
||||
}
|
||||
|
||||
func (m confirmModel) Init() tea.Cmd {
|
||||
return textinput.Blink
|
||||
}
|
||||
|
||||
func (m confirmModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
if msg.Runes != nil {
|
||||
switch msg.Runes[0] {
|
||||
case 'y':
|
||||
m.confirmed = true
|
||||
return m, tea.Quit
|
||||
case 'n':
|
||||
m.confirmed = false
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
switch msg.Type {
|
||||
case tea.KeyCtrlC, tea.KeyEsc:
|
||||
return m, tea.Quit
|
||||
}
|
||||
default:
|
||||
return m, nil
|
||||
}
|
||||
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m confirmModel) View() string {
|
||||
return fmt.Sprintf(
|
||||
"%s y / n (esc to quit)",
|
||||
m.prompt,
|
||||
) + "\n"
|
||||
}
|
||||
|
||||
func Confirm(prompt string) (bool, error) {
|
||||
p := tea.NewProgram(confirmModel{
|
||||
prompt: prompt,
|
||||
err: nil,
|
||||
})
|
||||
|
||||
_m, err := p.Run()
|
||||
var confirm bool
|
||||
err := huh.NewConfirm().
|
||||
Title(prompt).
|
||||
Affirmative("Yes!").
|
||||
Negative("No.").
|
||||
Value(&confirm).Run()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
m, ok := _m.(confirmModel)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("unexpected model: %T", _m)
|
||||
}
|
||||
|
||||
return m.confirmed, nil
|
||||
return confirm, err
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package update
|
||||
|
||||
type GithubRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
type VersionData struct {
|
||||
Latest string `json:"latest"`
|
||||
Next string `json:"next"`
|
||||
RC string `json:"rc"`
|
||||
}
|
||||
|
||||
type NewVersion struct {
|
||||
|
@ -13,4 +11,7 @@ type NewVersion struct {
|
|||
AssetURL string
|
||||
}
|
||||
|
||||
const githubReleaseURL = "https://api.github.com/repos/woodpecker-ci/woodpecker/releases/latest"
|
||||
const (
|
||||
woodpeckerVersionURL = "https://woodpecker-ci.org/version.json"
|
||||
githubBinaryURL = "https://github.com/woodpecker-ci/woodpecker/releases/download/v%s/woodpecker-cli_%s_%s.tar.gz"
|
||||
)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
|
@ -17,14 +18,18 @@ import (
|
|||
)
|
||||
|
||||
func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) {
|
||||
return checkForUpdate(ctx, woodpeckerVersionURL, force)
|
||||
}
|
||||
|
||||
func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVersion, error) {
|
||||
log.Debug().Msgf("Current version: %s", version.String())
|
||||
|
||||
if version.String() == "dev" && !force {
|
||||
log.Debug().Msgf("Skipping update check for development version")
|
||||
if (version.String() == "dev" || strings.HasPrefix(version.String(), "next-")) && !force {
|
||||
log.Debug().Msgf("Skipping update check for development & next versions")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", githubReleaseURL, nil)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", versionURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -39,34 +44,32 @@ func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) {
|
|||
return nil, errors.New("failed to fetch the latest release")
|
||||
}
|
||||
|
||||
var release GithubRelease
|
||||
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
|
||||
var versionData VersionData
|
||||
if err := json.NewDecoder(resp.Body).Decode(&versionData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
upstreamVersion := versionData.Latest
|
||||
if strings.HasPrefix(version.String(), "next-") {
|
||||
upstreamVersion = versionData.Next
|
||||
} else if strings.HasSuffix(version.String(), "rc-") {
|
||||
upstreamVersion = versionData.RC
|
||||
}
|
||||
|
||||
installedVersion := strings.TrimPrefix(version.Version, "v")
|
||||
upstreamVersion = strings.TrimPrefix(upstreamVersion, "v")
|
||||
|
||||
// using the latest release
|
||||
if release.TagName == version.String() && !force {
|
||||
if installedVersion == upstreamVersion && !force {
|
||||
log.Debug().Msgf("No new version available")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
log.Debug().Msgf("Latest version: %s", release.TagName)
|
||||
|
||||
assetURL := ""
|
||||
fileName := fmt.Sprintf("woodpecker-cli_%s_%s.tar.gz", runtime.GOOS, runtime.GOARCH)
|
||||
for _, asset := range release.Assets {
|
||||
if fileName == asset.Name {
|
||||
assetURL = asset.BrowserDownloadURL
|
||||
log.Debug().Msgf("Found asset for the current OS and arch: %s", assetURL)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if assetURL == "" {
|
||||
return nil, errors.New("no asset found for the current OS")
|
||||
}
|
||||
log.Debug().Msgf("New version available: %s", upstreamVersion)
|
||||
|
||||
assetURL := fmt.Sprintf(githubBinaryURL, upstreamVersion, runtime.GOOS, runtime.GOARCH)
|
||||
return &NewVersion{
|
||||
Version: release.TagName,
|
||||
Version: upstreamVersion,
|
||||
AssetURL: assetURL,
|
||||
}, nil
|
||||
}
|
||||
|
|
61
cli/update/updater_test.go
Normal file
61
cli/update/updater_test.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/version"
|
||||
)
|
||||
|
||||
func TestCheckForUpdate(t *testing.T) {
|
||||
version.Version = "1.0.0"
|
||||
fixtureHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/version.json" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = io.WriteString(w, `{"latest": "1.0.1", "next": "1.0.2", "rc": "1.0.3"}`)
|
||||
}
|
||||
ts := httptest.NewServer(http.HandlerFunc(fixtureHandler))
|
||||
defer ts.Close()
|
||||
|
||||
newVersion, err := checkForUpdate(context.Background(), ts.URL+"/version.json", false)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to check for updates: %v", err)
|
||||
}
|
||||
|
||||
if newVersion == nil || newVersion.Version != "1.0.1" {
|
||||
t.Fatalf("Expected a new version 1.0.1, got: %s", newVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDownloadNewVersion(t *testing.T) {
|
||||
downloadFilePath := "/woodpecker-cli_linux_amd64.tar.gz"
|
||||
|
||||
fixtureHandler := func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != downloadFilePath {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = io.WriteString(w, `blob`)
|
||||
}
|
||||
ts := httptest.NewServer(http.HandlerFunc(fixtureHandler))
|
||||
defer ts.Close()
|
||||
|
||||
file, err := downloadNewVersion(context.Background(), ts.URL+downloadFilePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to download new version: %v", err)
|
||||
}
|
||||
|
||||
if file == "" {
|
||||
t.Fatalf("Expected a file path, got: %s", file)
|
||||
}
|
||||
|
||||
_ = os.Remove(file)
|
||||
}
|
|
@ -2012,6 +2012,53 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/logs/{number}/{stepId}": {
|
||||
"delete": {
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Pipeline logs"
|
||||
],
|
||||
"summary": "Deletes step log",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the step id",
|
||||
"name": "stepId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "No Content"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{repo_id}/move": {
|
||||
"post": {
|
||||
"produces": [
|
||||
|
@ -3844,6 +3891,9 @@ const docTemplate = `{
|
|||
"Org": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"forge_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -3922,7 +3972,7 @@ const docTemplate = `{
|
|||
"errors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/errors.PipelineError"
|
||||
"$ref": "#/definitions/types.PipelineError"
|
||||
}
|
||||
},
|
||||
"event": {
|
||||
|
@ -4048,6 +4098,9 @@ const docTemplate = `{
|
|||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allow_deploy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allow_pr": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -4072,6 +4125,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"
|
||||
|
@ -4123,6 +4179,9 @@ const docTemplate = `{
|
|||
"RepoPatch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allow_deploy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allow_pr": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -4365,6 +4424,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"
|
||||
|
@ -4402,45 +4464,6 @@ const docTemplate = `{
|
|||
"EventManual"
|
||||
]
|
||||
},
|
||||
"errors.PipelineError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"is_warning": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/errors.PipelineErrorType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors.PipelineErrorType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"linter",
|
||||
"deprecation",
|
||||
"compiler",
|
||||
"generic",
|
||||
"bad_habit"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"PipelineErrorTypeBadHabit": "some bad-habit error",
|
||||
"PipelineErrorTypeCompiler": "some error with the config semantics",
|
||||
"PipelineErrorTypeDeprecation": "using some deprecated feature",
|
||||
"PipelineErrorTypeGeneric": "some generic error",
|
||||
"PipelineErrorTypeLinter": "some error with the config syntax"
|
||||
},
|
||||
"x-enum-varnames": [
|
||||
"PipelineErrorTypeLinter",
|
||||
"PipelineErrorTypeDeprecation",
|
||||
"PipelineErrorTypeCompiler",
|
||||
"PipelineErrorTypeGeneric",
|
||||
"PipelineErrorTypeBadHabit"
|
||||
]
|
||||
},
|
||||
"model.Workflow": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -4487,6 +4510,45 @@ const docTemplate = `{
|
|||
"$ref": "#/definitions/StatusValue"
|
||||
}
|
||||
}
|
||||
},
|
||||
"types.PipelineError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"is_warning": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/types.PipelineErrorType"
|
||||
}
|
||||
}
|
||||
},
|
||||
"types.PipelineErrorType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"linter",
|
||||
"deprecation",
|
||||
"compiler",
|
||||
"generic",
|
||||
"bad_habit"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"PipelineErrorTypeBadHabit": "some bad-habit error",
|
||||
"PipelineErrorTypeCompiler": "some error with the config semantics",
|
||||
"PipelineErrorTypeDeprecation": "using some deprecated feature",
|
||||
"PipelineErrorTypeGeneric": "some generic error",
|
||||
"PipelineErrorTypeLinter": "some error with the config syntax"
|
||||
},
|
||||
"x-enum-varnames": [
|
||||
"PipelineErrorTypeLinter",
|
||||
"PipelineErrorTypeDeprecation",
|
||||
"PipelineErrorTypeCompiler",
|
||||
"PipelineErrorTypeGeneric",
|
||||
"PipelineErrorTypeBadHabit"
|
||||
]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
|
|
@ -246,11 +246,6 @@ var flags = append([]cli.Flag{
|
|||
Usage: "Disable version check in admin web ui.",
|
||||
Name: "skip-version-check",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
EnvVars: []string{"WOODPECKER_ADDONS"},
|
||||
Name: "addons",
|
||||
Usage: "list of addon files",
|
||||
},
|
||||
//
|
||||
// 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",
|
||||
|
@ -341,9 +347,10 @@ var flags = append([]cli.Flag{
|
|||
Value: true,
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
EnvVars: []string{"WOODPECKER_GITHUB_SKIP_VERIFY"},
|
||||
Name: "github-skip-verify",
|
||||
Usage: "github skip ssl verification",
|
||||
EnvVars: []string{"WOODPECKER_GITHUB_PUBLIC_ONLY"},
|
||||
Name: "github-public-only",
|
||||
Usage: "github tokens should only get access to public repos",
|
||||
Value: false,
|
||||
},
|
||||
//
|
||||
// Gitea
|
||||
|
@ -353,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",
|
||||
|
@ -389,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
|
||||
//
|
||||
|
@ -409,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)
|
||||
//
|
||||
|
@ -440,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,17 +29,9 @@ 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/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"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/shared/addon"
|
||||
addonTypes "go.woodpecker-ci.org/woodpecker/v2/shared/addon/types"
|
||||
)
|
||||
|
||||
func setupStore(c *cli.Context) (store.Store, error) {
|
||||
|
@ -101,108 +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) {
|
||||
addonForge, err := addon.Load[forge.Forge](c.StringSlice("addons"), addonTypes.TypeForge)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if addonForge != nil {
|
||||
return addonForge.Value, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
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"),
|
||||
}
|
||||
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,17 +1,19 @@
|
|||
# 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:21-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.
|
||||
|
@ -20,9 +22,12 @@ COPY Makefile /
|
|||
ENV PATH=$PATH:/usr/local/go/bin
|
||||
|
||||
# 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
|
||||
|
|
|
@ -75,7 +75,7 @@ kubectl apply -f $PLUGIN_TEMPLATE
|
|||
|
||||
```yaml title=".woodpecker.yaml"
|
||||
steps:
|
||||
deploy-to-k8s:
|
||||
- name: deploy-to-k8s
|
||||
image: laszlocloud/my-k8s-plugin
|
||||
settings:
|
||||
template: config/k8s/service.yaml
|
||||
|
|
|
@ -50,7 +50,8 @@ git commit -m "updated README [CI SKIP]"
|
|||
|
||||
## Steps
|
||||
|
||||
Every step of your workflow executes commands inside a specified container. The defined commands are executed serially.
|
||||
Every step of your workflow executes commands inside a specified container.<br>
|
||||
The defined steps are executed in sequence by default, if they should run in parallel you can use [`depends_on`](./20-workflow-syntax.md#depends_on).<br>
|
||||
The associated commit is checked out with git to a workspace which is mounted to every step of the workflow as the working directory.
|
||||
|
||||
```diff
|
||||
|
@ -484,6 +485,19 @@ Normally steps of a workflow are executed serially in the order in which they ar
|
|||
- go test
|
||||
```
|
||||
|
||||
:::note
|
||||
You can define a step to start immediately without dependencies by adding an empty `depends_on: []`. By setting `depends_on` on a single step all other steps will be immediately executed as well if no further dependencies are specified.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: check code format
|
||||
image: mstruebing/editorconfig-checker
|
||||
depends_on: [] # enable parallel steps
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### `volumes`
|
||||
|
||||
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.
|
||||
|
|
|
@ -135,5 +135,5 @@ docker run --rm \
|
|||
These should also be built for different OS/architectures.
|
||||
- Use [built-in env vars](../50-environment.md#built-in-environment-variables) where possible.
|
||||
- Do not use any configuration except settings (and internal env vars). This means: Don't require using [`environment`](../50-environment.md) and don't require specific secret names.
|
||||
- Add a `docs.md` file, listing all your settings and plugin metadata ([example](https://codeberg.org/woodpecker-plugins/plugin-docker-buildx/src/branch/main/docs.md)).
|
||||
- Add your plugin to the [plugin index](/plugins) using your `docs.md` ([the example above in the index](https://woodpecker-ci.org/plugins/Docker%20Buildx)).
|
||||
- Add a `docs.md` file, listing all your settings and plugin metadata ([example](https://github.com/woodpecker-ci/plugin-git/blob/main/docs.md)).
|
||||
- Add your plugin to the [plugin index](/plugins) using your `docs.md` ([the example above in the index](https://woodpecker-ci.org/plugins/Git%20Clone)).
|
||||
|
|
|
@ -16,6 +16,15 @@ Your Version-Control-System will notify Woodpecker about events via webhooks. If
|
|||
|
||||
Enables handling webhook's pull request event. If disabled, then pipeline won't run for pull requests.
|
||||
|
||||
## Allow deployments
|
||||
|
||||
Enables a pipeline to be started with the `deploy` event from a successful pipeline.
|
||||
|
||||
:::danger
|
||||
Only activate this option if you trust all users who have push access to your repository.
|
||||
Otherwise, these users will be able to steal secrets that are only available for `deploy` events.
|
||||
:::
|
||||
|
||||
## Protected
|
||||
|
||||
Every pipeline initiated by an webhook event needs to be approved by a project members with push permissions before being executed.
|
||||
|
|
|
@ -473,12 +473,6 @@ Supported variables:
|
|||
- `owner`: the repo's owner
|
||||
- `repo`: the repo's name
|
||||
|
||||
### `WOODPECKER_ADDONS`
|
||||
|
||||
> Default: empty
|
||||
|
||||
List of addon files. See [addons](./75-addons/75-overview.md).
|
||||
|
||||
---
|
||||
|
||||
### `WOODPECKER_LIMIT_MEM_SWAP`
|
||||
|
@ -559,4 +553,8 @@ See [Bitbucket configuration](./11-forges/50-bitbucket.md#configuration)
|
|||
|
||||
### `WOODPECKER_GITLAB_...`
|
||||
|
||||
See [Gitlab configuration](./11-forges/40-gitlab.md#configuration)
|
||||
See [GitLab configuration](./11-forges/40-gitlab.md#configuration)
|
||||
|
||||
### `WOODPECKER_ADDON_FORGE`
|
||||
|
||||
See [addon forges](./11-forges/100-addon.md).
|
||||
|
|
68
docs/docs/30-administration/11-forges/100-addon.md
Normal file
68
docs/docs/30-administration/11-forges/100-addon.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
# 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.
|
||||
|
||||
:::warning
|
||||
Addon forges are still experimental. Their implementation can change and break at any time.
|
||||
:::
|
||||
|
||||
:::danger
|
||||
You need to trust the author of the addon forge you use. It can access authentication codes and other possibly sensitive information.
|
||||
:::
|
||||
|
||||
## Usage
|
||||
|
||||
To use an addon forge, download the correct addon version. Then, you can add the following to your configuration:
|
||||
|
||||
```ini
|
||||
WOODPECKER_ADDON_FORGE=/path/to/your/addon/forge/file
|
||||
```
|
||||
|
||||
In case you run Woodpecker as container, you probably want to mount the addon binary to `/opt/addons/`.
|
||||
|
||||
### Bug reports
|
||||
|
||||
If you experience bugs, please check which component has the issue. If it's the addon, **do not raise an issue in the main repository**, but rather use the separate addon repositories. To check which component is responsible for the bug, look at the logs. Logs from addons are marked with a special field `addon` containing their addon file name.
|
||||
|
||||
## List of addon forges
|
||||
|
||||
If you wrote or found an addon forge, please add it here so others can find it!
|
||||
|
||||
_Be the first one to add your addon forge!_
|
||||
|
||||
## Creating addon forges
|
||||
|
||||
Addons use RPC to communicate to the server and are implemented using the [`go-plugin` library](https://github.com/hashicorp/go-plugin).
|
||||
|
||||
### Writing your code
|
||||
|
||||
This example will use the Go language.
|
||||
|
||||
Directly import Woodpecker's Go packages (`go.woodpecker-ci.org/woodpecker/woodpecker/v2`) and use the interfaces and types defined there.
|
||||
|
||||
In the `main` function, just call `"go.woodpecker-ci.org/woodpecker/v2/server/forge/addon".Serve` with a `"go.woodpecker-ci.org/woodpecker/v2/server/forge".Forge` as argument.
|
||||
This will take care of connecting the addon forge to the server.
|
||||
|
||||
### Example structure
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/addon"
|
||||
forgeTypes "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
||||
func main() {
|
||||
addon.Serve(config{})
|
||||
}
|
||||
|
||||
type config struct {
|
||||
}
|
||||
|
||||
// `config` must implement `"go.woodpecker-ci.org/woodpecker/v2/server/forge".Forge`. You must directly use Woodpecker's packages - see imports above.
|
||||
```
|
|
@ -81,3 +81,9 @@ Read the value for `WOODPECKER_GITHUB_SECRET` from the specified filepath.
|
|||
> Default: `false`
|
||||
|
||||
Configure if SSL verification should be skipped.
|
||||
|
||||
### `WOODPECKER_GITHUB_PUBLIC_ONLY`
|
||||
|
||||
> Default: `false`
|
||||
|
||||
Configures the GitHub OAuth client to only obtain a token that can manage public repositories.
|
||||
|
|
|
@ -10,24 +10,23 @@ Woodpecker comes with experimental support for Bitbucket Datacenter / Server, fo
|
|||
|
||||
To enable Bitbucket Server you should configure the Woodpecker container using the following environment variables:
|
||||
|
||||
```diff
|
||||
# docker-compose.yml
|
||||
version: '3'
|
||||
```diff title="docker-compose.yaml"
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
woodpecker-server:
|
||||
[...]
|
||||
environment:
|
||||
- [...]
|
||||
+ - WOODPECKER_BITBUCKET_DC=true
|
||||
+ - WOODPECKER_BITBUCKET_DC_GIT_USERNAME=foo
|
||||
+ - WOODPECKER_BITBUCKET_DC_GIT_PASSWORD=bar
|
||||
+ - WOODPECKER_BITBUCKET_DC_CLIENT_ID=xxx
|
||||
+ - WOODPECKER_BITBUCKET_DC_CLIENT_SECRET=yyy
|
||||
+ - WOODPECKER_BITBUCKET_DC_URL=http://stash.mycompany.com
|
||||
services:
|
||||
woodpecker-server:
|
||||
[...]
|
||||
environment:
|
||||
- [...]
|
||||
+ - WOODPECKER_BITBUCKET_DC=true
|
||||
+ - WOODPECKER_BITBUCKET_DC_GIT_USERNAME=foo
|
||||
+ - WOODPECKER_BITBUCKET_DC_GIT_PASSWORD=bar
|
||||
+ - WOODPECKER_BITBUCKET_DC_CLIENT_ID=xxx
|
||||
+ - WOODPECKER_BITBUCKET_DC_CLIENT_SECRET=yyy
|
||||
+ - WOODPECKER_BITBUCKET_DC_URL=http://stash.mycompany.com
|
||||
|
||||
woodpecker-agent:
|
||||
[...]
|
||||
woodpecker-agent:
|
||||
[...]
|
||||
```
|
||||
|
||||
## Service Account
|
||||
|
|
|
@ -40,6 +40,11 @@ steps:
|
|||
|
||||
You can use [Limit Ranges](https://kubernetes.io/docs/concepts/policy/limit-range/) if you want to set the limits by per-namespace basis.
|
||||
|
||||
### Runtime class
|
||||
|
||||
`runtimeClassName` specifies the name of the RuntimeClass which will be used to run this pod. If no `runtimeClassName` is specified, the default RuntimeHandler will be used.
|
||||
See the [kubernetes documentation](https://kubernetes.io/docs/concepts/containers/runtime-class/) for more information on specifying runtime classes.
|
||||
|
||||
### Service account
|
||||
|
||||
`serviceAccountName` specifies the name of the ServiceAccount which the pod will mount. This service account must be created externally.
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
# Creating addons
|
||||
|
||||
Addons are written in Go.
|
||||
|
||||
## Writing your code
|
||||
|
||||
An addon consists of two variables/functions in Go.
|
||||
|
||||
1. The `Type` variable. Specifies the type of the addon and must be directly accessed from `shared/addons/types/types.go`.
|
||||
2. The `Addon` function which is the main point of your addon.
|
||||
This function takes the `zerolog` logger you should use to log errors, warnings, etc. as argument.
|
||||
|
||||
It returns two values:
|
||||
|
||||
1. The actual addon. For type reference see [table below](#return-types).
|
||||
2. An error. If this error is not `nil`, Woodpecker exits.
|
||||
|
||||
Directly import Woodpecker's Go package (`go.woodpecker-ci.org/woodpecker/woodpecker/v2`) and use the interfaces and types defined there.
|
||||
|
||||
### Return types
|
||||
|
||||
| Addon type | Return type |
|
||||
| ---------- | -------------------------------------------------------------------- |
|
||||
| `Forge` | `"go.woodpecker-ci.org/woodpecker/woodpecker/v2/server/forge".Forge` |
|
||||
|
||||
### Using configurations
|
||||
|
||||
If you write a plugin for the server (`Forge` and the services), you can access the server config.
|
||||
|
||||
Therefore, use the `"go.woodpecker-ci.org/woodpecker/v2/server".Config` variable.
|
||||
|
||||
:::warning
|
||||
The config is not available when your addon is initialized, i.e., the `Addon` function is called.
|
||||
Only use the config in the interface methods.
|
||||
:::
|
||||
|
||||
## Compiling
|
||||
|
||||
After you write your addon code, compile your addon:
|
||||
|
||||
```sh
|
||||
go build -buildmode plugin
|
||||
```
|
||||
|
||||
The output file is your addon that is now ready to be used.
|
||||
|
||||
## Restrictions
|
||||
|
||||
Addons must directly depend on Woodpecker's core (`go.woodpecker-ci.org/woodpecker/woodpecker/v2`).
|
||||
The addon must have been built with **exactly the same code** as the Woodpecker instance you'd like to use it on. This means: If you build your addon with a specific commit from Woodpecker `next`, you can likely only use it with the Woodpecker version compiled from this commit.
|
||||
Also, if you change something inside Woodpecker without committing, it might fail because you need to recompile your addon with this code first.
|
||||
|
||||
In addition to this, addons are only supported on Linux, FreeBSD, and macOS.
|
||||
|
||||
:::info
|
||||
It is recommended to at least support the latest version of Woodpecker.
|
||||
:::
|
||||
|
||||
### Compile for different versions
|
||||
|
||||
As long as there are no changes to Woodpecker's interfaces,
|
||||
or they are backwards-compatible, you can compile the addon for multiple versions
|
||||
by changing the version of `go.woodpecker-ci.org/woodpecker/woodpecker/v2` using `go get` before compiling.
|
||||
|
||||
## Logging
|
||||
|
||||
The entrypoint receives a `zerolog.Logger` as input. **Do not use any other logging solution.** This logger follows the configuration of the Woodpecker instance and adds a special field `addon` to the log entries which allows users to find out which component is writing the log messages.
|
||||
|
||||
## Example structure
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
addon_types "go.woodpecker-ci.org/woodpecker/v2/shared/addon/types"
|
||||
)
|
||||
|
||||
var Type = addon_types.TypeForge
|
||||
|
||||
func Addon(logger zerolog.Logger) (forge.Forge, error) {
|
||||
logger.Info().Msg("hello world from addon")
|
||||
return &config{l: logger}, nil
|
||||
}
|
||||
|
||||
type config struct {
|
||||
l zerolog.Logger
|
||||
}
|
||||
|
||||
// In this case, `config` must implement `forge.Forge`. You must directly use Woodpecker's packages - see imports above.
|
||||
```
|
|
@ -1,40 +0,0 @@
|
|||
# Addons
|
||||
|
||||
:::warning
|
||||
Addons are still experimental. Their implementation can change and break at any time.
|
||||
:::
|
||||
|
||||
:::danger
|
||||
You need to trust the author of the addons you use. Depending on their type, addons can access forge authentication codes, your secrets or other sensitive information.
|
||||
:::
|
||||
|
||||
To adapt Woodpecker to your needs beyond the [configuration](../10-server-config.md), Woodpecker has its own **addon** system, built ontop of [Go's internal plugin system](https://go.dev/pkg/plugin).
|
||||
|
||||
Addons can be used for:
|
||||
|
||||
- Forges
|
||||
|
||||
## Restrictions
|
||||
|
||||
Addons are restricted by how Go plugins work. This includes the following restrictions:
|
||||
|
||||
- only supported on Linux, FreeBSD, and macOS
|
||||
- addons must have been built for the correct Woodpecker version. If an addon is not provided specifically for this version, you likely won't be able to use it.
|
||||
|
||||
## Usage
|
||||
|
||||
To use an addon, download the addon version built for your Woodpecker version. Then, you can add the following to your configuration:
|
||||
|
||||
```ini
|
||||
WOODPECKER_ADDONS=/path/to/your/addon/file.so
|
||||
```
|
||||
|
||||
In case you run Woodpecker as container, you probably want to mount the addon binaries to `/opt/addons/`.
|
||||
|
||||
You can list multiple addons, Woodpecker will automatically determine their type. If you specify multiple addons with the same type, only the first one will be used.
|
||||
|
||||
Using an addon always overwrites Woodpecker's internal setup. This means, that a forge addon will be used if specified, no matter what's configured for the forges natively supported by Woodpecker.
|
||||
|
||||
### Bug reports
|
||||
|
||||
If you experience bugs, please check which component has the issue. If it's the addon, **do not raise an issue in the main repository**, but rather use the separate addon repositories. To check which component is responsible for the bug, look at the logs. Logs from addons are marked with a special field `addon` containing their addon file name.
|
|
@ -1,6 +0,0 @@
|
|||
label: 'Addons'
|
||||
collapsible: true
|
||||
collapsed: true
|
||||
link:
|
||||
type: 'doc'
|
||||
id: 'overview'
|
|
@ -8,7 +8,7 @@
|
|||
## Addons and extensions
|
||||
|
||||
If you are wondering whether your contribution will be accepted to be merged in the Woodpecker core, or whether it's better to write an
|
||||
[addon](../30-administration/75-addons/75-overview.md), [extension](../30-administration/100-external-configuration-api.md) or an
|
||||
[addon forge](../30-administration/11-forges/100-addon.md), [extension](../30-administration/100-external-configuration-api.md) or an
|
||||
[external custom backend](../30-administration/22-backends/50-custom-backends.md), please check these points:
|
||||
|
||||
- Is your change very specific to your setup and unlikely to be used by anyone else?
|
||||
|
|
|
@ -194,6 +194,11 @@
|
|||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@ importers:
|
|||
version: 3.1.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@docusaurus/preset-classic':
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.1(@algolia/client-search@4.22.1)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)(typescript@5.4.3)
|
||||
version: 3.1.1(@algolia/client-search@4.22.1)(@types/react@18.2.70)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)(typescript@5.4.3)
|
||||
'@easyops-cn/docusaurus-search-local':
|
||||
specifier: ^0.40.1
|
||||
version: 0.40.1(@docusaurus/theme-common@3.1.1)(@docusaurus/types@3.1.1)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@mdx-js/react':
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.1(@types/react@18.2.69)(react@18.2.0)
|
||||
version: 3.0.1(@types/react@18.2.70)(react@18.2.0)
|
||||
'@svgr/webpack':
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0(typescript@5.4.3)
|
||||
|
@ -69,7 +69,7 @@ importers:
|
|||
version: 20.11.30
|
||||
'@types/react':
|
||||
specifier: ^18.2.67
|
||||
version: 18.2.69
|
||||
version: 18.2.70
|
||||
'@types/react-helmet':
|
||||
specifier: ^6.1.11
|
||||
version: 6.1.11
|
||||
|
@ -100,7 +100,7 @@ importers:
|
|||
version: 3.1.1(react-dom@18.2.0)(react@18.2.0)
|
||||
'@docusaurus/theme-classic':
|
||||
specifier: ^3.0.0
|
||||
version: 3.1.1(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
version: 3.1.1(@types/react@18.2.70)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@docusaurus/types':
|
||||
specifier: ^3.0.0
|
||||
version: 3.1.1(react-dom@18.2.0)(react@18.2.0)
|
||||
|
@ -1481,7 +1481,7 @@ packages:
|
|||
resolution: {integrity: sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==}
|
||||
dev: false
|
||||
|
||||
/@docsearch/react@3.6.0(@algolia/client-search@4.22.1)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0):
|
||||
/@docsearch/react@3.6.0(@algolia/client-search@4.22.1)(@types/react@18.2.70)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0):
|
||||
resolution: {integrity: sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==}
|
||||
peerDependencies:
|
||||
'@types/react': '>= 16.8.0 < 19.0.0'
|
||||
|
@ -1501,7 +1501,7 @@ packages:
|
|||
'@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)(search-insights@2.13.0)
|
||||
'@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.22.1)(algoliasearch@4.22.1)
|
||||
'@docsearch/css': 3.6.0
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
algoliasearch: 4.22.1
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
@ -1676,7 +1676,7 @@ packages:
|
|||
'@docusaurus/react-loadable': 5.5.2(react@18.2.0)
|
||||
'@docusaurus/types': 3.1.1(react-dom@18.2.0)(react@18.2.0)
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
'@types/react-router-config': 5.0.11
|
||||
'@types/react-router-dom': 5.3.3
|
||||
react: 18.2.0
|
||||
|
@ -1979,7 +1979,7 @@ packages:
|
|||
- webpack-cli
|
||||
dev: false
|
||||
|
||||
/@docusaurus/preset-classic@3.1.1(@algolia/client-search@4.22.1)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)(typescript@5.4.3):
|
||||
/@docusaurus/preset-classic@3.1.1(@algolia/client-search@4.22.1)(@types/react@18.2.70)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)(typescript@5.4.3):
|
||||
resolution: {integrity: sha512-jG4ys/hWYf69iaN/xOmF+3kjs4Nnz1Ay3CjFLDtYa8KdxbmUhArA9HmP26ru5N0wbVWhY+6kmpYhTJpez5wTyg==}
|
||||
engines: {node: '>=18.0'}
|
||||
peerDependencies:
|
||||
|
@ -1995,9 +1995,9 @@ packages:
|
|||
'@docusaurus/plugin-google-gtag': 3.1.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@docusaurus/plugin-google-tag-manager': 3.1.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@docusaurus/plugin-sitemap': 3.1.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@docusaurus/theme-classic': 3.1.1(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@docusaurus/theme-classic': 3.1.1(@types/react@18.2.70)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@docusaurus/theme-common': 3.1.1(@docusaurus/types@3.1.1)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@docusaurus/theme-search-algolia': 3.1.1(@algolia/client-search@4.22.1)(@docusaurus/types@3.1.1)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)(typescript@5.4.3)
|
||||
'@docusaurus/theme-search-algolia': 3.1.1(@algolia/client-search@4.22.1)(@docusaurus/types@3.1.1)(@types/react@18.2.70)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)(typescript@5.4.3)
|
||||
'@docusaurus/types': 3.1.1(react-dom@18.2.0)(react@18.2.0)
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
@ -2028,11 +2028,11 @@ packages:
|
|||
peerDependencies:
|
||||
react: '*'
|
||||
dependencies:
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
|
||||
/@docusaurus/theme-classic@3.1.1(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3):
|
||||
/@docusaurus/theme-classic@3.1.1(@types/react@18.2.70)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3):
|
||||
resolution: {integrity: sha512-GiPE/jbWM8Qv1A14lk6s9fhc0LhPEQ00eIczRO4QL2nAQJZXkjPG6zaVx+1cZxPFWbAsqSjKe2lqkwF3fGkQ7Q==}
|
||||
engines: {node: '>=18.0'}
|
||||
peerDependencies:
|
||||
|
@ -2051,7 +2051,7 @@ packages:
|
|||
'@docusaurus/utils': 3.1.1(@docusaurus/types@3.1.1)
|
||||
'@docusaurus/utils-common': 3.1.1(@docusaurus/types@3.1.1)
|
||||
'@docusaurus/utils-validation': 3.1.1(@docusaurus/types@3.1.1)
|
||||
'@mdx-js/react': 3.0.1(@types/react@18.2.69)(react@18.2.0)
|
||||
'@mdx-js/react': 3.0.1(@types/react@18.2.70)(react@18.2.0)
|
||||
clsx: 2.1.0
|
||||
copy-text-to-clipboard: 3.2.0
|
||||
infima: 0.2.0-alpha.43
|
||||
|
@ -2100,7 +2100,7 @@ packages:
|
|||
'@docusaurus/utils': 3.1.1(@docusaurus/types@3.1.1)
|
||||
'@docusaurus/utils-common': 3.1.1(@docusaurus/types@3.1.1)
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
'@types/react-router-config': 5.0.11
|
||||
clsx: 2.1.0
|
||||
parse-numeric-range: 1.3.0
|
||||
|
@ -2128,14 +2128,14 @@ packages:
|
|||
- vue-template-compiler
|
||||
- webpack-cli
|
||||
|
||||
/@docusaurus/theme-search-algolia@3.1.1(@algolia/client-search@4.22.1)(@docusaurus/types@3.1.1)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)(typescript@5.4.3):
|
||||
/@docusaurus/theme-search-algolia@3.1.1(@algolia/client-search@4.22.1)(@docusaurus/types@3.1.1)(@types/react@18.2.70)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)(typescript@5.4.3):
|
||||
resolution: {integrity: sha512-tBH9VY5EpRctVdaAhT+b1BY8y5dyHVZGFXyCHgTrvcXQy5CV4q7serEX7U3SveNT9zksmchPyct6i1sFDC4Z5g==}
|
||||
engines: {node: '>=18.0'}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
dependencies:
|
||||
'@docsearch/react': 3.6.0(@algolia/client-search@4.22.1)(@types/react@18.2.69)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)
|
||||
'@docsearch/react': 3.6.0(@algolia/client-search@4.22.1)(@types/react@18.2.70)(react-dom@18.2.0)(react@18.2.0)(search-insights@2.13.0)
|
||||
'@docusaurus/core': 3.1.1(@docusaurus/types@3.1.1)(debug@4.3.4)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
'@docusaurus/logger': 3.1.1
|
||||
'@docusaurus/plugin-content-docs': 3.1.1(debug@4.3.4)(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3)
|
||||
|
@ -2195,7 +2195,7 @@ packages:
|
|||
dependencies:
|
||||
'@mdx-js/mdx': 3.0.1
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
commander: 5.1.0
|
||||
joi: 17.12.2
|
||||
react: 18.2.0
|
||||
|
@ -2656,14 +2656,14 @@ packages:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/@mdx-js/react@3.0.1(@types/react@18.2.69)(react@18.2.0):
|
||||
/@mdx-js/react@3.0.1(@types/react@18.2.70)(react@18.2.0):
|
||||
resolution: {integrity: sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16'
|
||||
react: '>=16'
|
||||
dependencies:
|
||||
'@types/mdx': 2.0.12
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
react: 18.2.0
|
||||
|
||||
/@napi-rs/wasm-runtime@0.1.2:
|
||||
|
@ -3406,31 +3406,31 @@ packages:
|
|||
/@types/react-helmet@6.1.11:
|
||||
resolution: {integrity: sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==}
|
||||
dependencies:
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
dev: true
|
||||
|
||||
/@types/react-router-config@5.0.11:
|
||||
resolution: {integrity: sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==}
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
'@types/react-router': 5.1.20
|
||||
|
||||
/@types/react-router-dom@5.3.3:
|
||||
resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==}
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
'@types/react-router': 5.1.20
|
||||
|
||||
/@types/react-router@5.1.20:
|
||||
resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==}
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.2.69
|
||||
'@types/react': 18.2.70
|
||||
|
||||
/@types/react@18.2.69:
|
||||
resolution: {integrity: sha512-W1HOMUWY/1Yyw0ba5TkCV+oqynRjG7BnteBB+B7JmAK7iw3l2SW+VGOxL+akPweix6jk2NNJtyJKpn4TkpfK3Q==}
|
||||
/@types/react@18.2.70:
|
||||
resolution: {integrity: sha512-hjlM2hho2vqklPhopNkXkdkeq6Lv8WSZTpr7956zY+3WS5cfYUewtCzsJLsbW5dEv3lfSeQ4W14ZFeKC437JRQ==}
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.12
|
||||
'@types/scheduler': 0.16.8
|
||||
|
@ -3963,7 +3963,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001600
|
||||
electron-to-chromium: 1.4.715
|
||||
electron-to-chromium: 1.4.717
|
||||
node-releases: 2.0.14
|
||||
update-browserslist-db: 1.0.13(browserslist@4.23.0)
|
||||
|
||||
|
@ -4926,8 +4926,8 @@ packages:
|
|||
/ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
|
||||
/electron-to-chromium@1.4.715:
|
||||
resolution: {integrity: sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==}
|
||||
/electron-to-chromium@1.4.717:
|
||||
resolution: {integrity: sha512-6Fmg8QkkumNOwuZ/5mIbMU9WI3H2fmn5ajcVya64I5Yr5CcNmO7vcLt0Y7c96DCiMO5/9G+4sI2r6eEvdg1F7A==}
|
||||
|
||||
/emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
@ -4983,8 +4983,8 @@ packages:
|
|||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
/es-module-lexer@1.4.2:
|
||||
resolution: {integrity: sha512-7nOqkomXZEaxUDJw21XZNtRk739QvrPSoZoRtbsEfcii00vdzZUh6zh1CQwHhrib8MdEtJfv5rJiGeb4KuV/vw==}
|
||||
/es-module-lexer@1.5.0:
|
||||
resolution: {integrity: sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw==}
|
||||
|
||||
/es6-promise@3.3.1:
|
||||
resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
|
||||
|
@ -4996,7 +4996,7 @@ packages:
|
|||
webpack: ^4.40.0 || ^5.0.0
|
||||
dependencies:
|
||||
esbuild: 0.20.2
|
||||
get-tsconfig: 4.7.2
|
||||
get-tsconfig: 4.7.3
|
||||
loader-utils: 2.0.4
|
||||
webpack: 5.91.0
|
||||
webpack-sources: 1.4.3
|
||||
|
@ -5163,8 +5163,8 @@ packages:
|
|||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
/express@4.19.1:
|
||||
resolution: {integrity: sha512-K4w1/Bp7y8iSiVObmCrtq8Cs79XjJc/RU2YYkZQ7wpUu5ZyZ7MtPHkqoMz4pf+mgXfNvo2qft8D9OnrH2ABk9w==}
|
||||
/express@4.19.2:
|
||||
resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
dependencies:
|
||||
accepts: 1.3.8
|
||||
|
@ -5469,8 +5469,8 @@ packages:
|
|||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
/get-tsconfig@4.7.2:
|
||||
resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==}
|
||||
/get-tsconfig@4.7.3:
|
||||
resolution: {integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==}
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
dev: false
|
||||
|
@ -9605,7 +9605,7 @@ packages:
|
|||
compression: 1.7.4
|
||||
connect-history-api-fallback: 2.0.0
|
||||
default-gateway: 6.0.3
|
||||
express: 4.19.1
|
||||
express: 4.19.2
|
||||
graceful-fs: 4.2.11
|
||||
html-entities: 2.5.2
|
||||
http-proxy-middleware: 2.0.6(@types/express@4.17.21)(debug@4.3.4)
|
||||
|
@ -9667,7 +9667,7 @@ packages:
|
|||
browserslist: 4.23.0
|
||||
chrome-trace-event: 1.0.3
|
||||
enhanced-resolve: 5.16.0
|
||||
es-module-lexer: 1.4.2
|
||||
es-module-lexer: 1.5.0
|
||||
eslint-scope: 5.1.1
|
||||
events: 3.3.0
|
||||
glob-to-regexp: 0.4.1
|
||||
|
|
|
@ -50,7 +50,8 @@ git commit -m "updated README [CI SKIP]"
|
|||
|
||||
## Steps
|
||||
|
||||
Every step of your workflow executes commands inside a specified container. The defined commands are executed serially.
|
||||
Every step of your workflow executes commands inside a specified container.<br>
|
||||
The defined steps are executed in sequence by default, if they should run in parallel you can use [`depends_on`](./20-workflow-syntax.md#depends_on).<br>
|
||||
The associated commit is checked out with git to a workspace which is mounted to every step of the workflow as the working directory.
|
||||
|
||||
```diff
|
||||
|
@ -478,6 +479,19 @@ Normally steps of a workflow are executed serially in the order in which they ar
|
|||
- go test
|
||||
```
|
||||
|
||||
:::note
|
||||
You can define a step to start immediately without dependencies by adding an empty `depends_on: []`. By setting `depends_on` on a single step all other steps will be immediately executed as well if no further dependencies are specified.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- name: check code format
|
||||
image: mstruebing/editorconfig-checker
|
||||
depends_on: [] # enable parallel steps
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### `volumes`
|
||||
|
||||
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.
|
||||
|
|
23
go.mod
23
go.mod
|
@ -11,25 +11,27 @@ require (
|
|||
github.com/alessio/shellescape v1.4.2
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/cenkalti/backoff/v4 v4.2.1
|
||||
github.com/charmbracelet/bubbles v0.18.0
|
||||
github.com/charmbracelet/bubbletea v0.25.0
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/charmbracelet/huh v0.3.0
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20240327025511-ec643317aa10
|
||||
github.com/distribution/reference v0.5.0
|
||||
github.com/docker/cli v24.0.9+incompatible
|
||||
github.com/docker/docker v24.0.9+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/drone/envsubst v1.0.3
|
||||
github.com/expr-lang/expr v1.16.2
|
||||
github.com/expr-lang/expr v1.16.3
|
||||
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-ap/httpsig v0.0.0-20221203064646-3647b4d88fdf
|
||||
github.com/go-sql-driver/mysql v1.8.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/google/go-github/v60 v60.0.0
|
||||
github.com/google/go-github/v61 v61.0.0
|
||||
github.com/google/tink/go v1.7.0
|
||||
github.com/gorilla/securecookie v1.1.2
|
||||
github.com/hashicorp/go-hclog v1.2.0
|
||||
github.com/hashicorp/go-plugin v1.4.3
|
||||
github.com/jellydator/ttlcache/v3 v3.2.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kinbiko/jsonassert v1.1.1
|
||||
|
@ -81,7 +83,10 @@ require (
|
|||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.10.2 // indirect
|
||||
github.com/catppuccin/go v0.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.18.0 // indirect
|
||||
github.com/charmbracelet/bubbletea v0.25.0 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.9.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
|
@ -116,9 +121,9 @@ require (
|
|||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-hclog v1.2.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
|
@ -134,13 +139,15 @@ require (
|
|||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/miekg/dns v1.1.57 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oklog/run v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
|
||||
|
|
74
go.sum
74
go.sum
|
@ -1,3 +1,4 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8=
|
||||
code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM=
|
||||
codeberg.org/6543/go-yaml2json v1.0.0 h1:heGqo9VEi7gY2yNqjj7X4ADs5nzlFIbGsJtgYDLrnig=
|
||||
|
@ -40,14 +41,21 @@ github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZF
|
|||
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
|
||||
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
|
||||
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
|
||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/charmbracelet/huh v0.3.0 h1:CxPplWkgW2yUTDDG0Z4S5HH8SJOosWHd4LxCvi0XsKE=
|
||||
github.com/charmbracelet/huh v0.3.0/go.mod h1:fujUdKX8tC45CCSaRQdw789O6uaCRwx8l2NDyKfC4jA=
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20240327025511-ec643317aa10 h1:/HZJSyFVH5rB1MlCDfkhQhRbLPD2Er29ngWXiUQ8bik=
|
||||
github.com/charmbracelet/huh/spinner v0.0.0-20240327025511-ec643317aa10/go.mod h1:nrBG0YEHaxdbqHXW1xvG1hPqkuac9Eg7RTMvogiXuz0=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
|
@ -60,6 +68,7 @@ github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI
|
|||
github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY=
|
||||
github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic=
|
||||
github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
|
@ -102,8 +111,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/expr-lang/expr v1.16.2 h1:JvMnzUs3LeVHBvGFcXYmXo+Q6DPDmzrlcSBO6Wy3w4s=
|
||||
github.com/expr-lang/expr v1.16.2/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/expr-lang/expr v1.16.3 h1:NLldf786GffptcXNxxJx5dQ+FzeWDKChBDqOOwyK8to=
|
||||
github.com/expr-lang/expr v1.16.3/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
|
@ -146,8 +157,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
|
|||
github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
|
||||
github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
|
||||
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
|
@ -167,7 +178,12 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
|
|||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
|
@ -185,8 +201,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCyONLfn8=
|
||||
github.com/google/go-github/v60 v60.0.0/go.mod h1:ByhX2dP9XT9o/ll2yXAu2VD8l5eNVg8hD4Cr0S/LmQk=
|
||||
github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go=
|
||||
github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
@ -205,12 +221,17 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw
|
|||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
|
||||
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM=
|
||||
github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
|
@ -257,6 +278,8 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
|
|||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE=
|
||||
github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4=
|
||||
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
|
||||
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
|
@ -331,6 +354,9 @@ github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
|||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
|
||||
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/moby v24.0.9+incompatible h1:Z/hFbZJqC5Fmuf6jesMLdHU71CMAgdiSJ1ZYey+bFmg=
|
||||
|
@ -345,8 +371,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
|||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
|
@ -357,6 +383,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/neticdk/go-bitbucket v1.0.0 h1:FPvHEgPHoDwD2VHbpyu2R2gnoWQ867RxZd2FivS4wSw=
|
||||
github.com/neticdk/go-bitbucket v1.0.0/go.mod h1:IrHeWO1CrNi0DlOvfhAA9bGRSeNSUB6/SAfzmwbA5aU=
|
||||
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
|
||||
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
|
@ -382,6 +410,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
|
@ -513,6 +542,10 @@ 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/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=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
|
@ -522,7 +555,11 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
|
|||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -538,9 +575,11 @@ 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/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=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -548,6 +587,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -603,8 +643,11 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
@ -625,10 +668,19 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
||||
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
|
@ -658,6 +710,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
|
||||
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
|
||||
|
|
|
@ -213,7 +213,7 @@ func (e *docker) StartStep(ctx context.Context, step *backend.Step, taskUUID str
|
|||
}
|
||||
|
||||
// add default volumes to the host configuration
|
||||
hostConfig.Binds = utils.DedupStrings(append(hostConfig.Binds, e.volumes...))
|
||||
hostConfig.Binds = utils.DeduplicateStrings(append(hostConfig.Binds, e.volumes...))
|
||||
|
||||
_, err := e.client.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName)
|
||||
if client.IsErrNotFound(err) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
// BackendOptions defines all the advanced options for the kubernetes backend
|
||||
type BackendOptions struct {
|
||||
Resources Resources `mapstructure:"resources"`
|
||||
RuntimeClassName *string `mapstructure:"runtimeClassName"`
|
||||
ServiceAccountName string `mapstructure:"serviceAccountName"`
|
||||
NodeSelector map[string]string `mapstructure:"nodeSelector"`
|
||||
Tolerations []Toleration `mapstructure:"tolerations"`
|
||||
|
|
|
@ -316,6 +316,9 @@ func (e *kube) TailStep(ctx context.Context, step *types.Step, taskUUID string)
|
|||
}
|
||||
|
||||
if pod.Name == podName {
|
||||
if isImagePullBackOffState(pod) {
|
||||
up <- true
|
||||
}
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodRunning, v1.PodSucceeded, v1.PodFailed:
|
||||
up <- true
|
||||
|
|
|
@ -117,6 +117,7 @@ func podSpec(step *types.Step, config *config, options BackendOptions) (v1.PodSp
|
|||
var err error
|
||||
spec := v1.PodSpec{
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
RuntimeClassName: options.RuntimeClassName,
|
||||
ServiceAccountName: options.ServiceAccountName,
|
||||
ImagePullSecrets: imagePullSecretsReferences(config.ImagePullSecretNames),
|
||||
HostAliases: hostAliases(step.ExtraHosts),
|
||||
|
@ -135,9 +136,11 @@ func podSpec(step *types.Step, config *config, options BackendOptions) (v1.PodSp
|
|||
func podContainer(step *types.Step, podName, goos string, options BackendOptions) (v1.Container, error) {
|
||||
var err error
|
||||
container := v1.Container{
|
||||
Name: podName,
|
||||
Image: step.Image,
|
||||
WorkingDir: step.WorkingDir,
|
||||
Name: podName,
|
||||
Image: step.Image,
|
||||
WorkingDir: step.WorkingDir,
|
||||
Ports: containerPorts(step.Ports),
|
||||
SecurityContext: containerSecurityContext(options.SecurityContext, step.Privileged),
|
||||
}
|
||||
|
||||
if step.Pull {
|
||||
|
@ -155,8 +158,6 @@ func podContainer(step *types.Step, podName, goos string, options BackendOptions
|
|||
}
|
||||
|
||||
container.Env = mapToEnvVars(step.Environment)
|
||||
container.Ports = containerPorts(step.Ports)
|
||||
container.SecurityContext = containerSecurityContext(options.SecurityContext, step.Privileged)
|
||||
|
||||
container.Resources, err = resourceRequirements(options.Resources)
|
||||
if err != nil {
|
||||
|
|
|
@ -245,6 +245,7 @@ func TestFullPod(t *testing.T) {
|
|||
"nodeSelector": {
|
||||
"storage": "ssd"
|
||||
},
|
||||
"runtimeClassName": "runc",
|
||||
"serviceAccountName": "wp-svc-acc",
|
||||
"securityContext": {
|
||||
"runAsUser": 101,
|
||||
|
@ -289,6 +290,7 @@ func TestFullPod(t *testing.T) {
|
|||
"status": {}
|
||||
}`
|
||||
|
||||
runtimeClass := "runc"
|
||||
hostAliases := []types.HostAlias{
|
||||
{Name: "cloudflare", IP: "1.1.1.1"},
|
||||
{Name: "cf.v6", IP: "2606:4700:4700::64"},
|
||||
|
@ -333,6 +335,7 @@ func TestFullPod(t *testing.T) {
|
|||
SecurityContext: SecurityContextConfig{RunAsNonRoot: false},
|
||||
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{
|
||||
NodeSelector: map[string]string{"storage": "ssd"},
|
||||
RuntimeClassName: &runtimeClass,
|
||||
ServiceAccountName: "wp-svc-acc",
|
||||
Tolerations: []Toleration{{Key: "net-port", Value: "100Mbit", Effect: TaintEffectNoSchedule}},
|
||||
Resources: Resources{
|
||||
|
|
|
@ -2,28 +2,12 @@ package errors
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/multierr"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types"
|
||||
)
|
||||
|
||||
type PipelineErrorType string
|
||||
|
||||
const (
|
||||
PipelineErrorTypeLinter PipelineErrorType = "linter" // some error with the config syntax
|
||||
PipelineErrorTypeDeprecation PipelineErrorType = "deprecation" // using some deprecated feature
|
||||
PipelineErrorTypeCompiler PipelineErrorType = "compiler" // some error with the config semantics
|
||||
PipelineErrorTypeGeneric PipelineErrorType = "generic" // some generic error
|
||||
PipelineErrorTypeBadHabit PipelineErrorType = "bad_habit" // some bad-habit error
|
||||
)
|
||||
|
||||
type PipelineError struct {
|
||||
Type PipelineErrorType `json:"type"`
|
||||
Message string `json:"message"`
|
||||
IsWarning bool `json:"is_warning"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
type LinterErrorData struct {
|
||||
File string `json:"file"`
|
||||
Field string `json:"field"`
|
||||
|
@ -35,12 +19,8 @@ type DeprecationErrorData struct {
|
|||
Docs string `json:"docs"`
|
||||
}
|
||||
|
||||
func (e *PipelineError) Error() string {
|
||||
return fmt.Sprintf("[%s] %s", e.Type, e.Message)
|
||||
}
|
||||
|
||||
func (e *PipelineError) GetLinterData() *LinterErrorData {
|
||||
if e.Type != PipelineErrorTypeLinter {
|
||||
func GetLinterData(e *types.PipelineError) *LinterErrorData {
|
||||
if e.Type != types.PipelineErrorTypeLinter {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -51,16 +31,16 @@ func (e *PipelineError) GetLinterData() *LinterErrorData {
|
|||
return nil
|
||||
}
|
||||
|
||||
func GetPipelineErrors(err error) []*PipelineError {
|
||||
var pipelineErrors []*PipelineError
|
||||
func GetPipelineErrors(err error) []*types.PipelineError {
|
||||
var pipelineErrors []*types.PipelineError
|
||||
for _, _err := range multierr.Errors(err) {
|
||||
var err *PipelineError
|
||||
var err *types.PipelineError
|
||||
if errors.As(_err, &err) {
|
||||
pipelineErrors = append(pipelineErrors, err)
|
||||
} else {
|
||||
pipelineErrors = append(pipelineErrors, &PipelineError{
|
||||
pipelineErrors = append(pipelineErrors, &types.PipelineError{
|
||||
Message: _err.Error(),
|
||||
Type: PipelineErrorTypeGeneric,
|
||||
Type: types.PipelineErrorTypeGeneric,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"go.uber.org/multierr"
|
||||
|
||||
pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types"
|
||||
)
|
||||
|
||||
func TestGetPipelineErrors(t *testing.T) {
|
||||
|
@ -16,7 +17,7 @@ func TestGetPipelineErrors(t *testing.T) {
|
|||
tests := []struct {
|
||||
title string
|
||||
err error
|
||||
expected []*pipeline_errors.PipelineError
|
||||
expected []*types.PipelineError
|
||||
}{
|
||||
{
|
||||
title: "nil error",
|
||||
|
@ -25,10 +26,10 @@ func TestGetPipelineErrors(t *testing.T) {
|
|||
},
|
||||
{
|
||||
title: "warning",
|
||||
err: &pipeline_errors.PipelineError{
|
||||
err: &types.PipelineError{
|
||||
IsWarning: true,
|
||||
},
|
||||
expected: []*pipeline_errors.PipelineError{
|
||||
expected: []*types.PipelineError{
|
||||
{
|
||||
IsWarning: true,
|
||||
},
|
||||
|
@ -36,10 +37,10 @@ func TestGetPipelineErrors(t *testing.T) {
|
|||
},
|
||||
{
|
||||
title: "pipeline error",
|
||||
err: &pipeline_errors.PipelineError{
|
||||
err: &types.PipelineError{
|
||||
IsWarning: false,
|
||||
},
|
||||
expected: []*pipeline_errors.PipelineError{
|
||||
expected: []*types.PipelineError{
|
||||
{
|
||||
IsWarning: false,
|
||||
},
|
||||
|
@ -48,14 +49,14 @@ func TestGetPipelineErrors(t *testing.T) {
|
|||
{
|
||||
title: "multiple warnings",
|
||||
err: multierr.Combine(
|
||||
&pipeline_errors.PipelineError{
|
||||
&types.PipelineError{
|
||||
IsWarning: true,
|
||||
},
|
||||
&pipeline_errors.PipelineError{
|
||||
&types.PipelineError{
|
||||
IsWarning: true,
|
||||
},
|
||||
),
|
||||
expected: []*pipeline_errors.PipelineError{
|
||||
expected: []*types.PipelineError{
|
||||
{
|
||||
IsWarning: true,
|
||||
},
|
||||
|
@ -67,15 +68,15 @@ func TestGetPipelineErrors(t *testing.T) {
|
|||
{
|
||||
title: "multiple errors and warnings",
|
||||
err: multierr.Combine(
|
||||
&pipeline_errors.PipelineError{
|
||||
&types.PipelineError{
|
||||
IsWarning: true,
|
||||
},
|
||||
&pipeline_errors.PipelineError{
|
||||
&types.PipelineError{
|
||||
IsWarning: false,
|
||||
},
|
||||
errors.New("some error"),
|
||||
),
|
||||
expected: []*pipeline_errors.PipelineError{
|
||||
expected: []*types.PipelineError{
|
||||
{
|
||||
IsWarning: true,
|
||||
},
|
||||
|
@ -83,7 +84,7 @@ func TestGetPipelineErrors(t *testing.T) {
|
|||
IsWarning: false,
|
||||
},
|
||||
{
|
||||
Type: pipeline_errors.PipelineErrorTypeGeneric,
|
||||
Type: types.PipelineErrorTypeGeneric,
|
||||
IsWarning: false,
|
||||
Message: "some error",
|
||||
},
|
||||
|
@ -111,14 +112,14 @@ func TestHasBlockingErrors(t *testing.T) {
|
|||
},
|
||||
{
|
||||
title: "warning",
|
||||
err: &pipeline_errors.PipelineError{
|
||||
err: &types.PipelineError{
|
||||
IsWarning: true,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
title: "pipeline error",
|
||||
err: &pipeline_errors.PipelineError{
|
||||
err: &types.PipelineError{
|
||||
IsWarning: false,
|
||||
},
|
||||
expected: true,
|
||||
|
@ -126,10 +127,10 @@ func TestHasBlockingErrors(t *testing.T) {
|
|||
{
|
||||
title: "multiple warnings",
|
||||
err: multierr.Combine(
|
||||
&pipeline_errors.PipelineError{
|
||||
&types.PipelineError{
|
||||
IsWarning: true,
|
||||
},
|
||||
&pipeline_errors.PipelineError{
|
||||
&types.PipelineError{
|
||||
IsWarning: true,
|
||||
},
|
||||
),
|
||||
|
@ -138,10 +139,10 @@ func TestHasBlockingErrors(t *testing.T) {
|
|||
{
|
||||
title: "multiple errors and warnings",
|
||||
err: multierr.Combine(
|
||||
&pipeline_errors.PipelineError{
|
||||
&types.PipelineError{
|
||||
IsWarning: true,
|
||||
},
|
||||
&pipeline_errors.PipelineError{
|
||||
&types.PipelineError{
|
||||
IsWarning: false,
|
||||
},
|
||||
errors.New("some error"),
|
||||
|
|
24
pipeline/errors/types/errors.go
Normal file
24
pipeline/errors/types/errors.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
type PipelineErrorType string
|
||||
|
||||
const (
|
||||
PipelineErrorTypeLinter PipelineErrorType = "linter" // some error with the config syntax
|
||||
PipelineErrorTypeDeprecation PipelineErrorType = "deprecation" // using some deprecated feature
|
||||
PipelineErrorTypeCompiler PipelineErrorType = "compiler" // some error with the config semantics
|
||||
PipelineErrorTypeGeneric PipelineErrorType = "generic" // some generic error
|
||||
PipelineErrorTypeBadHabit PipelineErrorType = "bad_habit" // some bad-habit error
|
||||
)
|
||||
|
||||
type PipelineError struct {
|
||||
Type PipelineErrorType `json:"type"`
|
||||
Message string `json:"message"`
|
||||
IsWarning bool `json:"is_warning"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
func (e *PipelineError) Error() string {
|
||||
return fmt.Sprintf("[%s] %s", e.Type, e.Message)
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
// Copyright 2023 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
yaml_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types"
|
||||
)
|
||||
|
||||
// Cacher defines a compiler transform that can be used
|
||||
// to implement default caching for a repository.
|
||||
type Cacher interface {
|
||||
Restore(repo, branch string, mounts []string) *yaml_types.Container
|
||||
Rebuild(repo, branch string, mounts []string) *yaml_types.Container
|
||||
}
|
||||
|
||||
type volumeCacher struct {
|
||||
base string
|
||||
}
|
||||
|
||||
func (c *volumeCacher) Restore(repo, branch string, mounts []string) *yaml_types.Container {
|
||||
return &yaml_types.Container{
|
||||
Name: "rebuild_cache",
|
||||
Image: "plugins/volume-cache:1.0.0",
|
||||
Settings: map[string]any{
|
||||
"mount": mounts,
|
||||
"path": "/cache",
|
||||
"restore": true,
|
||||
"file": strings.ReplaceAll(branch, "/", "_") + ".tar",
|
||||
"fallback_to": "main.tar",
|
||||
},
|
||||
Volumes: yaml_types.Volumes{
|
||||
Volumes: []*yaml_types.Volume{
|
||||
{
|
||||
Source: path.Join(c.base, repo),
|
||||
Destination: "/cache",
|
||||
// TODO add access mode
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *volumeCacher) Rebuild(repo, branch string, mounts []string) *yaml_types.Container {
|
||||
return &yaml_types.Container{
|
||||
Name: "rebuild_cache",
|
||||
Image: "plugins/volume-cache:1.0.0",
|
||||
Settings: map[string]any{
|
||||
"mount": mounts,
|
||||
"path": "/cache",
|
||||
"rebuild": true,
|
||||
"flush": true,
|
||||
"file": strings.ReplaceAll(branch, "/", "_") + ".tar",
|
||||
},
|
||||
Volumes: yaml_types.Volumes{
|
||||
Volumes: []*yaml_types.Volume{
|
||||
{
|
||||
Source: path.Join(c.base, repo),
|
||||
Destination: "/cache",
|
||||
// TODO add access mode
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type s3Cacher struct {
|
||||
bucket string
|
||||
access string
|
||||
secret string
|
||||
region string
|
||||
}
|
||||
|
||||
func (c *s3Cacher) Restore(_, _ string, mounts []string) *yaml_types.Container {
|
||||
return &yaml_types.Container{
|
||||
Name: "rebuild_cache",
|
||||
Image: "plugins/s3-cache:latest",
|
||||
Settings: map[string]any{
|
||||
"mount": mounts,
|
||||
"access_key": c.access,
|
||||
"secret_key": c.secret,
|
||||
"bucket": c.bucket,
|
||||
"region": c.region,
|
||||
"rebuild": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *s3Cacher) Rebuild(_, _ string, mounts []string) *yaml_types.Container {
|
||||
return &yaml_types.Container{
|
||||
Name: "rebuild_cache",
|
||||
Image: "plugins/s3-cache:latest",
|
||||
Settings: map[string]any{
|
||||
"mount": mounts,
|
||||
"access_key": c.access,
|
||||
"secret_key": c.secret,
|
||||
"bucket": c.bucket,
|
||||
"region": c.region,
|
||||
"rebuild": true,
|
||||
"flush": true,
|
||||
},
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ package compiler
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
|
||||
|
@ -34,7 +33,6 @@ type Registry struct {
|
|||
Hostname string
|
||||
Username string
|
||||
Password string
|
||||
Token string
|
||||
}
|
||||
|
||||
type Secret struct {
|
||||
|
@ -68,7 +66,7 @@ func (s *Secret) Match(event string) bool {
|
|||
if len(s.Events) == 0 {
|
||||
return true
|
||||
}
|
||||
// tread all pull events the same way
|
||||
// treat all pull events the same way
|
||||
if event == "pull_request_closed" {
|
||||
event = "pull_request"
|
||||
}
|
||||
|
@ -82,8 +80,6 @@ func (s *Secret) Match(event string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
type secretMap map[string]Secret
|
||||
|
||||
type ResourceLimit struct {
|
||||
MemSwapLimit int64
|
||||
MemLimit int64
|
||||
|
@ -106,8 +102,7 @@ type Compiler struct {
|
|||
path string
|
||||
metadata metadata.Metadata
|
||||
registries []Registry
|
||||
secrets secretMap
|
||||
cacher Cacher
|
||||
secrets map[string]Secret
|
||||
reslimit ResourceLimit
|
||||
defaultCloneImage string
|
||||
trustedPipeline bool
|
||||
|
@ -224,11 +219,6 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
|
|||
}
|
||||
}
|
||||
|
||||
err := c.setupCache(conf, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// add services steps
|
||||
if len(conf.Services.ContainerList) != 0 {
|
||||
stage := new(backend_types.Stage)
|
||||
|
@ -297,48 +287,5 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
|
|||
|
||||
config.Stages = append(config.Stages, stepStages...)
|
||||
|
||||
err = c.setupCacheRebuild(conf, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) setupCache(conf *yaml_types.Workflow, ir *backend_types.Config) error {
|
||||
if c.local || len(conf.Cache) == 0 || c.cacher == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
container := c.cacher.Restore(path.Join(c.metadata.Repo.Owner, c.metadata.Repo.Name), c.metadata.Curr.Commit.Branch, conf.Cache)
|
||||
step, err := c.createProcess(container, backend_types.StepTypeCache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stage := new(backend_types.Stage)
|
||||
stage.Steps = append(stage.Steps, step)
|
||||
|
||||
ir.Stages = append(ir.Stages, stage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) setupCacheRebuild(conf *yaml_types.Workflow, ir *backend_types.Config) error {
|
||||
if c.local || len(conf.Cache) == 0 || c.metadata.Curr.Event != metadata.EventPush || c.cacher == nil {
|
||||
return nil
|
||||
}
|
||||
container := c.cacher.Rebuild(path.Join(c.metadata.Repo.Owner, c.metadata.Repo.Name), c.metadata.Curr.Commit.Branch, conf.Cache)
|
||||
|
||||
step, err := c.createProcess(container, backend_types.StepTypeCache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stage := new(backend_types.Stage)
|
||||
stage.Steps = append(stage.Steps, step)
|
||||
|
||||
ir.Stages = append(ir.Stages, stage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -149,34 +149,6 @@ func WithEnviron(env map[string]string) Option {
|
|||
}
|
||||
}
|
||||
|
||||
// WithCacher configures the compiler with default cache settings.
|
||||
func WithCacher(cacher Cacher) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.cacher = cacher
|
||||
}
|
||||
}
|
||||
|
||||
// WithVolumeCacher configures the compiler with default local volume
|
||||
// caching enabled.
|
||||
func WithVolumeCacher(base string) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.cacher = &volumeCacher{base: base}
|
||||
}
|
||||
}
|
||||
|
||||
// WithS3Cacher configures the compiler with default amazon s3
|
||||
// caching enabled.
|
||||
func WithS3Cacher(access, secret, region, bucket string) Option {
|
||||
return func(compiler *Compiler) {
|
||||
compiler.cacher = &s3Cacher{
|
||||
access: access,
|
||||
secret: secret,
|
||||
bucket: bucket,
|
||||
region: region,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithNetworks configures the compiler with additional networks
|
||||
// to be connected to pipeline containers
|
||||
func WithNetworks(networks ...string) Option {
|
||||
|
|
|
@ -166,30 +166,9 @@ func TestWithEnviron(t *testing.T) {
|
|||
assert.Equal(t, "true", compiler.env["SHOW"])
|
||||
}
|
||||
|
||||
func TestWithVolumeCacher(t *testing.T) {
|
||||
compiler := New(
|
||||
WithVolumeCacher("/cache"),
|
||||
)
|
||||
cacher, ok := compiler.cacher.(*volumeCacher)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "/cache", cacher.base)
|
||||
}
|
||||
|
||||
func TestWithDefaultCloneImage(t *testing.T) {
|
||||
compiler := New(
|
||||
WithDefaultCloneImage("not-an-image"),
|
||||
)
|
||||
assert.Equal(t, "not-an-image", compiler.defaultCloneImage)
|
||||
}
|
||||
|
||||
func TestWithS3Cacher(t *testing.T) {
|
||||
compiler := New(
|
||||
WithS3Cacher("some-access-key", "some-secret-key", "some-region", "some-bucket"),
|
||||
)
|
||||
cacher, ok := compiler.cacher.(*s3Cacher)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "some-bucket", cacher.bucket)
|
||||
assert.Equal(t, "some-access-key", cacher.access)
|
||||
assert.Equal(t, "some-region", cacher.region)
|
||||
assert.Equal(t, "some-secret-key", cacher.secret)
|
||||
}
|
||||
|
|
|
@ -16,11 +16,12 @@ package linter
|
|||
|
||||
import (
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
|
||||
errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types"
|
||||
)
|
||||
|
||||
func newLinterError(message, file, field string, isWarning bool) *errors.PipelineError {
|
||||
return &errors.PipelineError{
|
||||
Type: errors.PipelineErrorTypeLinter,
|
||||
func newLinterError(message, file, field string, isWarning bool) *errorTypes.PipelineError {
|
||||
return &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeLinter,
|
||||
Message: message,
|
||||
Data: &errors.LinterErrorData{File: file, Field: field},
|
||||
IsWarning: isWarning,
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"go.uber.org/multierr"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
|
||||
errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter/schema"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types"
|
||||
)
|
||||
|
@ -210,8 +211,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
}
|
||||
|
||||
if parsed.PipelineDoNotUseIt.ContainerList != nil {
|
||||
err = multierr.Append(err, &errors.PipelineError{
|
||||
Type: errors.PipelineErrorTypeDeprecation,
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "Please use 'steps:' instead of deprecated 'pipeline:' list",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
|
@ -223,8 +224,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
}
|
||||
|
||||
if parsed.PlatformDoNotUseIt != "" {
|
||||
err = multierr.Append(err, &errors.PipelineError{
|
||||
Type: errors.PipelineErrorTypeDeprecation,
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "Please use labels instead of deprecated 'platform' filters",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
|
@ -236,8 +237,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
}
|
||||
|
||||
if parsed.BranchesDoNotUseIt != nil {
|
||||
err = multierr.Append(err, &errors.PipelineError{
|
||||
Type: errors.PipelineErrorTypeDeprecation,
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "Please use global when instead of deprecated 'branches' filter",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
|
@ -250,8 +251,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
|
||||
for _, step := range parsed.Steps.ContainerList {
|
||||
if step.Group != "" {
|
||||
err = multierr.Append(err, &errors.PipelineError{
|
||||
Type: errors.PipelineErrorTypeDeprecation,
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "Please use depends_on instead of deprecated 'group' setting",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
|
@ -265,8 +266,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
|
||||
for i, c := range parsed.When.Constraints {
|
||||
if len(c.Event.Exclude) != 0 {
|
||||
err = multierr.Append(err, &errors.PipelineError{
|
||||
Type: errors.PipelineErrorTypeDeprecation,
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "Please only use allow lists for events",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
|
@ -281,8 +282,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
for _, step := range parsed.Steps.ContainerList {
|
||||
for i, c := range step.When.Constraints {
|
||||
if len(c.Event.Exclude) != 0 {
|
||||
err = multierr.Append(err, &errors.PipelineError{
|
||||
Type: errors.PipelineErrorTypeDeprecation,
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "Please only use allow lists for events",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
|
@ -298,8 +299,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
|
|||
for _, step := range parsed.Steps.ContainerList {
|
||||
for i, c := range step.Secrets.Secrets {
|
||||
if c.Source != c.Target {
|
||||
err = multierr.Append(err, &errors.PipelineError{
|
||||
Type: errors.PipelineErrorTypeDeprecation,
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeDeprecation,
|
||||
Message: "Secrets alternative names are deprecated, use environment with from_secret",
|
||||
Data: errors.DeprecationErrorData{
|
||||
File: config.File,
|
||||
|
@ -348,8 +349,8 @@ func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) {
|
|||
}
|
||||
}
|
||||
if field != "" {
|
||||
err = multierr.Append(err, &errors.PipelineError{
|
||||
Type: errors.PipelineErrorTypeBadHabit,
|
||||
err = multierr.Append(err, &errorTypes.PipelineError{
|
||||
Type: errorTypes.PipelineErrorTypeBadHabit,
|
||||
Message: "Please set an event filter on all when branches",
|
||||
Data: errors.LinterErrorData{
|
||||
File: config.File,
|
||||
|
|
|
@ -696,6 +696,10 @@
|
|||
},
|
||||
"securityContext": {
|
||||
"$ref": "#/definitions/step_backend_kubernetes_security_context"
|
||||
},
|
||||
"runtimeClassName": {
|
||||
"description": "Read more: https://woodpecker-ci.org/docs/administration/backends/kubernetes#runtimeclassname",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
|
||||
"codeberg.org/6543/xyaml"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
|
||||
errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -116,7 +116,7 @@ func parse(raw []byte) (Matrix, error) {
|
|||
Matrix map[string][]string
|
||||
}{}
|
||||
if err := xyaml.Unmarshal(raw, &data); err != nil {
|
||||
return nil, &errors.PipelineError{Message: err.Error(), Type: errors.PipelineErrorTypeCompiler}
|
||||
return nil, &errorTypes.PipelineError{Message: err.Error(), Type: errorTypes.PipelineErrorTypeCompiler}
|
||||
}
|
||||
return data.Matrix, nil
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ func parseList(raw []byte) ([]Axis, error) {
|
|||
}{}
|
||||
|
||||
if err := xyaml.Unmarshal(raw, &data); err != nil {
|
||||
return nil, &errors.PipelineError{Message: err.Error(), Type: errors.PipelineErrorTypeCompiler}
|
||||
return nil, &errorTypes.PipelineError{Message: err.Error(), Type: errorTypes.PipelineErrorTypeCompiler}
|
||||
}
|
||||
return data.Matrix.Include, nil
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ package types
|
|||
|
||||
import (
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/constraint"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types/base"
|
||||
)
|
||||
|
||||
type (
|
||||
|
@ -33,9 +32,8 @@ type (
|
|||
SkipClone bool `yaml:"skip_clone"`
|
||||
|
||||
// Undocumented
|
||||
Cache base.StringOrSlice `yaml:"cache,omitempty"`
|
||||
Networks WorkflowNetworks `yaml:"networks,omitempty"`
|
||||
Volumes WorkflowVolumes `yaml:"volumes,omitempty"`
|
||||
Networks WorkflowNetworks `yaml:"networks,omitempty"`
|
||||
Volumes WorkflowVolumes `yaml:"volumes,omitempty"`
|
||||
|
||||
// Deprecated
|
||||
PlatformDoNotUseIt string `yaml:"platform,omitempty"` // TODO: remove in next major version
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
|
|
@ -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,12 @@ 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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -116,6 +123,13 @@ func GetOrgPermissions(c *gin.Context) {
|
|||
// @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,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"
|
||||
|
@ -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)
|
||||
|
||||
|
@ -224,6 +231,66 @@ func GetStepLogs(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, logs)
|
||||
}
|
||||
|
||||
// DeleteStepLogs
|
||||
//
|
||||
// @Summary Deletes step log
|
||||
// @Router /repos/{repo_id}/logs/{number}/{stepId} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Pipeline logs
|
||||
// @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"
|
||||
// @Param stepId path int true "the step id"
|
||||
func DeleteStepLogs(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
repo := session.Repo(c)
|
||||
|
||||
pipelineNumber, err := strconv.ParseInt(c.Params.ByName("number"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
_pipeline, err := _store.GetPipelineNumber(repo, pipelineNumber)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
stepID, err := strconv.ParseInt(c.Params.ByName("stepId"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
_step, err := _store.StepLoad(stepID)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if _step.PipelineID != _pipeline.ID {
|
||||
// make sure we cannot read arbitrary logs by id
|
||||
_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("step with id %d is not part of repo %s", stepID, repo.FullName))
|
||||
return
|
||||
}
|
||||
|
||||
switch _step.State {
|
||||
case model.StatusRunning, model.StatusPending:
|
||||
c.String(http.StatusUnprocessableEntity, "Cannot delete logs for a pending or running step")
|
||||
return
|
||||
}
|
||||
|
||||
err = _store.LogDelete(_step)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetPipelineConfig
|
||||
//
|
||||
// @Summary Pipeline configuration
|
||||
|
@ -272,6 +339,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)
|
||||
|
@ -280,7 +354,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)
|
||||
|
@ -407,23 +481,22 @@ func PostPipeline(c *gin.Context) {
|
|||
refreshUserToken(c, user)
|
||||
|
||||
// make Deploy overridable
|
||||
pl.Deploy = c.DefaultQuery("deploy_to", pl.Deploy)
|
||||
|
||||
// make Event overridable to deploy
|
||||
// TODO refactor to use own proper API for deploy
|
||||
if event, ok := c.GetQuery("event"); ok {
|
||||
// only allow deploy from push, tag and release
|
||||
if pl.Event != model.EventPush && pl.Event != model.EventTag && pl.Event != model.EventRelease {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("can only deploy push, tag and release pipelines"))
|
||||
return
|
||||
}
|
||||
|
||||
pl.Event = model.WebhookEvent(event)
|
||||
|
||||
if pl.Event != model.EventDeploy {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, model.ErrInvalidWebhookEvent)
|
||||
return
|
||||
}
|
||||
|
||||
if !repo.AllowDeploy {
|
||||
_ = c.AbortWithError(http.StatusForbidden, fmt.Errorf("repo does not allow deployments"))
|
||||
return
|
||||
}
|
||||
|
||||
pl.Deploy = c.DefaultQuery("deploy_to", pl.Deploy)
|
||||
}
|
||||
|
||||
// Read query string parameters into pipelineParams, exclude reserved params
|
||||
|
|
|
@ -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
|
||||
|
@ -86,6 +91,7 @@ func PostRepo(c *gin.Context) {
|
|||
} else {
|
||||
repo = from
|
||||
repo.AllowPull = true
|
||||
repo.AllowDeploy = false
|
||||
repo.NetrcOnlyTrusted = true
|
||||
repo.CancelPreviousPipelineEvents = server.Config.Pipeline.DefaultCancelPreviousPipelineEvents
|
||||
}
|
||||
|
@ -137,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)
|
||||
|
@ -145,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."
|
||||
|
@ -156,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)
|
||||
|
@ -167,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 {
|
||||
|
@ -223,6 +231,9 @@ func PatchRepo(c *gin.Context) {
|
|||
if in.AllowPull != nil {
|
||||
repo.AllowPull = *in.AllowPull
|
||||
}
|
||||
if in.AllowDeploy != nil {
|
||||
repo.AllowDeploy = *in.AllowDeploy
|
||||
}
|
||||
if in.IsGated != nil {
|
||||
repo.IsGated = *in.IsGated
|
||||
}
|
||||
|
@ -338,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
|
||||
|
@ -363,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
|
||||
|
@ -386,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
|
||||
|
@ -405,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
|
||||
}
|
||||
|
@ -441,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 {
|
||||
|
@ -459,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
|
||||
|
@ -504,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
|
||||
}
|
||||
|
@ -567,8 +598,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 {
|
||||
|
@ -599,7 +635,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 {
|
||||
|
@ -632,10 +668,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
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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(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,9 +78,8 @@ func CalcNewNext(schedule string, now time.Time) (time.Time, error) {
|
|||
return c.Next(now), nil
|
||||
}
|
||||
|
||||
func runCron(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)
|
||||
ctx := context.Background()
|
||||
|
||||
newNext, err := CalcNewNext(cron.Schedule, now)
|
||||
if err != nil {
|
||||
|
@ -96,7 +96,7 @@ func runCron(store store.Store, forge forge.Forge, cron *model.Cron, now time.Ti
|
|||
return nil
|
||||
}
|
||||
|
||||
repo, newPipeline, err := CreatePipeline(ctx, store, forge, cron)
|
||||
repo, newPipeline, err := CreatePipeline(ctx, store, cron)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -105,12 +105,17 @@ func runCron(store store.Store, forge forge.Forge, cron *model.Cron, now time.Ti
|
|||
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
|
||||
|
@ -124,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)
|
||||
|
|
154
server/forge/addon/args.go
Normal file
154
server/forge/addon/args.go
Normal file
|
@ -0,0 +1,154 @@
|
|||
// 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 addon
|
||||
|
||||
import (
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
||||
type argumentsAuth struct {
|
||||
Token string `json:"token"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
type argumentsRepo struct {
|
||||
U *modelUser `json:"u"`
|
||||
RemoteID model.ForgeRemoteID `json:"remote_id"`
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type argumentsFileDir struct {
|
||||
U *modelUser `json:"u"`
|
||||
R *modelRepo `json:"r"`
|
||||
B *model.Pipeline `json:"b"`
|
||||
F string `json:"f"`
|
||||
}
|
||||
|
||||
type argumentsStatus struct {
|
||||
U *modelUser `json:"u"`
|
||||
R *modelRepo `json:"r"`
|
||||
B *model.Pipeline `json:"b"`
|
||||
P *model.Workflow `json:"p"`
|
||||
}
|
||||
|
||||
type argumentsNetrc struct {
|
||||
U *modelUser `json:"u"`
|
||||
R *modelRepo `json:"r"`
|
||||
}
|
||||
|
||||
type argumentsActivateDeactivate struct {
|
||||
U *modelUser `json:"u"`
|
||||
R *modelRepo `json:"r"`
|
||||
Link string `json:"link"`
|
||||
}
|
||||
|
||||
type argumentsBranchesPullRequests struct {
|
||||
U *modelUser `json:"u"`
|
||||
R *modelRepo `json:"r"`
|
||||
P *model.ListOptions `json:"p"`
|
||||
}
|
||||
|
||||
type argumentsBranchHead struct {
|
||||
U *modelUser `json:"u"`
|
||||
R *modelRepo `json:"r"`
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
|
||||
type argumentsOrgMembershipOrg struct {
|
||||
U *modelUser `json:"u"`
|
||||
Org string `json:"org"`
|
||||
}
|
||||
|
||||
type responseHook struct {
|
||||
Repo *modelRepo `json:"repo"`
|
||||
Pipeline *model.Pipeline `json:"pipeline"`
|
||||
}
|
||||
|
||||
type responseLogin struct {
|
||||
User *modelUser `json:"user"`
|
||||
RedirectURL string `json:"redirect_url"`
|
||||
}
|
||||
|
||||
type httpRequest struct {
|
||||
Method string `json:"method"`
|
||||
URL string `json:"url"`
|
||||
Header map[string][]string `json:"header"`
|
||||
Form map[string][]string `json:"form"`
|
||||
Body []byte `json:"body"`
|
||||
}
|
||||
|
||||
// modelUser is an extension of model.User to marshal all fields to JSON
|
||||
type modelUser struct {
|
||||
User *model.User `json:"user"`
|
||||
|
||||
ForgeRemoteID model.ForgeRemoteID `json:"forge_remote_id"`
|
||||
|
||||
// Token is the oauth2 token.
|
||||
Token string `json:"token"`
|
||||
|
||||
// Secret is the oauth2 token secret.
|
||||
Secret string `json:"secret"`
|
||||
|
||||
// Expiry is the token and secret expiration timestamp.
|
||||
Expiry int64 `json:"expiry"`
|
||||
|
||||
// Hash is a unique token used to sign tokens.
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
func (m *modelUser) asModel() *model.User {
|
||||
m.User.ForgeRemoteID = m.ForgeRemoteID
|
||||
m.User.Token = m.Token
|
||||
m.User.Secret = m.Secret
|
||||
m.User.Expiry = m.Expiry
|
||||
m.User.Hash = m.Hash
|
||||
return m.User
|
||||
}
|
||||
|
||||
func modelUserFromModel(u *model.User) *modelUser {
|
||||
return &modelUser{
|
||||
User: u,
|
||||
ForgeRemoteID: u.ForgeRemoteID,
|
||||
Token: u.Token,
|
||||
Secret: u.Secret,
|
||||
Expiry: u.Expiry,
|
||||
Hash: u.Hash,
|
||||
}
|
||||
}
|
||||
|
||||
// modelRepo is an extension of model.Repo to marshal all fields to JSON
|
||||
type modelRepo struct {
|
||||
Repo *model.Repo `json:"repo"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Hash string `json:"hash"`
|
||||
Perm *model.Perm `json:"perm"`
|
||||
}
|
||||
|
||||
func (m *modelRepo) asModel() *model.Repo {
|
||||
m.Repo.UserID = m.UserID
|
||||
m.Repo.Hash = m.Hash
|
||||
m.Repo.Perm = m.Perm
|
||||
return m.Repo
|
||||
}
|
||||
|
||||
func modelRepoFromModel(r *model.Repo) *modelRepo {
|
||||
return &modelRepo{
|
||||
Repo: r,
|
||||
UserID: r.UserID,
|
||||
Hash: r.Hash,
|
||||
Perm: r.Perm,
|
||||
}
|
||||
}
|
377
server/forge/addon/client.go
Normal file
377
server/forge/addon/client.go
Normal file
|
@ -0,0 +1,377 @@
|
|||
// 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 addon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/rpc"
|
||||
"os/exec"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
||||
// make sure RPC implements forge.Forge
|
||||
var _ forge.Forge = new(RPC)
|
||||
|
||||
func Load(file string) (forge.Forge, error) {
|
||||
client := plugin.NewClient(&plugin.ClientConfig{
|
||||
HandshakeConfig: HandshakeConfig,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
pluginKey: &Plugin{},
|
||||
},
|
||||
Cmd: exec.Command(file),
|
||||
Logger: &clientLogger{
|
||||
logger: log.With().Str("addon", file).Logger(),
|
||||
},
|
||||
})
|
||||
// TODO defer client.Kill()
|
||||
|
||||
rpcClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw, err := rpcClient.Dispense(pluginKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extension, _ := raw.(forge.Forge)
|
||||
return extension, nil
|
||||
}
|
||||
|
||||
type RPC struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func (g *RPC) Name() string {
|
||||
var resp string
|
||||
_ = g.client.Call("Plugin.Name", nil, &resp)
|
||||
return resp
|
||||
}
|
||||
|
||||
func (g *RPC) URL() string {
|
||||
var resp string
|
||||
_ = g.client.Call("Plugin.URL", nil, &resp)
|
||||
return resp
|
||||
}
|
||||
|
||||
func (g *RPC) Login(_ context.Context, r *types.OAuthRequest) (*model.User, string, error) {
|
||||
args, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.Login", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var resp responseLogin
|
||||
err = json.Unmarshal(jsonResp, &resp)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return resp.User.asModel(), resp.RedirectURL, nil
|
||||
}
|
||||
|
||||
func (g *RPC) Auth(_ context.Context, token, secret string) (string, error) {
|
||||
args, err := json.Marshal(&argumentsAuth{
|
||||
Token: token,
|
||||
Secret: secret,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var resp string
|
||||
return resp, g.client.Call("Plugin.Auth", args, &resp)
|
||||
}
|
||||
|
||||
func (g *RPC) Teams(_ context.Context, u *model.User) ([]*model.Team, error) {
|
||||
args, err := json.Marshal(modelUserFromModel(u))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.Teams", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []*model.Team
|
||||
return resp, json.Unmarshal(jsonResp, &resp)
|
||||
}
|
||||
|
||||
func (g *RPC) Repo(_ context.Context, u *model.User, remoteID model.ForgeRemoteID, owner, name string) (*model.Repo, error) {
|
||||
args, err := json.Marshal(&argumentsRepo{
|
||||
U: modelUserFromModel(u),
|
||||
RemoteID: remoteID,
|
||||
Owner: owner,
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.Repo", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp *modelRepo
|
||||
err = json.Unmarshal(jsonResp, resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.asModel(), nil
|
||||
}
|
||||
|
||||
func (g *RPC) Repos(_ context.Context, u *model.User) ([]*model.Repo, error) {
|
||||
args, err := json.Marshal(modelUserFromModel(u))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.Repos", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []*modelRepo
|
||||
err = json.Unmarshal(jsonResp, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var modelRepos []*model.Repo
|
||||
for _, repo := range resp {
|
||||
modelRepos = append(modelRepos, repo.asModel())
|
||||
}
|
||||
return modelRepos, nil
|
||||
}
|
||||
|
||||
func (g *RPC) File(_ context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]byte, error) {
|
||||
args, err := json.Marshal(&argumentsFileDir{
|
||||
U: modelUserFromModel(u),
|
||||
R: modelRepoFromModel(r),
|
||||
B: b,
|
||||
F: f,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []byte
|
||||
return resp, g.client.Call("Plugin.File", args, &resp)
|
||||
}
|
||||
|
||||
func (g *RPC) Dir(_ context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]*types.FileMeta, error) {
|
||||
args, err := json.Marshal(&argumentsFileDir{
|
||||
U: modelUserFromModel(u),
|
||||
R: modelRepoFromModel(r),
|
||||
B: b,
|
||||
F: f,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.Dir", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []*types.FileMeta
|
||||
return resp, json.Unmarshal(jsonResp, &resp)
|
||||
}
|
||||
|
||||
func (g *RPC) Status(_ context.Context, u *model.User, r *model.Repo, b *model.Pipeline, p *model.Workflow) error {
|
||||
args, err := json.Marshal(&argumentsStatus{
|
||||
U: modelUserFromModel(u),
|
||||
R: modelRepoFromModel(r),
|
||||
B: b,
|
||||
P: p,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var jsonResp []byte
|
||||
return g.client.Call("Plugin.Status", args, &jsonResp)
|
||||
}
|
||||
|
||||
func (g *RPC) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||
args, err := json.Marshal(&argumentsNetrc{
|
||||
U: modelUserFromModel(u),
|
||||
R: modelRepoFromModel(r),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.Netrc", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp *model.Netrc
|
||||
return resp, json.Unmarshal(jsonResp, &resp)
|
||||
}
|
||||
|
||||
func (g *RPC) Activate(_ context.Context, u *model.User, r *model.Repo, link string) error {
|
||||
args, err := json.Marshal(&argumentsActivateDeactivate{
|
||||
U: modelUserFromModel(u),
|
||||
R: modelRepoFromModel(r),
|
||||
Link: link,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var jsonResp []byte
|
||||
return g.client.Call("Plugin.Activate", args, &jsonResp)
|
||||
}
|
||||
|
||||
func (g *RPC) Deactivate(_ context.Context, u *model.User, r *model.Repo, link string) error {
|
||||
args, err := json.Marshal(&argumentsActivateDeactivate{
|
||||
U: modelUserFromModel(u),
|
||||
R: modelRepoFromModel(r),
|
||||
Link: link,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var jsonResp []byte
|
||||
return g.client.Call("Plugin.Deactivate", args, &jsonResp)
|
||||
}
|
||||
|
||||
func (g *RPC) Branches(_ context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]string, error) {
|
||||
args, err := json.Marshal(&argumentsBranchesPullRequests{
|
||||
U: modelUserFromModel(u),
|
||||
R: modelRepoFromModel(r),
|
||||
P: p,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.Branches", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []string
|
||||
return resp, json.Unmarshal(jsonResp, &resp)
|
||||
}
|
||||
|
||||
func (g *RPC) BranchHead(_ context.Context, u *model.User, r *model.Repo, branch string) (*model.Commit, error) {
|
||||
args, err := json.Marshal(&argumentsBranchHead{
|
||||
U: modelUserFromModel(u),
|
||||
R: modelRepoFromModel(r),
|
||||
Branch: branch,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.BranchHead", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp *model.Commit
|
||||
return resp, json.Unmarshal(jsonResp, &resp)
|
||||
}
|
||||
|
||||
func (g *RPC) PullRequests(_ context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]*model.PullRequest, error) {
|
||||
args, err := json.Marshal(&argumentsBranchesPullRequests{
|
||||
U: modelUserFromModel(u),
|
||||
R: modelRepoFromModel(r),
|
||||
P: p,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.PullRequests", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []*model.PullRequest
|
||||
return resp, json.Unmarshal(jsonResp, &resp)
|
||||
}
|
||||
|
||||
func (g *RPC) Hook(_ context.Context, r *http.Request) (*model.Repo, *model.Pipeline, error) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
args, err := json.Marshal(&httpRequest{
|
||||
Method: r.Method,
|
||||
URL: r.URL.String(),
|
||||
Header: r.Header,
|
||||
Form: r.Form,
|
||||
Body: body,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.Hook", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var resp responseHook
|
||||
err = json.Unmarshal(jsonResp, &resp)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp.Repo.asModel(), resp.Pipeline, nil
|
||||
}
|
||||
|
||||
func (g *RPC) OrgMembership(_ context.Context, u *model.User, org string) (*model.OrgPerm, error) {
|
||||
args, err := json.Marshal(&argumentsOrgMembershipOrg{
|
||||
U: modelUserFromModel(u),
|
||||
Org: org,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.OrgMembership", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp *model.OrgPerm
|
||||
return resp, json.Unmarshal(jsonResp, &resp)
|
||||
}
|
||||
|
||||
func (g *RPC) Org(_ context.Context, u *model.User, org string) (*model.Org, error) {
|
||||
args, err := json.Marshal(&argumentsOrgMembershipOrg{
|
||||
U: modelUserFromModel(u),
|
||||
Org: org,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var jsonResp []byte
|
||||
err = g.client.Call("Plugin.Org", args, &jsonResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp *model.Org
|
||||
return resp, json.Unmarshal(jsonResp, &resp)
|
||||
}
|
165
server/forge/addon/logger.go
Normal file
165
server/forge/addon/logger.go
Normal file
|
@ -0,0 +1,165 @@
|
|||
// 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 addon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
stdlog "log"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type clientLogger struct {
|
||||
logger zerolog.Logger
|
||||
name string
|
||||
withArgs []any
|
||||
}
|
||||
|
||||
func convertLvl(level hclog.Level) zerolog.Level {
|
||||
switch level {
|
||||
case hclog.Error:
|
||||
return zerolog.ErrorLevel
|
||||
case hclog.Warn:
|
||||
return zerolog.WarnLevel
|
||||
case hclog.Info:
|
||||
return zerolog.InfoLevel
|
||||
case hclog.Debug:
|
||||
return zerolog.DebugLevel
|
||||
case hclog.Trace:
|
||||
return zerolog.TraceLevel
|
||||
}
|
||||
return zerolog.NoLevel
|
||||
}
|
||||
|
||||
func (c *clientLogger) applyArgs(args []any) *zerolog.Logger {
|
||||
var key string
|
||||
logger := c.logger.With()
|
||||
args = append(args, c.withArgs)
|
||||
for i, arg := range args {
|
||||
switch {
|
||||
case key != "":
|
||||
logger.Any(key, arg)
|
||||
key = ""
|
||||
case i == len(args)-1:
|
||||
logger.Any(hclog.MissingKey, arg)
|
||||
default:
|
||||
|
||||
key, _ = arg.(string)
|
||||
}
|
||||
}
|
||||
l := logger.Logger()
|
||||
return &l
|
||||
}
|
||||
|
||||
func (c *clientLogger) Log(level hclog.Level, msg string, args ...any) {
|
||||
c.applyArgs(args).WithLevel(convertLvl(level)).Msg(msg)
|
||||
}
|
||||
|
||||
func (c *clientLogger) Trace(msg string, args ...any) {
|
||||
c.applyArgs(args).Trace().Msg(msg)
|
||||
}
|
||||
|
||||
func (c *clientLogger) Debug(msg string, args ...any) {
|
||||
c.applyArgs(args).Debug().Msg(msg)
|
||||
}
|
||||
|
||||
func (c *clientLogger) Info(msg string, args ...any) {
|
||||
c.applyArgs(args).Info().Msg(msg)
|
||||
}
|
||||
|
||||
func (c *clientLogger) Warn(msg string, args ...any) {
|
||||
c.applyArgs(args).Warn().Msg(msg)
|
||||
}
|
||||
|
||||
func (c *clientLogger) Error(msg string, args ...any) {
|
||||
c.applyArgs(args).Error().Msg(msg)
|
||||
}
|
||||
|
||||
func (c *clientLogger) IsTrace() bool {
|
||||
return log.Logger.GetLevel() >= zerolog.TraceLevel
|
||||
}
|
||||
|
||||
func (c *clientLogger) IsDebug() bool {
|
||||
return log.Logger.GetLevel() >= zerolog.DebugLevel
|
||||
}
|
||||
|
||||
func (c *clientLogger) IsInfo() bool {
|
||||
return log.Logger.GetLevel() >= zerolog.InfoLevel
|
||||
}
|
||||
|
||||
func (c *clientLogger) IsWarn() bool {
|
||||
return log.Logger.GetLevel() >= zerolog.WarnLevel
|
||||
}
|
||||
|
||||
func (c *clientLogger) IsError() bool {
|
||||
return log.Logger.GetLevel() >= zerolog.ErrorLevel
|
||||
}
|
||||
|
||||
func (c *clientLogger) ImpliedArgs() []any {
|
||||
return c.withArgs
|
||||
}
|
||||
|
||||
func (c *clientLogger) With(args ...any) hclog.Logger {
|
||||
return &clientLogger{
|
||||
logger: c.logger,
|
||||
name: c.name,
|
||||
withArgs: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientLogger) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *clientLogger) Named(name string) hclog.Logger {
|
||||
curr := c.name
|
||||
if curr != "" {
|
||||
curr = c.name + "."
|
||||
}
|
||||
return c.ResetNamed(curr + name)
|
||||
}
|
||||
|
||||
func (c *clientLogger) ResetNamed(name string) hclog.Logger {
|
||||
return &clientLogger{
|
||||
logger: c.logger,
|
||||
name: name,
|
||||
withArgs: c.withArgs,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientLogger) SetLevel(level hclog.Level) {
|
||||
c.logger = c.logger.Level(convertLvl(level))
|
||||
}
|
||||
|
||||
func (c *clientLogger) StandardLogger(opts *hclog.StandardLoggerOptions) *stdlog.Logger {
|
||||
return stdlog.New(c.StandardWriter(opts), "", 0)
|
||||
}
|
||||
|
||||
func (c *clientLogger) StandardWriter(*hclog.StandardLoggerOptions) io.Writer {
|
||||
return ioAdapter{logger: c.logger}
|
||||
}
|
||||
|
||||
type ioAdapter struct {
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
func (i ioAdapter) Write(p []byte) (n int, err error) {
|
||||
str := string(bytes.TrimRight(p, " \t\n"))
|
||||
i.logger.Log().Msg(str)
|
||||
return len(p), nil
|
||||
}
|
43
server/forge/addon/plugin.go
Normal file
43
server/forge/addon/plugin.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// 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 addon
|
||||
|
||||
import (
|
||||
"net/rpc"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
)
|
||||
|
||||
const pluginKey = "forge"
|
||||
|
||||
var HandshakeConfig = plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "WOODPECKER_FORGE_ADDON_PLUGIN",
|
||||
MagicCookieValue: "woodpecker-plugin-magic-cookie-value",
|
||||
}
|
||||
|
||||
type Plugin struct {
|
||||
Impl forge.Forge
|
||||
}
|
||||
|
||||
func (p *Plugin) Server(*plugin.MuxBroker) (any, error) {
|
||||
return &RPCServer{Impl: p.Impl}, nil
|
||||
}
|
||||
|
||||
func (*Plugin) Client(_ *plugin.MuxBroker, c *rpc.Client) (any, error) {
|
||||
return &RPC{client: c}, nil
|
||||
}
|
278
server/forge/addon/server.go
Normal file
278
server/forge/addon/server.go
Normal file
|
@ -0,0 +1,278 @@
|
|||
// 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 addon
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
|
||||
)
|
||||
|
||||
func Serve(impl forge.Forge) {
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: HandshakeConfig,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
pluginKey: &Plugin{Impl: impl},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func mkCtx() context.Context {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
type RPCServer struct {
|
||||
Impl forge.Forge
|
||||
}
|
||||
|
||||
func (s *RPCServer) Name(_ []byte, resp *string) error {
|
||||
*resp = s.Impl.Name()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPCServer) URL(_ []byte, resp *string) error {
|
||||
*resp = s.Impl.URL()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RPCServer) Teams(args []byte, resp *[]byte) error {
|
||||
var a *modelUser
|
||||
err := json.Unmarshal(args, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
teams, err := s.Impl.Teams(mkCtx(), a.asModel())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(teams)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) Repo(args []byte, resp *[]byte) error {
|
||||
var a argumentsRepo
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repos, err := s.Impl.Repo(mkCtx(), a.U.asModel(), a.RemoteID, a.Owner, a.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(modelRepoFromModel(repos))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) Repos(args []byte, resp *[]byte) error {
|
||||
var a *modelUser
|
||||
err := json.Unmarshal(args, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repos, err := s.Impl.Repos(mkCtx(), a.asModel())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var modelRepos []*modelRepo
|
||||
for _, repo := range repos {
|
||||
modelRepos = append(modelRepos, modelRepoFromModel(repo))
|
||||
}
|
||||
*resp, err = json.Marshal(modelRepos)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) File(args []byte, resp *[]byte) error {
|
||||
var a argumentsFileDir
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = s.Impl.File(mkCtx(), a.U.asModel(), a.R.asModel(), a.B, a.F)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) Dir(args []byte, resp *[]byte) error {
|
||||
var a argumentsFileDir
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
meta, err := s.Impl.Dir(mkCtx(), a.U.asModel(), a.R.asModel(), a.B, a.F)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(meta)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) Status(args []byte, resp *[]byte) error {
|
||||
var a argumentsStatus
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp = []byte{}
|
||||
return s.Impl.Status(mkCtx(), a.U.asModel(), a.R.asModel(), a.B, a.P)
|
||||
}
|
||||
|
||||
func (s *RPCServer) Netrc(args []byte, resp *[]byte) error {
|
||||
var a argumentsNetrc
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netrc, err := s.Impl.Netrc(a.U.asModel(), a.R.asModel())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(netrc)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) Activate(args []byte, resp *[]byte) error {
|
||||
var a argumentsActivateDeactivate
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp = []byte{}
|
||||
return s.Impl.Activate(mkCtx(), a.U.asModel(), a.R.asModel(), a.Link)
|
||||
}
|
||||
|
||||
func (s *RPCServer) Deactivate(args []byte, resp *[]byte) error {
|
||||
var a argumentsActivateDeactivate
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp = []byte{}
|
||||
return s.Impl.Deactivate(mkCtx(), a.U.asModel(), a.R.asModel(), a.Link)
|
||||
}
|
||||
|
||||
func (s *RPCServer) Branches(args []byte, resp *[]byte) error {
|
||||
var a argumentsBranchesPullRequests
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
branches, err := s.Impl.Branches(mkCtx(), a.U.asModel(), a.R.asModel(), a.P)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(branches)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) BranchHead(args []byte, resp *[]byte) error {
|
||||
var a argumentsBranchHead
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commit, err := s.Impl.BranchHead(mkCtx(), a.U.asModel(), a.R.asModel(), a.Branch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(commit)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) PullRequests(args []byte, resp *[]byte) error {
|
||||
var a argumentsBranchesPullRequests
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prs, err := s.Impl.PullRequests(mkCtx(), a.U.asModel(), a.R.asModel(), a.P)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(prs)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) OrgMembership(args []byte, resp *[]byte) error {
|
||||
var a argumentsOrgMembershipOrg
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
org, err := s.Impl.OrgMembership(mkCtx(), a.U.asModel(), a.Org)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(org)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) Org(args []byte, resp *[]byte) error {
|
||||
var a argumentsOrgMembershipOrg
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
org, err := s.Impl.Org(mkCtx(), a.U.asModel(), a.Org)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(org)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) Hook(args []byte, resp *[]byte) error {
|
||||
var a httpRequest
|
||||
err := json.Unmarshal(args, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(a.Method, a.URL, bytes.NewBuffer(a.Body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header = a.Header
|
||||
req.Form = a.Form
|
||||
repo, pipeline, err := s.Impl.Hook(mkCtx(), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(&responseHook{
|
||||
Repo: modelRepoFromModel(repo),
|
||||
Pipeline: pipeline,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *RPCServer) Login(args []byte, resp *[]byte) error {
|
||||
var a *types.OAuthRequest
|
||||
err := json.Unmarshal(args, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user, red, err := s.Impl.Login(mkCtx(), a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*resp, err = json.Marshal(&responseLogin{
|
||||
User: modelUserFromModel(user),
|
||||
RedirectURL: red,
|
||||
})
|
||||
return err
|
||||
}
|
|
@ -17,6 +17,7 @@ package bitbucket
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -232,9 +233,15 @@ func (c *config) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error
|
|||
func (c *config) File(ctx context.Context, u *model.User, r *model.Repo, p *model.Pipeline, f string) ([]byte, error) {
|
||||
config, err := c.newClient(ctx, u).FindSource(r.Owner, r.Name, p.Commit, f)
|
||||
if err != nil {
|
||||
var rspErr internal.Error
|
||||
if ok := errors.As(err, &rspErr); ok && rspErr.Status == http.StatusNotFound {
|
||||
return nil, &forge_types.ErrConfigNotFound{
|
||||
Configs: []string{f},
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return []byte(*config), err
|
||||
return []byte(*config), nil
|
||||
}
|
||||
|
||||
// Dir fetches a folder from the bitbucket repository
|
||||
|
@ -245,6 +252,12 @@ func (c *config) Dir(ctx context.Context, u *model.User, r *model.Repo, p *model
|
|||
for {
|
||||
filesResp, err := client.GetRepoFiles(r.Owner, r.Name, p.Commit, f, page)
|
||||
if err != nil {
|
||||
var rspErr internal.Error
|
||||
if ok := errors.As(err, &rspErr); ok && rspErr.Status == http.StatusNotFound {
|
||||
return nil, &forge_types.ErrConfigNotFound{
|
||||
Configs: []string{f},
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range filesResp.Values {
|
||||
|
|
|
@ -18,6 +18,7 @@ package bitbucket
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -179,6 +180,7 @@ func Test_bitbucket(t *testing.T) {
|
|||
g.It("Should handle not found error", func() {
|
||||
_, err := c.File(ctx, fakeUser, fakeRepo, fakePipeline, "file_not_found")
|
||||
g.Assert(err).IsNotNil()
|
||||
g.Assert(errors.Is(err, &types.ErrConfigNotFound{})).IsTrue()
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -222,8 +224,9 @@ func Test_bitbucket(t *testing.T) {
|
|||
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()
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -118,7 +118,7 @@ func getChangedFilesFromPushHook(hook *pushHook) []string {
|
|||
files = append(files, hook.HeadCommit.Removed...)
|
||||
files = append(files, hook.HeadCommit.Modified...)
|
||||
|
||||
return utils.DedupStrings(files)
|
||||
return utils.DeduplicateStrings(files)
|
||||
}
|
||||
|
||||
// helper function that extracts the Pipeline data from a Gitea tag hook
|
||||
|
|
|
@ -18,7 +18,7 @@ package github
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-github/v60/github"
|
||||
"github.com/google/go-github/v61/github"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/google/go-github/v60/github"
|
||||
"github.com/google/go-github/v61/github"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v60/github"
|
||||
"github.com/google/go-github/v61/github"
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
|
@ -51,6 +51,7 @@ type Opts struct {
|
|||
Secret string // GitHub oauth client secret.
|
||||
SkipVerify bool // Skip ssl verification.
|
||||
MergeRef bool // Clone pull requests using the merge ref.
|
||||
OnlyPublic bool // Only obtain OAuth tokens with access to public repos.
|
||||
}
|
||||
|
||||
// New returns a Forge implementation that integrates with a GitHub Cloud or
|
||||
|
@ -63,6 +64,7 @@ func New(opts Opts) (forge.Forge, error) {
|
|||
Secret: opts.Secret,
|
||||
SkipVerify: opts.SkipVerify,
|
||||
MergeRef: opts.MergeRef,
|
||||
OnlyPublic: opts.OnlyPublic,
|
||||
}
|
||||
if opts.URL != defaultURL {
|
||||
r.url = strings.TrimSuffix(opts.URL, "/")
|
||||
|
@ -79,6 +81,7 @@ type client struct {
|
|||
Secret string
|
||||
SkipVerify bool
|
||||
MergeRef bool
|
||||
OnlyPublic bool
|
||||
}
|
||||
|
||||
// Name returns the string name of this driver
|
||||
|
@ -405,10 +408,17 @@ func (c *client) newContext(ctx context.Context) context.Context {
|
|||
|
||||
// helper function to return the GitHub oauth2 config
|
||||
func (c *client) newConfig() *oauth2.Config {
|
||||
scopes := []string{"user:email", "read:org"}
|
||||
if c.OnlyPublic {
|
||||
scopes = append(scopes, []string{"admin:repo_hook", "repo:status"}...)
|
||||
} else {
|
||||
scopes = append(scopes, "repo")
|
||||
}
|
||||
|
||||
return &oauth2.Config{
|
||||
ClientID: c.Client,
|
||||
ClientSecret: c.Secret,
|
||||
Scopes: []string{"repo", "user:email", "read:org"},
|
||||
Scopes: scopes,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("%s/login/oauth/authorize", c.url),
|
||||
TokenURL: fmt.Sprintf("%s/login/oauth/access_token", c.url),
|
||||
|
@ -620,7 +630,7 @@ func (c *client) loadChangedFilesFromPullRequest(ctx context.Context, pull *gith
|
|||
|
||||
opts.Page = resp.NextPage
|
||||
}
|
||||
return utils.DedupStrings(fileList), nil
|
||||
return utils.DeduplicateStrings(fileList), nil
|
||||
})
|
||||
|
||||
return pipeline, err
|
||||
|
|
|
@ -22,7 +22,7 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-github/v60/github"
|
||||
"github.com/google/go-github/v61/github"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
|
@ -215,5 +215,5 @@ func getChangedFilesFromCommits(commits []*github.HeadCommit) []string {
|
|||
files = append(files, cm.Removed...)
|
||||
files = append(files, cm.Modified...)
|
||||
}
|
||||
return utils.DedupStrings(files)
|
||||
return utils.DeduplicateStrings(files)
|
||||
}
|
||||
|
|
|
@ -189,7 +189,7 @@ func convertPushHook(hook *gitlab.PushEvent) (*model.Repo, *model.Pipeline, erro
|
|||
files = append(files, cm.Removed...)
|
||||
files = append(files, cm.Modified...)
|
||||
}
|
||||
pipeline.ChangedFiles = utils.DedupStrings(files)
|
||||
pipeline.ChangedFiles = utils.DeduplicateStrings(files)
|
||||
|
||||
return repo, pipeline, nil
|
||||
}
|
||||
|
|
|
@ -808,7 +808,7 @@ func (g *GitLab) loadChangedFilesFromMergeRequest(ctx context.Context, tmpRepo *
|
|||
for _, file := range changes {
|
||||
files = append(files, file.NewPath, file.OldPath)
|
||||
}
|
||||
pipeline.ChangedFiles = utils.DedupStrings(files)
|
||||
pipeline.ChangedFiles = utils.DeduplicateStrings(files)
|
||||
|
||||
return pipeline, nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -525,11 +525,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
|
||||
|
|
|
@ -16,41 +16,41 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types"
|
||||
)
|
||||
|
||||
type Pipeline struct {
|
||||
ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"`
|
||||
RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'pipeline_repo_id'"`
|
||||
Number int64 `json:"number" xorm:"UNIQUE(s) 'pipeline_number'"`
|
||||
Author string `json:"author" xorm:"INDEX 'pipeline_author'"`
|
||||
Parent int64 `json:"parent" xorm:"pipeline_parent"`
|
||||
Event WebhookEvent `json:"event" xorm:"pipeline_event"`
|
||||
Status StatusValue `json:"status" xorm:"INDEX 'pipeline_status'"`
|
||||
Errors []*errors.PipelineError `json:"errors" xorm:"json 'pipeline_errors'"`
|
||||
Created int64 `json:"created_at" xorm:"pipeline_created"`
|
||||
Updated int64 `json:"updated_at" xorm:"updated NOT NULL DEFAULT 0 'updated'"`
|
||||
Started int64 `json:"started_at" xorm:"pipeline_started"`
|
||||
Finished int64 `json:"finished_at" xorm:"pipeline_finished"`
|
||||
Deploy string `json:"deploy_to" xorm:"pipeline_deploy"`
|
||||
Commit string `json:"commit" xorm:"pipeline_commit"`
|
||||
Branch string `json:"branch" xorm:"pipeline_branch"`
|
||||
Ref string `json:"ref" xorm:"pipeline_ref"`
|
||||
Refspec string `json:"refspec" xorm:"pipeline_refspec"`
|
||||
Title string `json:"title" xorm:"pipeline_title"`
|
||||
Message string `json:"message" xorm:"TEXT 'pipeline_message'"`
|
||||
Timestamp int64 `json:"timestamp" xorm:"pipeline_timestamp"`
|
||||
Sender string `json:"sender" xorm:"pipeline_sender"` // uses reported user for webhooks and name of cron for cron pipelines
|
||||
Avatar string `json:"author_avatar" xorm:"pipeline_avatar"`
|
||||
Email string `json:"author_email" xorm:"pipeline_email"`
|
||||
ForgeURL string `json:"forge_url" xorm:"pipeline_forge_url"`
|
||||
Reviewer string `json:"reviewed_by" xorm:"pipeline_reviewer"`
|
||||
Reviewed int64 `json:"reviewed_at" xorm:"pipeline_reviewed"`
|
||||
Workflows []*Workflow `json:"workflows,omitempty" xorm:"-"`
|
||||
ChangedFiles []string `json:"changed_files,omitempty" xorm:"LONGTEXT 'changed_files'"`
|
||||
AdditionalVariables map[string]string `json:"variables,omitempty" xorm:"json 'additional_variables'"`
|
||||
PullRequestLabels []string `json:"pr_labels,omitempty" xorm:"json 'pr_labels'"`
|
||||
IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"`
|
||||
ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"`
|
||||
RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'pipeline_repo_id'"`
|
||||
Number int64 `json:"number" xorm:"UNIQUE(s) 'pipeline_number'"`
|
||||
Author string `json:"author" xorm:"INDEX 'pipeline_author'"`
|
||||
Parent int64 `json:"parent" xorm:"pipeline_parent"`
|
||||
Event WebhookEvent `json:"event" xorm:"pipeline_event"`
|
||||
Status StatusValue `json:"status" xorm:"INDEX 'pipeline_status'"`
|
||||
Errors []*types.PipelineError `json:"errors" xorm:"json 'pipeline_errors'"`
|
||||
Created int64 `json:"created_at" xorm:"pipeline_created"`
|
||||
Updated int64 `json:"updated_at" xorm:"updated NOT NULL DEFAULT 0 'updated'"`
|
||||
Started int64 `json:"started_at" xorm:"pipeline_started"`
|
||||
Finished int64 `json:"finished_at" xorm:"pipeline_finished"`
|
||||
Deploy string `json:"deploy_to" xorm:"pipeline_deploy"`
|
||||
Commit string `json:"commit" xorm:"pipeline_commit"`
|
||||
Branch string `json:"branch" xorm:"pipeline_branch"`
|
||||
Ref string `json:"ref" xorm:"pipeline_ref"`
|
||||
Refspec string `json:"refspec" xorm:"pipeline_refspec"`
|
||||
Title string `json:"title" xorm:"pipeline_title"`
|
||||
Message string `json:"message" xorm:"TEXT 'pipeline_message'"`
|
||||
Timestamp int64 `json:"timestamp" xorm:"pipeline_timestamp"`
|
||||
Sender string `json:"sender" xorm:"pipeline_sender"` // uses reported user for webhooks and name of cron for cron pipelines
|
||||
Avatar string `json:"author_avatar" xorm:"pipeline_avatar"`
|
||||
Email string `json:"author_email" xorm:"pipeline_email"`
|
||||
ForgeURL string `json:"forge_url" xorm:"pipeline_forge_url"`
|
||||
Reviewer string `json:"reviewed_by" xorm:"pipeline_reviewer"`
|
||||
Reviewed int64 `json:"reviewed_at" xorm:"pipeline_reviewed"`
|
||||
Workflows []*Workflow `json:"workflows,omitempty" xorm:"-"`
|
||||
ChangedFiles []string `json:"changed_files,omitempty" xorm:"LONGTEXT 'changed_files'"`
|
||||
AdditionalVariables map[string]string `json:"variables,omitempty" xorm:"json 'additional_variables'"`
|
||||
PullRequestLabels []string `json:"pr_labels,omitempty" xorm:"json 'pr_labels'"`
|
||||
IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"`
|
||||
} // @name Pipeline
|
||||
|
||||
// TableName return database table name for xorm
|
||||
|
|
|
@ -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:"INDEX repo_user_id"`
|
||||
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'repo_id'"`
|
||||
UserID int64 `json:"-" xorm:"INDEX 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:"INDEX repo_org_id"`
|
||||
|
@ -44,6 +45,7 @@ type Repo struct {
|
|||
IsGated bool `json:"gated" xorm:"repo_gated"`
|
||||
IsActive bool `json:"active" xorm:"repo_active"`
|
||||
AllowPull bool `json:"allow_pr" xorm:"repo_allow_pr"`
|
||||
AllowDeploy bool `json:"allow_deploy" xorm:"repo_allow_deploy"`
|
||||
Config string `json:"config_file" xorm:"varchar(500) 'repo_config_path'"`
|
||||
Hash string `json:"-" xorm:"varchar(500) 'repo_hash'"`
|
||||
Perm *Perm `json:"-" xorm:"-"`
|
||||
|
@ -112,6 +114,7 @@ type RepoPatch struct {
|
|||
Timeout *int64 `json:"timeout,omitempty"`
|
||||
Visibility *string `json:"visibility,omitempty"`
|
||||
AllowPull *bool `json:"allow_pr,omitempty"`
|
||||
AllowDeploy *bool `json:"allow_deploy,omitempty"`
|
||||
CancelPreviousPipelineEvents *[]WebhookEvent `json:"cancel_previous_pipeline_events"`
|
||||
NetrcOnlyTrusted *bool `json:"netrc_only_trusted"`
|
||||
} // @name RepoPatch
|
||||
|
|
|
@ -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.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue