Merge branch 'main' into renovate/eslint-9.x

This commit is contained in:
qwerty287 2024-04-19 17:00:07 +02:00
commit b3e4c43f8d
No known key found for this signature in database
GPG key ID: 1218A32A886A5002
68 changed files with 2318 additions and 1401 deletions

View file

@ -90,7 +90,11 @@
"binutils", "binutils",
"nocolor", "nocolor",
"logfile", "logfile",
"Keyfunc" "Keyfunc",
"protoc",
"PROTOC",
"GOBIN",
"GOPATH"
], ],
"ignorePaths": [ "ignorePaths": [
"**/node_modules/**/*", "**/node_modules/**/*",

2
.mockery.yaml Normal file
View file

@ -0,0 +1,2 @@
---
disable-version-string: true

View file

@ -24,7 +24,7 @@ repos:
- id: checkmake - id: checkmake
exclude: '^docker/Dockerfile.make$' # actually a Dockerfile and not a makefile exclude: '^docker/Dockerfile.make$' # actually a Dockerfile and not a makefile
- repo: https://github.com/hadolint/hadolint - repo: https://github.com/hadolint/hadolint
rev: v2.12.1-beta rev: v2.12.0
hooks: hooks:
- id: hadolint - id: hadolint
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier

View file

@ -90,11 +90,10 @@ steps:
release: release:
depends_on: depends_on:
- checksums - checksums
image: docker.io/plugins/github-release image: woodpeckerci/plugin-github-release:1.1.2
secrets:
- source: github_token
target: github_release_api_key
settings: settings:
api_key:
from_secret: github_token
files: files:
- dist/*.tar.gz - dist/*.tar.gz
- dist/*.deb - dist/*.deb

View file

@ -58,8 +58,6 @@ ifeq (in_docker,$(firstword $(MAKECMDGOALS)))
-e TARGETOS="$(TARGETOS)" \ -e TARGETOS="$(TARGETOS)" \
-e TARGETARCH="$(TARGETARCH)" \ -e TARGETARCH="$(TARGETARCH)" \
-e CGO_ENABLED="$(CGO_ENABLED)" \ -e CGO_ENABLED="$(CGO_ENABLED)" \
-e GOPATH=/tmp/go \
-e HOME=/tmp/home \
-v $(PWD):/build --rm woodpecker/make:local make $(MAKE_ARGS) -v $(PWD):/build --rm woodpecker/make:local make $(MAKE_ARGS)
else else
@ -110,7 +108,7 @@ clean-all: clean ## Clean all artifacts
rm -rf docs/docs/40-cli.md docs/swagger.json rm -rf docs/docs/40-cli.md docs/swagger.json
.PHONY: generate .PHONY: generate
generate: generate-swagger ## Run all code generations generate: install-tools generate-swagger ## Run all code generations
go generate ./... go generate ./...
generate-swagger: install-tools ## Run swagger code generation generate-swagger: install-tools ## Run swagger code generation
@ -137,6 +135,15 @@ install-tools: ## Install development tools
fi ; \ fi ; \
hash addlicense > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ hash addlicense > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install github.com/google/addlicense@latest; \ 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 fi
ui-dependencies: ## Install UI dependencies ui-dependencies: ## Install UI dependencies

View file

@ -57,7 +57,7 @@ func After(_ *cli.Context) error {
if waitForUpdateCheck != nil { if waitForUpdateCheck != nil {
select { select {
case <-waitForUpdateCheck.Done(): case <-waitForUpdateCheck.Done():
// When the actual command already finished, we still wait 250ms for the update check to finish // When the actual command already finished, we still wait 500ms for the update check to finish
case <-time.After(time.Millisecond * 500): case <-time.After(time.Millisecond * 500):
log.Debug().Msg("Update check stopped due to timeout") log.Debug().Msg("Update check stopped due to timeout")
cancelWaitForUpdate(errors.New("update check timeout")) cancelWaitForUpdate(errors.New("update check timeout"))

View file

@ -30,9 +30,12 @@ func Load(c *cli.Context) error {
return err return err
} }
if config == nil && !c.IsSet("server-url") && !c.IsSet("token") { if config == nil {
log.Info().Msg("The woodpecker-cli is not yet set up. Please run `woodpecker-cli setup`") config = &Config{
return errors.New("woodpecker-cli is not setup") LogLevel: "info",
ServerURL: c.String("server-url"),
Token: c.String("token"),
}
} }
if !c.IsSet("server") { 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 return nil
} }

View file

@ -3891,6 +3891,9 @@ const docTemplate = `{
"Org": { "Org": {
"type": "object", "type": "object",
"properties": { "properties": {
"forge_id": {
"type": "integer"
},
"id": { "id": {
"type": "integer" "type": "integer"
}, },
@ -4122,6 +4125,9 @@ const docTemplate = `{
"default_branch": { "default_branch": {
"type": "string" "type": "string"
}, },
"forge_id": {
"type": "integer"
},
"forge_remote_id": { "forge_remote_id": {
"description": "ForgeRemoteID is the unique identifier for the repository on the forge.", "description": "ForgeRemoteID is the unique identifier for the repository on the forge.",
"type": "string" "type": "string"
@ -4418,6 +4424,9 @@ const docTemplate = `{
"description": "Email is the email address for this user.\n\nrequired: true", "description": "Email is the email address for this user.\n\nrequired: true",
"type": "string" "type": "string"
}, },
"forge_id": {
"type": "integer"
},
"id": { "id": {
"description": "the id for this user.\n\nrequired: true", "description": "the id for this user.\n\nrequired: true",
"type": "integer" "type": "integer"

View file

@ -246,11 +246,6 @@ var flags = append([]cli.Flag{
Usage: "Disable version check in admin web ui.", Usage: "Disable version check in admin web ui.",
Name: "skip-version-check", Name: "skip-version-check",
}, },
&cli.StringFlag{
EnvVars: []string{"WOODPECKER_ADDON_FORGE"},
Name: "addon-forge",
Usage: "forge addon",
},
// //
// backend options for pipeline compiler // backend options for pipeline compiler
// //
@ -309,6 +304,35 @@ var flags = append([]cli.Flag{
Usage: "set the cpus allowed to execute containers", 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 // GitHub
// //
&cli.BoolFlag{ &cli.BoolFlag{
@ -316,24 +340,6 @@ var flags = append([]cli.Flag{
Name: "github", Name: "github",
Usage: "github driver is enabled", 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{ &cli.BoolFlag{
EnvVars: []string{"WOODPECKER_GITHUB_MERGE_REF"}, EnvVars: []string{"WOODPECKER_GITHUB_MERGE_REF"},
Name: "github-merge-ref", Name: "github-merge-ref",
@ -346,11 +352,6 @@ var flags = append([]cli.Flag{
Usage: "github tokens should only get access to public repos", Usage: "github tokens should only get access to public repos",
Value: false, Value: false,
}, },
&cli.BoolFlag{
EnvVars: []string{"WOODPECKER_GITHUB_SKIP_VERIFY"},
Name: "github-skip-verify",
Usage: "github skip ssl verification",
},
// //
// Gitea // Gitea
// //
@ -359,29 +360,6 @@ var flags = append([]cli.Flag{
Name: "gitea", Name: "gitea",
Usage: "gitea driver is enabled", 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{ &cli.StringFlag{
EnvVars: []string{"WOODPECKER_DEV_GITEA_OAUTH_URL"}, EnvVars: []string{"WOODPECKER_DEV_GITEA_OAUTH_URL"},
Name: "gitea-oauth-server", Name: "gitea-oauth-server",
@ -395,18 +373,6 @@ var flags = append([]cli.Flag{
Name: "bitbucket", Name: "bitbucket",
Usage: "bitbucket driver is enabled", 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 // Gitlab
// //
@ -415,29 +381,6 @@ var flags = append([]cli.Flag{
Name: "gitlab", Name: "gitlab",
Usage: "gitlab driver is enabled", 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) // Bitbucket DataCenter/Server (previously Stash)
// //
@ -446,23 +389,6 @@ var flags = append([]cli.Flag{
Name: "bitbucket-dc", Name: "bitbucket-dc",
Usage: "Bitbucket DataCenter/Server driver is enabled", 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{ &cli.StringFlag{
EnvVars: []string{"WOODPECKER_BITBUCKET_DC_GIT_USERNAME"}, EnvVars: []string{"WOODPECKER_BITBUCKET_DC_GIT_USERNAME"},
Name: "bitbucket-dc-git-username", Name: "bitbucket-dc-git-username",

View file

@ -38,7 +38,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto" "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto"
"go.woodpecker-ci.org/woodpecker/v2/server" "go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/cron" "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" woodpeckerGrpcServer "go.woodpecker-ci.org/woodpecker/v2/server/grpc"
"go.woodpecker-ci.org/woodpecker/v2/server/logging" "go.woodpecker-ci.org/woodpecker/v2/server/logging"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "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) _store, err := setupStore(c)
if err != nil { if err != nil {
return fmt.Errorf("can't setup store: %w", err) 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 { if err != nil {
return fmt.Errorf("can't setup globals: %w", err) return fmt.Errorf("can't setup globals: %w", err)
} }
@ -107,7 +102,7 @@ func run(c *cli.Context) error {
setupMetrics(&g, _store) setupMetrics(&g, _store)
g.Go(func() error { g.Go(func() error {
return cron.Start(c.Context, _store, _forge) return cron.Start(c.Context, _store)
}) })
// start the grpc server // start the grpc server
@ -130,7 +125,6 @@ func run(c *cli.Context) error {
) )
woodpeckerServer := woodpeckerGrpcServer.NewWoodpeckerServer( woodpeckerServer := woodpeckerGrpcServer.NewWoodpeckerServer(
_forge,
server.Config.Services.Queue, server.Config.Services.Queue,
server.Config.Services.Logs, server.Config.Services.Logs,
server.Config.Services.Pubsub, server.Config.Services.Pubsub,
@ -270,17 +264,13 @@ func run(c *cli.Context) error {
return g.Wait() return g.Wait()
} }
func setupEvilGlobals(c *cli.Context, s store.Store, f forge.Forge) error { func setupEvilGlobals(c *cli.Context, s store.Store) error {
// forge
server.Config.Services.Forge = f
// services // services
server.Config.Services.Queue = setupQueue(c, s) server.Config.Services.Queue = setupQueue(c, s)
server.Config.Services.Logs = logging.New() server.Config.Services.Logs = logging.New()
server.Config.Services.Pubsub = pubsub.New() server.Config.Services.Pubsub = pubsub.New()
server.Config.Services.Membership = setupMembershipService(c, f) server.Config.Services.Membership = setupMembershipService(c, s)
serviceMangager, err := services.NewManager(c, s, setup.Forge)
serviceMangager, err := services.NewManager(c, s)
if err != nil { if err != nil {
return fmt.Errorf("could not setup service manager: %w", err) return fmt.Errorf("could not setup service manager: %w", err)
} }

View file

@ -18,9 +18,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"os" "os"
"strings"
"time" "time"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
@ -31,13 +29,6 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/server" "go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/cache" "go.woodpecker-ci.org/woodpecker/v2/server/cache"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/addon"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucketdatacenter"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/gitea"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/github"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/gitlab"
"go.woodpecker-ci.org/woodpecker/v2/server/queue" "go.woodpecker-ci.org/woodpecker/v2/server/queue"
"go.woodpecker-ci.org/woodpecker/v2/server/store" "go.woodpecker-ci.org/woodpecker/v2/server/store"
"go.woodpecker-ci.org/woodpecker/v2/server/store/datastore" "go.woodpecker-ci.org/woodpecker/v2/server/store/datastore"
@ -100,103 +91,8 @@ func setupQueue(c *cli.Context, s store.Store) queue.Queue {
return queue.WithTaskStore(queue.New(c.Context), s) return queue.WithTaskStore(queue.New(c.Context), s)
} }
func setupMembershipService(_ *cli.Context, r forge.Forge) cache.MembershipService { func setupMembershipService(_ *cli.Context, _store store.Store) cache.MembershipService {
return cache.NewMembershipService(r) return cache.NewMembershipService(_store)
}
// setupForge helper function to set up the forge from the CLI arguments.
func setupForge(c *cli.Context) (forge.Forge, error) {
switch {
case c.String("addon-forge") != "":
return addon.Load(c.String("addon-forge"))
case c.Bool("github"):
return setupGitHub(c)
case c.Bool("gitlab"):
return setupGitLab(c)
case c.Bool("bitbucket"):
return setupBitbucket(c)
case c.Bool("bitbucket-dc"):
return setupBitbucketDatacenter(c)
case c.Bool("gitea"):
return setupGitea(c)
default:
return nil, fmt.Errorf("version control system not configured")
}
}
// setupBitbucket helper function to setup the Bitbucket forge from the CLI arguments.
func setupBitbucket(c *cli.Context) (forge.Forge, error) {
opts := &bitbucket.Opts{
Client: c.String("bitbucket-client"),
Secret: c.String("bitbucket-secret"),
}
log.Trace().Msgf("forge (bitbucket) opts: %#v", opts)
return bitbucket.New(opts)
}
// setupGitea helper function to set up the Gitea forge from the CLI arguments.
func setupGitea(c *cli.Context) (forge.Forge, error) {
server, err := url.Parse(c.String("gitea-server"))
if err != nil {
return nil, err
}
oauth2Server := c.String("gitea-oauth-server")
if oauth2Server != "" {
oauth2URL, err := url.Parse(oauth2Server)
if err != nil {
return nil, err
}
oauth2Server = strings.TrimRight(oauth2URL.String(), "/")
}
opts := gitea.Opts{
URL: strings.TrimRight(server.String(), "/"),
OAuth2URL: oauth2Server,
Client: c.String("gitea-client"),
Secret: c.String("gitea-secret"),
SkipVerify: c.Bool("gitea-skip-verify"),
}
if len(opts.URL) == 0 {
return nil, fmt.Errorf("WOODPECKER_GITEA_URL must be set")
}
log.Trace().Msgf("forge (gitea) opts: %#v", opts)
return gitea.New(opts)
}
// setupBitbucketDatacenter helper function to setup the Bitbucket DataCenter/Server forge from the CLI arguments.
func setupBitbucketDatacenter(c *cli.Context) (forge.Forge, error) {
opts := bitbucketdatacenter.Opts{
URL: c.String("bitbucket-dc-server"),
Username: c.String("bitbucket-dc-git-username"),
Password: c.String("bitbucket-dc-git-password"),
ClientID: c.String("bitbucket-dc-client-id"),
ClientSecret: c.String("bitbucket-dc-client-secret"),
}
log.Trace().Msgf("Forge (bitbucketdatacenter) opts: %#v", opts)
return bitbucketdatacenter.New(opts)
}
// setupGitLab helper function to setup the GitLab forge from the CLI arguments.
func setupGitLab(c *cli.Context) (forge.Forge, error) {
return gitlab.New(gitlab.Opts{
URL: c.String("gitlab-server"),
ClientID: c.String("gitlab-client"),
ClientSecret: c.String("gitlab-secret"),
SkipVerify: c.Bool("gitlab-skip-verify"),
})
}
// setupGitHub helper function to setup the GitHub forge from the CLI arguments.
func setupGitHub(c *cli.Context) (forge.Forge, error) {
opts := github.Opts{
URL: c.String("github-server"),
Client: c.String("github-client"),
Secret: c.String("github-secret"),
SkipVerify: c.Bool("github-skip-verify"),
MergeRef: c.Bool("github-merge-ref"),
OnlyPublic: c.Bool("github-public-only"),
}
log.Trace().Msgf("forge (github) opts: %#v", opts)
return github.New(opts)
} }
func setupMetrics(g *errgroup.Group, _store store.Store) { func setupMetrics(g *errgroup.Group, _store store.Store) {

View file

@ -1,17 +1,19 @@
# docker build --rm -f docker/Dockerfile.make -t woodpecker/make:local . # 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/golang:1.22-alpine3.19 as golang_image
FROM docker.io/node:21-alpine3.18 FROM docker.io/node:21-alpine3.19
# renovate: datasource=repology depName=alpine_3_18/make versioning=loose # renovate: datasource=repology depName=alpine_3_19/make versioning=loose
ENV MAKE_VERSION="4.4.1-r1" ENV MAKE_VERSION="4.4.1-r2"
# renovate: datasource=repology depName=alpine_3_18/gcc versioning=loose # renovate: datasource=repology depName=alpine_3_19/gcc versioning=loose
ENV GCC_VERSION="12.2.1_git20220924-r10" ENV GCC_VERSION="13.2.1_git20231014-r0"
# renovate: datasource=repology depName=alpine_3_18/binutils-gold versioning=loose # renovate: datasource=repology depName=alpine_3_19/binutils-gold versioning=loose
ENV BINUTILS_GOLD_VERSION="2.40-r7" ENV BINUTILS_GOLD_VERSION="2.41-r0"
# renovate: datasource=repology depName=alpine_3_18/musl-dev versioning=loose # renovate: datasource=repology depName=alpine_3_19/musl-dev versioning=loose
ENV MUSL_DEV_VERSION="1.2.4-r2" 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 corepack enable
# Build packages. # Build packages.
@ -20,9 +22,12 @@ COPY Makefile /
ENV PATH=$PATH:/usr/local/go/bin ENV PATH=$PATH:/usr/local/go/bin
# Cache tools # Cache tools
RUN make install-tools && \ RUN GOBIN=/usr/local/go/bin make install-tools && \
mv /root/go/bin/* /usr/local/go/bin/ && \ rm -rf /Makefile
chmod 755 /usr/local/go/bin/*
ENV GOPATH=/tmp/go
ENV HOME=/tmp/home
ENV PATH=$PATH:/usr/local/go/bin:/tmp/go/bin
WORKDIR /build WORKDIR /build
RUN chmod -R 777 /root RUN chmod -R 777 /root

View file

@ -194,6 +194,11 @@
"name": "Forge deployments", "name": "Forge deployments",
"docs": "https://raw.githubusercontent.com/woodpecker-ci/plugin-deployments/main/docs.md", "docs": "https://raw.githubusercontent.com/woodpecker-ci/plugin-deployments/main/docs.md",
"verified": true "verified": true
},
{
"name": "Twine",
"docs": "https://gitea.elara.ws/music-kraken/plugin-twine/raw/branch/master/docs.md",
"verified": false
} }
] ]
} }

File diff suppressed because it is too large Load diff

8
go.mod
View file

@ -57,11 +57,11 @@ require (
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
github.com/zalando/go-keyring v0.2.4 github.com/zalando/go-keyring v0.2.4
go.uber.org/multierr v1.11.0 go.uber.org/multierr v1.11.0
golang.org/x/crypto v0.21.0 golang.org/x/crypto v0.22.0
golang.org/x/net v0.22.0 golang.org/x/net v0.24.0
golang.org/x/oauth2 v0.18.0 golang.org/x/oauth2 v0.18.0
golang.org/x/sync v0.6.0 golang.org/x/sync v0.6.0
golang.org/x/term v0.18.0 golang.org/x/term v0.19.0
golang.org/x/text v0.14.0 golang.org/x/text v0.14.0
google.golang.org/grpc v1.62.1 google.golang.org/grpc v1.62.1
google.golang.org/protobuf v1.33.0 google.golang.org/protobuf v1.33.0
@ -170,7 +170,7 @@ require (
go.uber.org/zap v1.26.0 // indirect go.uber.org/zap v1.26.0 // indirect
golang.org/x/arch v0.6.0 // indirect golang.org/x/arch v0.6.0 // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/sys v0.19.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.1 // indirect golang.org/x/tools v0.16.1 // indirect
google.golang.org/appengine v1.6.8 // indirect google.golang.org/appengine v1.6.8 // indirect

8
go.sum
View file

@ -542,6 +542,8 @@ 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.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -575,6 +577,8 @@ 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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.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 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
@ -621,6 +625,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -629,6 +635,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View file

@ -16,8 +16,3 @@ package proto
//go:generate protoc --go_out=paths=source_relative:. woodpecker.proto //go:generate protoc --go_out=paths=source_relative:. woodpecker.proto
//go:generate protoc --go-grpc_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

View file

@ -15,8 +15,8 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.32.0 // protoc-gen-go v1.33.0
// protoc v4.25.1 // protoc v4.24.4
// source: woodpecker.proto // source: woodpecker.proto
package proto package proto

View file

@ -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. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions: // versions:
// - protoc-gen-go-grpc v1.3.0 // - protoc-gen-go-grpc v1.3.0
// - protoc v4.25.1 // - protoc v4.24.4
// source: woodpecker.proto // source: woodpecker.proto
package 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) { func (c *woodpeckerClient) Version(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VersionResponse, error) {
out := new(VersionResponse) 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 { if err != nil {
return nil, err 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) { func (c *woodpeckerClient) Next(ctx context.Context, in *NextRequest, opts ...grpc.CallOption) (*NextResponse, error) {
out := new(NextResponse) 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 { if err != nil {
return nil, err 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) { func (c *woodpeckerClient) Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty) 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 { if err != nil {
return nil, err 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) { func (c *woodpeckerClient) Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty) 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 { if err != nil {
return nil, err 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) { func (c *woodpeckerClient) Done(ctx context.Context, in *DoneRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty) 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 { if err != nil {
return nil, err 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) { func (c *woodpeckerClient) Extend(ctx context.Context, in *ExtendRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty) 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 { if err != nil {
return nil, err 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) { func (c *woodpeckerClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty) 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 { if err != nil {
return nil, err 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) { func (c *woodpeckerClient) Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty) 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 { if err != nil {
return nil, err 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) { func (c *woodpeckerClient) RegisterAgent(ctx context.Context, in *RegisterAgentRequest, opts ...grpc.CallOption) (*RegisterAgentResponse, error) {
out := new(RegisterAgentResponse) 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 { if err != nil {
return nil, err 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) { func (c *woodpeckerClient) ReportHealth(ctx context.Context, in *ReportHealthRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty) 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 { if err != nil {
return nil, err return nil, err
} }
@ -249,7 +234,7 @@ func _Woodpecker_Version_Handler(srv interface{}, ctx context.Context, dec func(
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_Version_FullMethodName, FullMethod: "/proto.Woodpecker/Version",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).Version(ctx, req.(*Empty)) 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{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_Next_FullMethodName, FullMethod: "/proto.Woodpecker/Next",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).Next(ctx, req.(*NextRequest)) 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{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_Init_FullMethodName, FullMethod: "/proto.Woodpecker/Init",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).Init(ctx, req.(*InitRequest)) 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{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_Wait_FullMethodName, FullMethod: "/proto.Woodpecker/Wait",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).Wait(ctx, req.(*WaitRequest)) 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{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_Done_FullMethodName, FullMethod: "/proto.Woodpecker/Done",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).Done(ctx, req.(*DoneRequest)) 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{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_Extend_FullMethodName, FullMethod: "/proto.Woodpecker/Extend",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).Extend(ctx, req.(*ExtendRequest)) 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{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_Update_FullMethodName, FullMethod: "/proto.Woodpecker/Update",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).Update(ctx, req.(*UpdateRequest)) 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{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_Log_FullMethodName, FullMethod: "/proto.Woodpecker/Log",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).Log(ctx, req.(*LogRequest)) return srv.(WoodpeckerServer).Log(ctx, req.(*LogRequest))
@ -393,7 +378,7 @@ func _Woodpecker_RegisterAgent_Handler(srv interface{}, ctx context.Context, dec
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_RegisterAgent_FullMethodName, FullMethod: "/proto.Woodpecker/RegisterAgent",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).RegisterAgent(ctx, req.(*RegisterAgentRequest)) return srv.(WoodpeckerServer).RegisterAgent(ctx, req.(*RegisterAgentRequest))
@ -429,7 +414,7 @@ func _Woodpecker_ReportHealth_Handler(srv interface{}, ctx context.Context, dec
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: Woodpecker_ReportHealth_FullMethodName, FullMethod: "/proto.Woodpecker/ReportHealth",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerServer).ReportHealth(ctx, req.(*ReportHealthRequest)) return srv.(WoodpeckerServer).ReportHealth(ctx, req.(*ReportHealthRequest))
@ -493,10 +478,6 @@ var Woodpecker_ServiceDesc = grpc.ServiceDesc{
Metadata: "woodpecker.proto", Metadata: "woodpecker.proto",
} }
const (
WoodpeckerAuth_Auth_FullMethodName = "/proto.WoodpeckerAuth/Auth"
)
// WoodpeckerAuthClient is the client API for WoodpeckerAuth service. // 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. // 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) { func (c *woodpeckerAuthClient) Auth(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthResponse, error) {
out := new(AuthResponse) 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 { if err != nil {
return nil, err return nil, err
} }
@ -559,7 +540,7 @@ func _WoodpeckerAuth_Auth_Handler(srv interface{}, ctx context.Context, dec func
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: WoodpeckerAuth_Auth_FullMethodName, FullMethod: "/proto.WoodpeckerAuth/Auth",
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WoodpeckerAuthServer).Auth(ctx, req.(*AuthRequest)) return srv.(WoodpeckerAuthServer).Auth(ctx, req.(*AuthRequest))

View file

@ -20,6 +20,7 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"go.woodpecker-ci.org/woodpecker/v2/server" "go.woodpecker-ci.org/woodpecker/v2/server"
cronScheduler "go.woodpecker-ci.org/woodpecker/v2/server/cron" cronScheduler "go.woodpecker-ci.org/woodpecker/v2/server/cron"
@ -80,7 +81,7 @@ func RunCron(c *gin.Context) {
return return
} }
repo, newPipeline, err := cronScheduler.CreatePipeline(c, _store, server.Config.Services.Forge, cron) repo, newPipeline, err := cronScheduler.CreatePipeline(c, _store, cron)
if err != nil { if err != nil {
c.String(http.StatusInternalServerError, "Error creating pipeline for cron %q. %s", id, err) c.String(http.StatusInternalServerError, "Error creating pipeline for cron %q. %s", id, err)
return return
@ -109,7 +110,12 @@ func PostCron(c *gin.Context) {
repo := session.Repo(c) repo := session.Repo(c)
user := session.User(c) user := session.User(c)
_store := store.FromContext(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) in := new(model.Cron)
if err := c.Bind(in); err != nil { if err := c.Bind(in); err != nil {
@ -137,7 +143,7 @@ func PostCron(c *gin.Context) {
if in.Branch != "" { if in.Branch != "" {
// check if branch exists on forge // 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 { if err != nil {
c.String(http.StatusBadRequest, "Error inserting cron. branch not resolved: %s", err) c.String(http.StatusBadRequest, "Error inserting cron. branch not resolved: %s", err)
return return
@ -166,7 +172,12 @@ func PatchCron(c *gin.Context) {
repo := session.Repo(c) repo := session.Repo(c)
user := session.User(c) user := session.User(c)
_store := store.FromContext(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) id, err := strconv.ParseInt(c.Param("cron"), 10, 64)
if err != nil { if err != nil {
@ -188,7 +199,7 @@ func PatchCron(c *gin.Context) {
} }
if in.Branch != "" { if in.Branch != "" {
// check if branch exists on forge // 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 { if err != nil {
c.String(http.StatusBadRequest, "Error inserting cron. branch not resolved: %s", err) c.String(http.StatusBadRequest, "Error inserting cron. branch not resolved: %s", err)
return return

View file

@ -19,6 +19,7 @@ import (
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"go.woodpecker-ci.org/woodpecker/v2/server" "go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/forge" "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. // If the forge has a refresh token, the current access token may be stale.
// Therefore, we should refresh prior to dispatching the job. // Therefore, we should refresh prior to dispatching the job.
func refreshUserToken(c *gin.Context, user *model.User) { func refreshUserToken(c *gin.Context, user *model.User) {
_forge := server.Config.Services.Forge
_store := store.FromContext(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
}
forge.Refresh(c, _forge, _store, user) forge.Refresh(c, _forge, _store, user)
} }

View file

@ -104,7 +104,13 @@ func BlockTilQueueHasRunningItem(c *gin.Context) {
// @Param hook body object true "the webhook payload; forge is automatically detected" // @Param hook body object true "the webhook payload; forge is automatically detected"
func PostHook(c *gin.Context) { func PostHook(c *gin.Context) {
_store := store.FromContext(c) _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 // 1. Parse webhook

View file

@ -43,7 +43,12 @@ func HandleLogin(c *gin.Context) {
func HandleAuth(c *gin.Context) { func HandleAuth(c *gin.Context) {
_store := store.FromContext(c) _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 // when dealing with redirects, we may need to adjust the content type. I
// cannot, however, remember why, so need to revisit this line. // 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 // get the user from the database
u, err := _store.GetUserRemoteID(tmpuser.ForgeRemoteID, tmpuser.Login) u, err := _store.GetUserRemoteID(tmpuser.ForgeRemoteID, tmpuser.Login)
if err != nil { if err != nil && !errors.Is(err, types.RecordNotExist) {
if !errors.Is(err, types.RecordNotExist) { _ = c.AbortWithError(http.StatusInternalServerError, err)
_ = c.AbortWithError(http.StatusInternalServerError, err) return
return }
}
if errors.Is(err, types.RecordNotExist) {
// if self-registration is disabled we should return a not authorized error // if self-registration is disabled we should return a not authorized error
if !server.Config.Permissions.Open && !server.Config.Permissions.Admins.IsAdmin(tmpuser) { if !server.Config.Permissions.Open && !server.Config.Permissions.Admins.IsAdmin(tmpuser) {
log.Error().Msgf("cannot register %s. registration closed", tmpuser.Login) log.Error().Msgf("cannot register %s. registration closed", tmpuser.Login)
@ -100,6 +105,7 @@ func HandleAuth(c *gin.Context) {
Secret: tmpuser.Secret, Secret: tmpuser.Secret,
Email: tmpuser.Email, Email: tmpuser.Email,
Avatar: tmpuser.Avatar, Avatar: tmpuser.Avatar,
ForgeID: forgeID,
Hash: base32.StdEncoding.EncodeToString( Hash: base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32), securecookie.GenerateRandomKey(32),
), ),
@ -129,6 +135,7 @@ func HandleAuth(c *gin.Context) {
Name: u.Login, Name: u.Login,
IsUser: true, IsUser: true,
Private: false, Private: false,
ForgeID: u.ForgeID,
} }
if err := _store.OrgCreate(org); err != nil { if err := _store.OrgCreate(org); err != nil {
log.Error().Err(err).Msgf("on user creation, could create org for user") 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) { func GetLoginToken(c *gin.Context) {
_store := store.FromContext(c) _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{} in := &tokenPayload{}
err := c.Bind(in) err = c.Bind(in)
if err != nil { if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err) _ = c.AbortWithError(http.StatusBadRequest, err)
return 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 { if err != nil {
_ = c.AbortWithError(http.StatusUnauthorized, err) _ = c.AbortWithError(http.StatusUnauthorized, err)
return return

View file

@ -68,6 +68,13 @@ func GetOrgPermissions(c *gin.Context) {
user := session.User(c) user := session.User(c)
_store := store.FromContext(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) orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
if err != nil { if err != nil {
c.String(http.StatusBadRequest, "Error parsing org id. %s", err) c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
@ -96,7 +103,7 @@ func GetOrgPermissions(c *gin.Context) {
return 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 { if err != nil {
c.String(http.StatusInternalServerError, "Error getting membership for %d. %s", orgID, err) c.String(http.StatusInternalServerError, "Error getting membership for %d. %s", orgID, err)
return return
@ -116,6 +123,13 @@ func GetOrgPermissions(c *gin.Context) {
// @Param org_full_name path string true "the organizations full-name / slug" // @Param org_full_name path string true "the organizations full-name / slug"
func LookupOrg(c *gin.Context) { func LookupOrg(c *gin.Context) {
_store := store.FromContext(c) _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"), "/") orgFullName := strings.TrimLeft(c.Param("org_full_name"), "/")
@ -137,7 +151,7 @@ func LookupOrg(c *gin.Context) {
c.AbortWithStatus(http.StatusNotFound) c.AbortWithStatus(http.StatusNotFound)
return return
} else if !user.Admin { } 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 { if err != nil {
log.Error().Err(err).Msg("failed to check membership") log.Error().Err(err).Msg("failed to check membership")
c.Status(http.StatusInternalServerError) c.Status(http.StatusInternalServerError)

View file

@ -27,6 +27,7 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
"go.woodpecker-ci.org/woodpecker/v2/server" "go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "go.woodpecker-ci.org/woodpecker/v2/server/model"
@ -48,10 +49,16 @@ import (
func CreatePipeline(c *gin.Context) { func CreatePipeline(c *gin.Context) {
_store := store.FromContext(c) _store := store.FromContext(c)
repo := session.Repo(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 // parse create options
var opts model.PipelineOptions var opts model.PipelineOptions
err := json.NewDecoder(c.Request.Body).Decode(&opts) err = json.NewDecoder(c.Request.Body).Decode(&opts)
if err != nil { if err != nil {
_ = c.AbortWithError(http.StatusBadRequest, err) _ = c.AbortWithError(http.StatusBadRequest, err)
return return
@ -59,7 +66,7 @@ func CreatePipeline(c *gin.Context) {
user := session.User(c) 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) tmpPipeline := createTmpPipeline(model.EventManual, lastCommit, user, &opts)
@ -332,6 +339,13 @@ func CancelPipeline(c *gin.Context) {
_store := store.FromContext(c) _store := store.FromContext(c)
repo := session.Repo(c) repo := session.Repo(c)
user := session.User(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) num, _ := strconv.ParseInt(c.Params.ByName("number"), 10, 64)
pl, err := _store.GetPipelineNumber(repo, num) pl, err := _store.GetPipelineNumber(repo, num)
@ -340,7 +354,7 @@ func CancelPipeline(c *gin.Context) {
return 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) handlePipelineErr(c, err)
} else { } else {
c.Status(http.StatusNoContent) c.Status(http.StatusNoContent)

View file

@ -45,9 +45,14 @@ import (
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>) // @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" // @Param forge_remote_id query string true "the id of a repository at the forge"
func PostRepo(c *gin.Context) { func PostRepo(c *gin.Context) {
forge := server.Config.Services.Forge
_store := store.FromContext(c) _store := store.FromContext(c)
user := session.User(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")) forgeRemoteID := model.ForgeRemoteID(c.Query("forge_remote_id"))
if !forgeRemoteID.IsValid() { if !forgeRemoteID.IsValid() {
@ -67,7 +72,7 @@ func PostRepo(c *gin.Context) {
return return
} }
from, err := forge.Repo(c, user, forgeRemoteID, "", "") from, err := _forge.Repo(c, user, forgeRemoteID, "", "")
if err != nil { if err != nil {
c.String(http.StatusInternalServerError, "Could not fetch repository from forge.") c.String(http.StatusInternalServerError, "Could not fetch repository from forge.")
return return
@ -138,7 +143,7 @@ func PostRepo(c *gin.Context) {
// create an org if it doesn't exist yet // create an org if it doesn't exist yet
if errors.Is(err, types.RecordNotExist) { 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 { if err != nil {
msg := "could not fetch organization from forge." msg := "could not fetch organization from forge."
log.Error().Err(err).Msg(msg) log.Error().Err(err).Msg(msg)
@ -146,6 +151,7 @@ func PostRepo(c *gin.Context) {
return return
} }
org.ForgeID = user.ForgeID
err = _store.OrgCreate(org) err = _store.OrgCreate(org)
if err != nil { if err != nil {
msg := "could not create organization in store." msg := "could not create organization in store."
@ -157,7 +163,7 @@ func PostRepo(c *gin.Context) {
repo.OrgID = org.ID repo.OrgID = org.ID
err = forge.Activate(c, user, repo, hookURL) err = _forge.Activate(c, user, repo, hookURL)
if err != nil { if err != nil {
msg := "could not create webhook in forge." msg := "could not create webhook in forge."
log.Error().Err(err).Msg(msg) log.Error().Err(err).Msg(msg)
@ -168,6 +174,7 @@ func PostRepo(c *gin.Context) {
if enabledOnce { if enabledOnce {
err = _store.UpdateRepo(repo) err = _store.UpdateRepo(repo)
} else { } else {
repo.ForgeID = user.ForgeID // TODO: allow to use other connected forges of the user
err = _store.CreateRepo(repo) err = _store.CreateRepo(repo)
} }
if err != nil { if err != nil {
@ -342,9 +349,14 @@ func GetRepoPermissions(c *gin.Context) {
func GetRepoBranches(c *gin.Context) { func GetRepoBranches(c *gin.Context) {
repo := session.Repo(c) repo := session.Repo(c)
user := session.User(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 { if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err) _ = c.AbortWithError(http.StatusInternalServerError, err)
return return
@ -367,9 +379,14 @@ func GetRepoBranches(c *gin.Context) {
func GetRepoPullRequests(c *gin.Context) { func GetRepoPullRequests(c *gin.Context) {
repo := session.Repo(c) repo := session.Repo(c)
user := session.User(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 { if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err) _ = c.AbortWithError(http.StatusInternalServerError, err)
return return
@ -390,9 +407,14 @@ func GetRepoPullRequests(c *gin.Context) {
func DeleteRepo(c *gin.Context) { func DeleteRepo(c *gin.Context) {
remove, _ := strconv.ParseBool(c.Query("remove")) remove, _ := strconv.ParseBool(c.Query("remove"))
_store := store.FromContext(c) _store := store.FromContext(c)
repo := session.Repo(c) repo := session.Repo(c)
user := session.User(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.IsActive = false
repo.UserID = 0 repo.UserID = 0
@ -409,7 +431,7 @@ func DeleteRepo(c *gin.Context) {
} }
} }
if err := server.Config.Services.Forge.Deactivate(c, user, repo, server.Config.Server.WebhookHost); err != nil { if err := _forge.Deactivate(c, user, repo, server.Config.Server.WebhookHost); err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err) _ = c.AbortWithError(http.StatusInternalServerError, err)
return return
} }
@ -445,10 +467,15 @@ func RepairRepo(c *gin.Context) {
// @Param repo_id path int true "the repository id" // @Param repo_id path int true "the repository id"
// @Param to query string true "the username to move the repository to" // @Param to query string true "the username to move the repository to"
func MoveRepo(c *gin.Context) { func MoveRepo(c *gin.Context) {
forge := server.Config.Services.Forge
_store := store.FromContext(c) _store := store.FromContext(c)
repo := session.Repo(c) repo := session.Repo(c)
user := session.User(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") to, exists := c.GetQuery("to")
if !exists { if !exists {
@ -463,7 +490,7 @@ func MoveRepo(c *gin.Context) {
return return
} }
from, err := forge.Repo(c, user, "", owner, name) from, err := _forge.Repo(c, user, "", owner, name)
if err != nil { if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err) _ = c.AbortWithError(http.StatusInternalServerError, err)
return return
@ -508,10 +535,10 @@ func MoveRepo(c *gin.Context) {
sig, 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) 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()) c.String(http.StatusInternalServerError, err.Error())
return return
} }
@ -571,8 +598,13 @@ func RepairAllRepos(c *gin.Context) {
} }
func repairRepo(c *gin.Context, repo *model.Repo, withPerms, skipOnErr bool) { func repairRepo(c *gin.Context, repo *model.Repo, withPerms, skipOnErr bool) {
forge := server.Config.Services.Forge
_store := store.FromContext(c) _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) user, err := _store.GetUser(repo.UserID)
if err != nil { if err != nil {
@ -603,7 +635,7 @@ func repairRepo(c *gin.Context, repo *model.Repo, withPerms, skipOnErr bool) {
sig, 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 { if err != nil {
log.Error().Err(err).Msgf("get repo '%s/%s' from forge", repo.Owner, repo.Name) log.Error().Err(err).Msgf("get repo '%s/%s' from forge", repo.Owner, repo.Name)
if !skipOnErr { if !skipOnErr {
@ -636,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) 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()) c.String(http.StatusInternalServerError, err.Error())
return return
} }

View file

@ -21,6 +21,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"github.com/rs/zerolog/log"
"go.woodpecker-ci.org/woodpecker/v2/server" "go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "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" // @Param all query bool false "query all repos, including inactive ones"
func GetRepos(c *gin.Context) { func GetRepos(c *gin.Context) {
_store := store.FromContext(c) _store := store.FromContext(c)
_forge := server.Config.Services.Forge
user := session.User(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
}
all, _ := strconv.ParseBool(c.Query("all")) all, _ := strconv.ParseBool(c.Query("all"))
if all { if all {

View file

@ -132,6 +132,8 @@ func PostUser(c *gin.Context) {
Hash: base32.StdEncoding.EncodeToString( Hash: base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32), 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 { if err = user.Validate(); err != nil {
c.String(http.StatusBadRequest, err.Error()) c.String(http.StatusBadRequest, err.Error())

View file

@ -23,39 +23,39 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/server/forge" "go.woodpecker-ci.org/woodpecker/v2/server/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "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. // MembershipService is a service to check for user membership.
type MembershipService interface { type MembershipService interface {
// Get returns if the user is a member of the organization. // 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 { type membershipCache struct {
forge forge.Forge
cache *ttlcache.Cache[string, *model.OrgPerm] cache *ttlcache.Cache[string, *model.OrgPerm]
store store.Store
ttl time.Duration ttl time.Duration
} }
// NewMembershipService creates a new membership service. // NewMembershipService creates a new membership service.
func NewMembershipService(f forge.Forge) MembershipService { func NewMembershipService(_store store.Store) MembershipService {
//nolint:gomnd
return &membershipCache{ return &membershipCache{
ttl: 10 * time.Minute, ttl: 10 * time.Minute, //nolint: gomnd
forge: f, store: _store,
cache: ttlcache.New(ttlcache.WithDisableTouchOnHit[string, *model.OrgPerm]()), cache: ttlcache.New(ttlcache.WithDisableTouchOnHit[string, *model.OrgPerm]()),
} }
} }
// Get returns if the user is a member of the organization. // 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) key := fmt.Sprintf("%s-%s", u.ForgeRemoteID, org)
item := c.cache.Get(key) item := c.cache.Get(key)
if item != nil && !item.IsExpired() { if item != nil && !item.IsExpired() {
return item.Value(), nil return item.Value(), nil
} }
perm, err := c.forge.OrgMembership(ctx, u, org) perm, err := _forge.OrgMembership(ctx, u, org)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -21,7 +21,6 @@ import (
"time" "time"
"go.woodpecker-ci.org/woodpecker/v2/server/cache" "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/logging"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/pubsub" "go.woodpecker-ci.org/woodpecker/v2/server/pubsub"
@ -35,9 +34,8 @@ var Config = struct {
Pubsub *pubsub.Publisher Pubsub *pubsub.Publisher
Queue queue.Queue Queue queue.Queue
Logs logging.Log Logs logging.Log
Forge forge.Forge
Membership cache.MembershipService Membership cache.MembershipService
Manager *services.Manager Manager services.Manager
} }
Server struct { Server struct {
Key string Key string

View file

@ -22,6 +22,7 @@ import (
"github.com/robfig/cron" "github.com/robfig/cron"
"github.com/rs/zerolog/log" "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/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/pipeline" "go.woodpecker-ci.org/woodpecker/v2/server/pipeline"
@ -37,7 +38,7 @@ const (
) )
// Start starts the cron scheduler loop // 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 { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
@ -54,7 +55,7 @@ func Start(ctx context.Context, store store.Store, forge forge.Forge) error {
} }
for _, cron := range crons { for _, cron := range crons {
if err := runCron(ctx, store, forge, cron, now); err != nil { if err := runCron(ctx, store, cron, now); err != nil {
log.Error().Err(err).Int64("cronID", cron.ID).Msg("run cron failed") log.Error().Err(err).Int64("cronID", cron.ID).Msg("run cron failed")
} }
} }
@ -77,7 +78,7 @@ func CalcNewNext(schedule string, now time.Time) (time.Time, error) {
return c.Next(now), nil return c.Next(now), nil
} }
func runCron(ctx context.Context, store store.Store, forge forge.Forge, cron *model.Cron, now time.Time) error { func runCron(ctx context.Context, store store.Store, cron *model.Cron, now time.Time) error {
log.Trace().Msgf("cron: run id[%d]", cron.ID) log.Trace().Msgf("cron: run id[%d]", cron.ID)
newNext, err := CalcNewNext(cron.Schedule, now) newNext, err := CalcNewNext(cron.Schedule, now)
@ -95,7 +96,7 @@ func runCron(ctx context.Context, store store.Store, forge forge.Forge, cron *mo
return nil return nil
} }
repo, newPipeline, err := CreatePipeline(ctx, store, forge, cron) repo, newPipeline, err := CreatePipeline(ctx, store, cron)
if err != nil { if err != nil {
return err return err
} }
@ -104,12 +105,17 @@ func runCron(ctx context.Context, store store.Store, forge forge.Forge, cron *mo
return err 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) repo, err := store.GetRepo(cron.RepoID)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
if err != nil {
return nil, nil, err
}
if cron.Branch == "" { if cron.Branch == "" {
// fallback to the repos default branch // fallback to the repos default branch
cron.Branch = repo.Branch cron.Branch = repo.Branch
@ -123,9 +129,9 @@ func CreatePipeline(ctx context.Context, store store.Store, f forge.Forge, cron
// If the forge has a refresh token, the current access token // If the forge has a refresh token, the current access token
// may be stale. Therefore, we should refresh prior to dispatching // may be stale. Therefore, we should refresh prior to dispatching
// the pipeline. // 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }

View file

@ -22,13 +22,16 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"go.woodpecker-ci.org/woodpecker/v2/server"
mocks_forge "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" mocks_forge "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "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" mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks"
) )
func TestCreateBuild(t *testing.T) { func TestCreatePipeline(t *testing.T) {
forge := mocks_forge.NewForge(t) _manager := mocks_manager.NewManager(t)
_forge := mocks_forge.NewForge(t)
store := mocks_store.NewStore(t) store := mocks_store.NewStore(t)
ctx := context.Background() ctx := context.Background()
@ -47,12 +50,14 @@ func TestCreateBuild(t *testing.T) {
// mock things // mock things
store.On("GetRepo", mock.Anything).Return(repo1, nil) store.On("GetRepo", mock.Anything).Return(repo1, nil)
store.On("GetUser", mock.Anything).Return(creator, 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", ForgeURL: "https://example.com/sha1",
SHA: "sha1", SHA: "sha1",
}, nil) }, 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", Name: "test",
}) })
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -15,7 +15,6 @@
package forge package forge
//go:generate go install github.com/vektra/mockery/v2@latest
//go:generate mockery --name Forge --output mocks --case underscore //go:generate mockery --name Forge --output mocks --case underscore
import ( import (

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT. // Code generated by mockery. DO NOT EDIT.
package mocks package mocks

134
server/forge/setup/setup.go Normal file
View 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)
}

View file

@ -31,6 +31,7 @@ import (
grpcMetadata "google.golang.org/grpc/metadata" grpcMetadata "google.golang.org/grpc/metadata"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" "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/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/logging" "go.woodpecker-ci.org/woodpecker/v2/server/logging"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "go.woodpecker-ci.org/woodpecker/v2/server/model"
@ -41,7 +42,6 @@ import (
) )
type RPC struct { type RPC struct {
forge forge.Forge
queue queue.Queue queue queue.Queue
pubsub *pubsub.Publisher pubsub *pubsub.Publisher
logger logging.Log logger logging.Log
@ -418,11 +418,17 @@ func (s *RPC) updateForgeStatus(ctx context.Context, repo *model.Repo, pipeline
return 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 // only do status updates for parent steps
if workflow != nil { if workflow != nil {
err = s.forge.Status(ctx, user, repo, pipeline, workflow) err = _forge.Status(ctx, user, repo, pipeline, workflow)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("error setting commit status for %s/%d", repo.FullName, pipeline.Number) log.Error().Err(err).Msgf("error setting commit status for %s/%d", repo.FullName, pipeline.Number)
} }

View file

@ -23,7 +23,6 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto" "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/logging"
"go.woodpecker-ci.org/woodpecker/v2/server/pubsub" "go.woodpecker-ci.org/woodpecker/v2/server/pubsub"
"go.woodpecker-ci.org/woodpecker/v2/server/queue" "go.woodpecker-ci.org/woodpecker/v2/server/queue"
@ -37,7 +36,7 @@ type WoodpeckerServer struct {
peer RPC 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{ pipelineTime := promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "woodpecker", Namespace: "woodpecker",
Name: "pipeline_time", Name: "pipeline_time",
@ -49,7 +48,6 @@ func NewWoodpeckerServer(forge forge.Forge, queue queue.Queue, logger logging.Lo
Help: "Pipeline count.", Help: "Pipeline count.",
}, []string{"repo", "branch", "status", "pipeline"}) }, []string{"repo", "branch", "status", "pipeline"})
peer := RPC{ peer := RPC{
forge: forge,
store: store, store: store,
queue: queue, queue: queue,
pubsub: pubsub, pubsub: pubsub,

36
server/model/forge.go Normal file
View 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"`
}

View file

@ -16,11 +16,12 @@ package model
// Org represents an organization. // Org represents an organization.
type Org struct { type Org struct {
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'id'"` ID int64 `json:"id,omitempty" xorm:"pk autoincr 'id'"`
Name string `json:"name" xorm:"UNIQUE 'name'"` ForgeID int64 `json:"forge_id,omitempty" xorm:"forge_id"`
IsUser bool `json:"is_user" xorm:"is_user"` 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 // if name lookup has to check for membership or not
Private bool `json:"-" xorm:"private"` Private bool `json:"-" xorm:"private"`
} // @name Org } // @name Org
// TableName return database table name for xorm // TableName return database table name for xorm

View file

@ -22,8 +22,9 @@ import (
// Repo represents a repository. // Repo represents a repository.
type Repo struct { type Repo struct {
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'repo_id'"` ID int64 `json:"id,omitempty" xorm:"pk autoincr 'repo_id'"`
UserID int64 `json:"-" xorm:"repo_user_id"` UserID int64 `json:"-" xorm:"repo_user_id"`
ForgeID int64 `json:"forge_id,omitempty" xorm:"forge_id"`
// ForgeRemoteID is the unique identifier for the repository on the forge. // ForgeRemoteID is the unique identifier for the repository on the forge.
ForgeRemoteID ForgeRemoteID `json:"forge_remote_id" xorm:"forge_remote_id"` ForgeRemoteID ForgeRemoteID `json:"forge_remote_id" xorm:"forge_remote_id"`
OrgID int64 `json:"org_id" xorm:"repo_org_id"` OrgID int64 `json:"org_id" xorm:"repo_org_id"`

View file

@ -34,6 +34,8 @@ type User struct {
// required: true // required: true
ID int64 `json:"id" xorm:"pk autoincr 'user_id'"` 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"` ForgeRemoteID ForgeRemoteID `json:"-" xorm:"forge_remote_id"`
// Login is the username for this user. // Login is the username for this user.

View file

@ -20,6 +20,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"go.woodpecker-ci.org/woodpecker/v2/server"
forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/store" "go.woodpecker-ci.org/woodpecker/v2/server/store"
@ -32,6 +33,13 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe
return nil, ErrBadRequest{Msg: fmt.Sprintf("cannot approve a pipeline with status %s", currentPipeline.Status)} return nil, ErrBadRequest{Msg: fmt.Sprintf("cannot approve a pipeline with status %s", currentPipeline.Status)}
} }
forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
if err != nil {
msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName)
log.Error().Err(err).Str("repo", repo.FullName).Msg(msg)
return nil, fmt.Errorf(msg)
}
// fetch the pipeline file from the database // fetch the pipeline file from the database
configs, err := store.ConfigsForPipeline(currentPipeline.ID) configs, err := store.ConfigsForPipeline(currentPipeline.ID)
if err != nil { if err != nil {
@ -72,7 +80,7 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe
} }
} }
currentPipeline, pipelineItems, err := createPipelineItems(ctx, store, currentPipeline, user, repo, yamls, nil) currentPipeline, pipelineItems, err := createPipelineItems(ctx, forge, store, currentPipeline, user, repo, yamls, nil)
if err != nil { if err != nil {
msg := fmt.Sprintf("failure to createPipelineItems for %s", repo.FullName) msg := fmt.Sprintf("failure to createPipelineItems for %s", repo.FullName)
log.Error().Err(err).Msg(msg) log.Error().Err(err).Msg(msg)
@ -86,9 +94,9 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe
return nil, err return nil, err
} }
publishPipeline(ctx, currentPipeline, repo, user) publishPipeline(ctx, forge, currentPipeline, repo, user)
currentPipeline, err = start(ctx, store, currentPipeline, user, repo, pipelineItems) currentPipeline, err = start(ctx, forge, store, currentPipeline, user, repo, pipelineItems)
if err != nil { if err != nil {
msg := fmt.Sprintf("failure to start pipeline for %s: %v", repo.FullName, err) msg := fmt.Sprintf("failure to start pipeline for %s: %v", repo.FullName, err)
log.Error().Err(err).Msg(msg) log.Error().Err(err).Msg(msg)

View file

@ -21,13 +21,14 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"go.woodpecker-ci.org/woodpecker/v2/server" "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/model"
"go.woodpecker-ci.org/woodpecker/v2/server/queue" "go.woodpecker-ci.org/woodpecker/v2/server/queue"
"go.woodpecker-ci.org/woodpecker/v2/server/store" "go.woodpecker-ci.org/woodpecker/v2/server/store"
) )
// Cancel the pipeline and returns the status. // Cancel the pipeline and returns the status.
func Cancel(ctx context.Context, store store.Store, repo *model.Repo, user *model.User, pipeline *model.Pipeline) error { func Cancel(ctx context.Context, _forge forge.Forge, store store.Store, repo *model.Repo, user *model.User, pipeline *model.Pipeline) error {
if pipeline.Status != model.StatusRunning && pipeline.Status != model.StatusPending && pipeline.Status != model.StatusBlocked { if pipeline.Status != model.StatusRunning && pipeline.Status != model.StatusPending && pipeline.Status != model.StatusBlocked {
return &ErrBadRequest{Msg: "Cannot cancel a non-running or non-pending or non-blocked pipeline"} return &ErrBadRequest{Msg: "Cannot cancel a non-running or non-pending or non-blocked pipeline"}
} }
@ -88,7 +89,7 @@ func Cancel(ctx context.Context, store store.Store, repo *model.Repo, user *mode
return err return err
} }
updatePipelineStatus(ctx, killedPipeline, repo, user) updatePipelineStatus(ctx, _forge, killedPipeline, repo, user)
if killedPipeline.Workflows, err = store.WorkflowGetTree(killedPipeline); err != nil { if killedPipeline.Workflows, err = store.WorkflowGetTree(killedPipeline); err != nil {
return err return err
@ -100,6 +101,7 @@ func Cancel(ctx context.Context, store store.Store, repo *model.Repo, user *mode
func cancelPreviousPipelines( func cancelPreviousPipelines(
ctx context.Context, ctx context.Context,
_forge forge.Forge,
_store store.Store, _store store.Store,
pipeline *model.Pipeline, pipeline *model.Pipeline,
repo *model.Repo, repo *model.Repo,
@ -150,7 +152,7 @@ func cancelPreviousPipelines(
continue continue
} }
if err = Cancel(ctx, _store, repo, user, active); err != nil { if err = Cancel(ctx, _forge, _store, repo, user, active); err != nil {
log.Error(). log.Error().
Err(err). Err(err).
Str("ref", active.Ref). Str("ref", active.Ref).

View file

@ -34,7 +34,6 @@ var skipPipelineRegex = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
// Create a new pipeline and start it // Create a new pipeline and start it
func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline *model.Pipeline) (*model.Pipeline, error) { func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline *model.Pipeline) (*model.Pipeline, error) {
_forge := server.Config.Services.Forge
repoUser, err := _store.GetUser(repo.UserID) repoUser, err := _store.GetUser(repo.UserID)
if err != nil { if err != nil {
msg := fmt.Sprintf("failure to find repo owner via id '%d'", repo.UserID) msg := fmt.Sprintf("failure to find repo owner via id '%d'", repo.UserID)
@ -54,6 +53,13 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
} }
} }
_forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
if err != nil {
msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName)
log.Error().Err(err).Str("repo", repo.FullName).Msg(msg)
return nil, fmt.Errorf(msg)
}
// If the forge has a refresh token, the current access token // If the forge has a refresh token, the current access token
// may be stale. Therefore, we should refresh prior to dispatching // may be stale. Therefore, we should refresh prior to dispatching
// the pipeline. // the pipeline.
@ -82,13 +88,13 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
return nil, ErrFiltered return nil, ErrFiltered
} else if configFetchErr != nil { } else if configFetchErr != nil {
log.Debug().Str("repo", repo.FullName).Err(configFetchErr).Msgf("error while fetching config '%s' in '%s' with user: '%s'", repo.Config, pipeline.Ref, repoUser.Login) log.Debug().Str("repo", repo.FullName).Err(configFetchErr).Msgf("error while fetching config '%s' in '%s' with user: '%s'", repo.Config, pipeline.Ref, repoUser.Login)
return nil, updatePipelineWithErr(ctx, _store, pipeline, repo, repoUser, fmt.Errorf("pipeline definition not found in %s", repo.FullName)) return nil, updatePipelineWithErr(ctx, _forge, _store, pipeline, repo, repoUser, fmt.Errorf("pipeline definition not found in %s", repo.FullName))
} }
pipelineItems, parseErr := parsePipeline(_store, pipeline, repoUser, repo, forgeYamlConfigs, nil) pipelineItems, parseErr := parsePipeline(_forge, _store, pipeline, repoUser, repo, forgeYamlConfigs, nil)
if pipeline_errors.HasBlockingErrors(parseErr) { if pipeline_errors.HasBlockingErrors(parseErr) {
log.Debug().Str("repo", repo.FullName).Err(parseErr).Msg("failed to parse yaml") log.Debug().Str("repo", repo.FullName).Err(parseErr).Msg("failed to parse yaml")
return nil, updatePipelineWithErr(ctx, _store, pipeline, repo, repoUser, parseErr) return nil, updatePipelineWithErr(ctx, _forge, _store, pipeline, repo, repoUser, parseErr)
} else if parseErr != nil { } else if parseErr != nil {
pipeline.Errors = pipeline_errors.GetPipelineErrors(parseErr) pipeline.Errors = pipeline_errors.GetPipelineErrors(parseErr)
} }
@ -122,7 +128,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
return nil, fmt.Errorf(msg) return nil, fmt.Errorf(msg)
} }
if err := prepareStart(ctx, _store, pipeline, repoUser, repo); err != nil { if err := prepareStart(ctx, _forge, _store, pipeline, repoUser, repo); err != nil {
log.Error().Err(err).Str("repo", repo.FullName).Msgf("error preparing pipeline for %s#%d", repo.FullName, pipeline.Number) log.Error().Err(err).Str("repo", repo.FullName).Msgf("error preparing pipeline for %s#%d", repo.FullName, pipeline.Number)
return nil, err return nil, err
} }
@ -131,11 +137,11 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
return pipeline, nil return pipeline, nil
} }
if err := updatePipelinePending(ctx, _store, pipeline, repo, repoUser); err != nil { if err := updatePipelinePending(ctx, _forge, _store, pipeline, repo, repoUser); err != nil {
return nil, err return nil, err
} }
pipeline, err = start(ctx, _store, pipeline, repoUser, repo, pipelineItems) pipeline, err = start(ctx, _forge, _store, pipeline, repoUser, repo, pipelineItems)
if err != nil { if err != nil {
msg := fmt.Sprintf("failed to start pipeline for %s", repo.FullName) msg := fmt.Sprintf("failed to start pipeline for %s", repo.FullName)
log.Error().Err(err).Msg(msg) log.Error().Err(err).Msg(msg)
@ -145,7 +151,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
return pipeline, nil return pipeline, nil
} }
func updatePipelineWithErr(ctx context.Context, _store store.Store, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User, err error) error { func updatePipelineWithErr(ctx context.Context, _forge forge.Forge, _store store.Store, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User, err error) error {
_pipeline, err := UpdateToStatusError(_store, *pipeline, err) _pipeline, err := UpdateToStatusError(_store, *pipeline, err)
if err != nil { if err != nil {
return err return err
@ -153,12 +159,12 @@ func updatePipelineWithErr(ctx context.Context, _store store.Store, pipeline *mo
// update value in ref // update value in ref
*pipeline = *_pipeline *pipeline = *_pipeline
publishPipeline(ctx, pipeline, repo, repoUser) publishPipeline(ctx, _forge, pipeline, repo, repoUser)
return nil return nil
} }
func updatePipelinePending(ctx context.Context, _store store.Store, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User) error { func updatePipelinePending(ctx context.Context, _forge forge.Forge, _store store.Store, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User) error {
_pipeline, err := UpdateToStatusPending(_store, *pipeline, "") _pipeline, err := UpdateToStatusPending(_store, *pipeline, "")
if err != nil { if err != nil {
return err return err
@ -166,7 +172,7 @@ func updatePipelinePending(ctx context.Context, _store store.Store, pipeline *mo
// update value in ref // update value in ref
*pipeline = *_pipeline *pipeline = *_pipeline
publishPipeline(ctx, pipeline, repo, repoUser) publishPipeline(ctx, _forge, pipeline, repo, repoUser)
return nil return nil
} }

View file

@ -20,17 +20,25 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/store" "go.woodpecker-ci.org/woodpecker/v2/server/store"
) )
// Decline updates the status to declined for blocked pipelines because of a gated repo // Decline updates the status to declined for blocked pipelines because of a gated repo
func Decline(ctx context.Context, store store.Store, pipeline *model.Pipeline, user *model.User, repo *model.Repo) (*model.Pipeline, error) { func Decline(ctx context.Context, store store.Store, pipeline *model.Pipeline, user *model.User, repo *model.Repo) (*model.Pipeline, error) {
forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
if err != nil {
msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName)
log.Error().Err(err).Str("repo", repo.FullName).Msg(msg)
return nil, fmt.Errorf(msg)
}
if pipeline.Status != model.StatusBlocked { if pipeline.Status != model.StatusBlocked {
return nil, fmt.Errorf("cannot decline a pipeline with status %s", pipeline.Status) return nil, fmt.Errorf("cannot decline a pipeline with status %s", pipeline.Status)
} }
pipeline, err := UpdateToStatusDeclined(store, *pipeline, user.Login) pipeline, err = UpdateToStatusDeclined(store, *pipeline, user.Login)
if err != nil { if err != nil {
return nil, fmt.Errorf("error updating pipeline. %w", err) return nil, fmt.Errorf("error updating pipeline. %w", err)
} }
@ -53,7 +61,7 @@ func Decline(ctx context.Context, store store.Store, pipeline *model.Pipeline, u
} }
} }
updatePipelineStatus(ctx, pipeline, repo, user) updatePipelineStatus(ctx, forge, pipeline, repo, user)
publishToTopic(pipeline, repo) publishToTopic(pipeline, repo)

View file

@ -19,13 +19,13 @@ import (
"github.com/rs/zerolog/log" "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/model"
) )
func updatePipelineStatus(ctx context.Context, pipeline *model.Pipeline, repo *model.Repo, user *model.User) { func updatePipelineStatus(ctx context.Context, forge forge.Forge, pipeline *model.Pipeline, repo *model.Repo, user *model.User) {
for _, workflow := range pipeline.Workflows { for _, workflow := range pipeline.Workflows {
err := server.Config.Services.Forge.Status(ctx, user, repo, pipeline, workflow) err := forge.Status(ctx, user, repo, pipeline, workflow)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("error setting commit status for %s/%d", repo.FullName, pipeline.Number) log.Error().Err(err).Msgf("error setting commit status for %s/%d", repo.FullName, pipeline.Number)
return return

View file

@ -24,14 +24,15 @@ import (
pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler"
"go.woodpecker-ci.org/woodpecker/v2/server" "go.woodpecker-ci.org/woodpecker/v2/server"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder" "go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder"
"go.woodpecker-ci.org/woodpecker/v2/server/store" "go.woodpecker-ci.org/woodpecker/v2/server/store"
) )
func parsePipeline(store store.Store, currentPipeline *model.Pipeline, user *model.User, repo *model.Repo, yamls []*forge_types.FileMeta, envs map[string]string) ([]*stepbuilder.Item, error) { func parsePipeline(forge forge.Forge, store store.Store, currentPipeline *model.Pipeline, user *model.User, repo *model.Repo, yamls []*forge_types.FileMeta, envs map[string]string) ([]*stepbuilder.Item, error) {
netrc, err := server.Config.Services.Forge.Netrc(user, repo) netrc, err := forge.Netrc(user, repo)
if err != nil { if err != nil {
log.Error().Err(err).Msg("failed to generate netrc file") log.Error().Err(err).Msg("failed to generate netrc file")
} }
@ -80,7 +81,7 @@ func parsePipeline(store store.Store, currentPipeline *model.Pipeline, user *mod
Envs: envs, Envs: envs,
Host: server.Config.Server.Host, Host: server.Config.Server.Host,
Yamls: yamls, Yamls: yamls,
Forge: server.Config.Services.Forge, Forge: forge,
ProxyOpts: compiler.ProxyOptions{ ProxyOpts: compiler.ProxyOptions{
NoProxy: server.Config.Pipeline.Proxy.No, NoProxy: server.Config.Pipeline.Proxy.No,
HTTPProxy: server.Config.Pipeline.Proxy.HTTP, HTTPProxy: server.Config.Pipeline.Proxy.HTTP,
@ -90,23 +91,23 @@ func parsePipeline(store store.Store, currentPipeline *model.Pipeline, user *mod
return b.Build() return b.Build()
} }
func createPipelineItems(c context.Context, store store.Store, func createPipelineItems(c context.Context, forge forge.Forge, store store.Store,
currentPipeline *model.Pipeline, user *model.User, repo *model.Repo, currentPipeline *model.Pipeline, user *model.User, repo *model.Repo,
yamls []*forge_types.FileMeta, envs map[string]string, yamls []*forge_types.FileMeta, envs map[string]string,
) (*model.Pipeline, []*stepbuilder.Item, error) { ) (*model.Pipeline, []*stepbuilder.Item, error) {
pipelineItems, err := parsePipeline(store, currentPipeline, user, repo, yamls, envs) pipelineItems, err := parsePipeline(forge, store, currentPipeline, user, repo, yamls, envs)
if pipeline_errors.HasBlockingErrors(err) { if pipeline_errors.HasBlockingErrors(err) {
currentPipeline, uerr := UpdateToStatusError(store, *currentPipeline, err) currentPipeline, uerr := UpdateToStatusError(store, *currentPipeline, err)
if uerr != nil { if uerr != nil {
log.Error().Err(uerr).Msgf("error setting error status of pipeline for %s#%d", repo.FullName, currentPipeline.Number) log.Error().Err(uerr).Msgf("error setting error status of pipeline for %s#%d", repo.FullName, currentPipeline.Number)
} else { } else {
updatePipelineStatus(c, currentPipeline, repo, user) updatePipelineStatus(c, forge, currentPipeline, repo, user)
} }
return currentPipeline, nil, err return currentPipeline, nil, err
} else if err != nil { } else if err != nil {
currentPipeline.Errors = pipeline_errors.GetPipelineErrors(err) currentPipeline.Errors = pipeline_errors.GetPipelineErrors(err)
err = updatePipelinePending(c, store, currentPipeline, repo, user) err = updatePipelinePending(c, forge, store, currentPipeline, repo, user)
} }
currentPipeline = setPipelineStepsOnPipeline(currentPipeline, pipelineItems) currentPipeline = setPipelineStepsOnPipeline(currentPipeline, pipelineItems)

View file

@ -29,7 +29,13 @@ import (
// Restart a pipeline by creating a new one out of the old and start it // Restart a pipeline by creating a new one out of the old and start it
func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipeline, user *model.User, repo *model.Repo, envs map[string]string) (*model.Pipeline, error) { func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipeline, user *model.User, repo *model.Repo, envs map[string]string) (*model.Pipeline, error) {
forge := server.Config.Services.Forge forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
if err != nil {
msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName)
log.Error().Err(err).Str("repo", repo.FullName).Msg(msg)
return nil, fmt.Errorf(msg)
}
switch lastPipeline.Status { switch lastPipeline.Status {
case model.StatusDeclined, case model.StatusDeclined,
model.StatusBlocked: model.StatusBlocked:
@ -72,7 +78,7 @@ func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipelin
if uerr != nil { if uerr != nil {
log.Debug().Err(uerr).Msg("failure to update pipeline status") log.Debug().Err(uerr).Msg("failure to update pipeline status")
} else { } else {
updatePipelineStatus(ctx, newPipeline, repo, user) updatePipelineStatus(ctx, forge, newPipeline, repo, user)
} }
return newPipeline, nil return newPipeline, nil
} }
@ -82,20 +88,20 @@ func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipelin
return nil, fmt.Errorf(msg) return nil, fmt.Errorf(msg)
} }
newPipeline, pipelineItems, err := createPipelineItems(ctx, store, newPipeline, user, repo, pipelineFiles, envs) newPipeline, pipelineItems, err := createPipelineItems(ctx, forge, store, newPipeline, user, repo, pipelineFiles, envs)
if err != nil { if err != nil {
msg := fmt.Sprintf("failure to createPipelineItems for %s", repo.FullName) msg := fmt.Sprintf("failure to createPipelineItems for %s", repo.FullName)
log.Error().Err(err).Msg(msg) log.Error().Err(err).Msg(msg)
return nil, fmt.Errorf(msg) return nil, fmt.Errorf(msg)
} }
if err := prepareStart(ctx, store, newPipeline, user, repo); err != nil { if err := prepareStart(ctx, forge, store, newPipeline, user, repo); err != nil {
msg := fmt.Sprintf("failure to prepare pipeline for %s", repo.FullName) msg := fmt.Sprintf("failure to prepare pipeline for %s", repo.FullName)
log.Error().Err(err).Msg(msg) log.Error().Err(err).Msg(msg)
return nil, fmt.Errorf(msg) return nil, fmt.Errorf(msg)
} }
newPipeline, err = start(ctx, store, newPipeline, user, repo, pipelineItems) newPipeline, err = start(ctx, forge, store, newPipeline, user, repo, pipelineItems)
if err != nil { if err != nil {
msg := fmt.Sprintf("failure to start pipeline for %s", repo.FullName) msg := fmt.Sprintf("failure to start pipeline for %s", repo.FullName)
log.Error().Err(err).Msg(msg) log.Error().Err(err).Msg(msg)

View file

@ -19,20 +19,21 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder" "go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder"
"go.woodpecker-ci.org/woodpecker/v2/server/store" "go.woodpecker-ci.org/woodpecker/v2/server/store"
) )
// start a pipeline, make sure it was stored persistent in the store before // start a pipeline, make sure it was stored persistent in the store before
func start(ctx context.Context, store store.Store, activePipeline *model.Pipeline, user *model.User, repo *model.Repo, pipelineItems []*stepbuilder.Item) (*model.Pipeline, error) { func start(ctx context.Context, forge forge.Forge, store store.Store, activePipeline *model.Pipeline, user *model.User, repo *model.Repo, pipelineItems []*stepbuilder.Item) (*model.Pipeline, error) {
// call to cancel previous pipelines if needed // call to cancel previous pipelines if needed
if err := cancelPreviousPipelines(ctx, store, activePipeline, repo, user); err != nil { if err := cancelPreviousPipelines(ctx, forge, store, activePipeline, repo, user); err != nil {
// should be not breaking // should be not breaking
log.Error().Err(err).Msg("failed to cancel previous pipelines") log.Error().Err(err).Msg("failed to cancel previous pipelines")
} }
publishPipeline(ctx, activePipeline, repo, user) publishPipeline(ctx, forge, activePipeline, repo, user)
if err := queuePipeline(ctx, repo, pipelineItems); err != nil { if err := queuePipeline(ctx, repo, pipelineItems); err != nil {
log.Error().Err(err).Msg("queuePipeline") log.Error().Err(err).Msg("queuePipeline")
@ -42,17 +43,17 @@ func start(ctx context.Context, store store.Store, activePipeline *model.Pipelin
return activePipeline, nil return activePipeline, nil
} }
func prepareStart(ctx context.Context, store store.Store, activePipeline *model.Pipeline, user *model.User, repo *model.Repo) error { func prepareStart(ctx context.Context, forge forge.Forge, store store.Store, activePipeline *model.Pipeline, user *model.User, repo *model.Repo) error {
if err := store.WorkflowsCreate(activePipeline.Workflows); err != nil { if err := store.WorkflowsCreate(activePipeline.Workflows); err != nil {
log.Error().Err(err).Str("repo", repo.FullName).Msgf("error persisting steps for %s#%d", repo.FullName, activePipeline.Number) log.Error().Err(err).Str("repo", repo.FullName).Msgf("error persisting steps for %s#%d", repo.FullName, activePipeline.Number)
return err return err
} }
publishPipeline(ctx, activePipeline, repo, user) publishPipeline(ctx, forge, activePipeline, repo, user)
return nil return nil
} }
func publishPipeline(ctx context.Context, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User) { func publishPipeline(ctx context.Context, forge forge.Forge, pipeline *model.Pipeline, repo *model.Repo, repoUser *model.User) {
publishToTopic(pipeline, repo) publishToTopic(pipeline, repo)
updatePipelineStatus(ctx, pipeline, repo, repoUser) updatePipelineStatus(ctx, forge, pipeline, repo, repoUser)
} }

View file

@ -106,6 +106,13 @@ func SetPerm() gin.HandlerFunc {
_store := store.FromContext(c) _store := store.FromContext(c)
user := User(c) user := User(c)
repo := Repo(c) repo := 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
}
perm := new(model.Perm) perm := new(model.Perm)
if user != nil { if user != nil {
@ -116,7 +123,7 @@ func SetPerm() gin.HandlerFunc {
user.Login, repo.FullName) user.Login, repo.FullName)
} }
if time.Unix(perm.Synced, 0).Add(time.Hour).Before(time.Now()) { if time.Unix(perm.Synced, 0).Add(time.Hour).Before(time.Now()) {
_repo, err := server.Config.Services.Forge.Repo(c, user, repo.ForgeRemoteID, repo.Owner, repo.Name) _repo, err := _forge.Repo(c, user, repo.ForgeRemoteID, repo.Owner, repo.Name)
if err == nil { if err == nil {
log.Debug().Msgf("synced user permission for %s %s", user.Login, repo.FullName) log.Debug().Msgf("synced user permission for %s %s", user.Login, repo.FullName)
perm = _repo.Perm perm = _repo.Perm

View file

@ -145,7 +145,14 @@ func MustOrgMember(admin bool) gin.HandlerFunc {
return return
} }
perm, err := server.Config.Services.Membership.Get(c, user, org.Name) _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
}
perm, err := server.Config.Services.Membership.Get(c, _forge, user, org.Name)
if err != nil { if err != nil {
log.Error().Err(err).Msg("failed to check membership") log.Error().Err(err).Msg("failed to check membership")
c.String(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) c.String(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))

View file

@ -15,6 +15,8 @@
package token package token
import ( import (
"net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"go.woodpecker-ci.org/woodpecker/v2/server" "go.woodpecker-ci.org/woodpecker/v2/server"
@ -26,7 +28,13 @@ import (
func Refresh(c *gin.Context) { func Refresh(c *gin.Context) {
user := session.User(c) user := session.User(c)
if user != nil { if user != nil {
forge.Refresh(c, server.Config.Services.Forge, store.FromContext(c), user) _forge, err := server.Config.Services.Manager.ForgeFromUser(user)
if err != nil {
_ = c.AbortWithError(http.StatusInternalServerError, err)
return
}
forge.Refresh(c, _forge, store.FromContext(c), user)
} }
c.Next() c.Next()

View file

@ -16,9 +16,12 @@ package services
import ( import (
"crypto" "crypto"
"time"
"github.com/jellydator/ttlcache/v3"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/model" "go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/services/config" "go.woodpecker-ci.org/woodpecker/v2/server/services/config"
"go.woodpecker-ci.org/woodpecker/v2/server/services/environment" "go.woodpecker-ci.org/woodpecker/v2/server/services/environment"
@ -27,56 +30,119 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/server/store" "go.woodpecker-ci.org/woodpecker/v2/server/store"
) )
type Manager struct { //go:generate mockery --name Manager --output mocks --case underscore
const forgeCacheTTL = 10 * time.Minute
type SetupForge func(forge *model.Forge) (forge.Forge, error)
type Manager interface {
SignaturePublicKey() crypto.PublicKey
SecretServiceFromRepo(repo *model.Repo) secret.Service
SecretService() secret.Service
RegistryServiceFromRepo(repo *model.Repo) registry.Service
RegistryService() registry.Service
ConfigServiceFromRepo(repo *model.Repo) config.Service
EnvironmentService() environment.Service
ForgeFromRepo(repo *model.Repo) (forge.Forge, error)
ForgeFromUser(user *model.User) (forge.Forge, error)
ForgeMain() (forge.Forge, error)
}
type manager struct {
signaturePrivateKey crypto.PrivateKey
signaturePublicKey crypto.PublicKey
store store.Store
secret secret.Service secret secret.Service
registry registry.Service registry registry.Service
config config.Service config config.Service
environment environment.Service environment environment.Service
signaturePrivateKey crypto.PrivateKey forgeCache *ttlcache.Cache[int64, forge.Forge]
signaturePublicKey crypto.PublicKey setupForge SetupForge
} }
func NewManager(c *cli.Context, store store.Store) (*Manager, error) { func NewManager(c *cli.Context, store store.Store, setupForge SetupForge) (Manager, error) {
signaturePrivateKey, signaturePublicKey, err := setupSignatureKeys(store) signaturePrivateKey, signaturePublicKey, err := setupSignatureKeys(store)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Manager{ err = setupForgeService(c, store)
if err != nil {
return nil, err
}
return &manager{
signaturePrivateKey: signaturePrivateKey, signaturePrivateKey: signaturePrivateKey,
signaturePublicKey: signaturePublicKey, signaturePublicKey: signaturePublicKey,
store: store,
secret: setupSecretService(store), secret: setupSecretService(store),
registry: setupRegistryService(store, c.String("docker-config")), registry: setupRegistryService(store, c.String("docker-config")),
config: setupConfigService(c, signaturePrivateKey), config: setupConfigService(c, signaturePrivateKey),
environment: environment.Parse(c.StringSlice("environment")), environment: environment.Parse(c.StringSlice("environment")),
forgeCache: ttlcache.New(ttlcache.WithDisableTouchOnHit[int64, forge.Forge]()),
setupForge: setupForge,
}, nil }, nil
} }
func (e *Manager) SignaturePublicKey() crypto.PublicKey { func (m *manager) SignaturePublicKey() crypto.PublicKey {
return e.signaturePublicKey return m.signaturePublicKey
} }
func (e *Manager) SecretServiceFromRepo(_ *model.Repo) secret.Service { func (m *manager) SecretServiceFromRepo(_ *model.Repo) secret.Service {
return e.SecretService() return m.SecretService()
} }
func (e *Manager) SecretService() secret.Service { func (m *manager) SecretService() secret.Service {
return e.secret return m.secret
} }
func (e *Manager) RegistryServiceFromRepo(_ *model.Repo) registry.Service { func (m *manager) RegistryServiceFromRepo(_ *model.Repo) registry.Service {
return e.RegistryService() return m.RegistryService()
} }
func (e *Manager) RegistryService() registry.Service { func (m *manager) RegistryService() registry.Service {
return e.registry return m.registry
} }
func (e *Manager) ConfigServiceFromRepo(_ *model.Repo) config.Service { func (m *manager) ConfigServiceFromRepo(_ *model.Repo) config.Service {
// TODO: decied based on repo property which config service to use // TODO: decied based on repo property which config service to use
return e.config return m.config
} }
func (e *Manager) EnvironmentService() environment.Service { func (m *manager) EnvironmentService() environment.Service {
return e.environment return m.environment
}
func (m *manager) ForgeFromRepo(repo *model.Repo) (forge.Forge, error) {
return m.getForgeByID(repo.ForgeID)
}
func (m *manager) ForgeFromUser(user *model.User) (forge.Forge, error) {
return m.getForgeByID(user.ForgeID)
}
func (m *manager) ForgeMain() (forge.Forge, error) {
return m.getForgeByID(1) // main forge is always 1 and is configured via environment variables
}
func (m *manager) getForgeByID(id int64) (forge.Forge, error) {
item := m.forgeCache.Get(id)
if item != nil && !item.IsExpired() {
return item.Value(), nil
}
forgeModel, err := m.store.ForgeGet(id)
if err != nil {
return nil, err
}
forge, err := m.setupForge(forgeModel)
if err != nil {
return nil, err
}
m.forgeCache.Set(id, forge, forgeCacheTTL)
return forge, nil
} }

View file

@ -0,0 +1,270 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
package mocks
import (
crypto "crypto"
config "go.woodpecker-ci.org/woodpecker/v2/server/services/config"
environment "go.woodpecker-ci.org/woodpecker/v2/server/services/environment"
forge "go.woodpecker-ci.org/woodpecker/v2/server/forge"
mock "github.com/stretchr/testify/mock"
model "go.woodpecker-ci.org/woodpecker/v2/server/model"
registry "go.woodpecker-ci.org/woodpecker/v2/server/services/registry"
secret "go.woodpecker-ci.org/woodpecker/v2/server/services/secret"
)
// Manager is an autogenerated mock type for the Manager type
type Manager struct {
mock.Mock
}
// ConfigServiceFromRepo provides a mock function with given fields: repo
func (_m *Manager) ConfigServiceFromRepo(repo *model.Repo) config.Service {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for ConfigServiceFromRepo")
}
var r0 config.Service
if rf, ok := ret.Get(0).(func(*model.Repo) config.Service); ok {
r0 = rf(repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(config.Service)
}
}
return r0
}
// EnvironmentService provides a mock function with given fields:
func (_m *Manager) EnvironmentService() environment.Service {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for EnvironmentService")
}
var r0 environment.Service
if rf, ok := ret.Get(0).(func() environment.Service); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(environment.Service)
}
}
return r0
}
// ForgeFromRepo provides a mock function with given fields: repo
func (_m *Manager) ForgeFromRepo(repo *model.Repo) (forge.Forge, error) {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for ForgeFromRepo")
}
var r0 forge.Forge
var r1 error
if rf, ok := ret.Get(0).(func(*model.Repo) (forge.Forge, error)); ok {
return rf(repo)
}
if rf, ok := ret.Get(0).(func(*model.Repo) forge.Forge); ok {
r0 = rf(repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(forge.Forge)
}
}
if rf, ok := ret.Get(1).(func(*model.Repo) error); ok {
r1 = rf(repo)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ForgeFromUser provides a mock function with given fields: user
func (_m *Manager) ForgeFromUser(user *model.User) (forge.Forge, error) {
ret := _m.Called(user)
if len(ret) == 0 {
panic("no return value specified for ForgeFromUser")
}
var r0 forge.Forge
var r1 error
if rf, ok := ret.Get(0).(func(*model.User) (forge.Forge, error)); ok {
return rf(user)
}
if rf, ok := ret.Get(0).(func(*model.User) forge.Forge); ok {
r0 = rf(user)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(forge.Forge)
}
}
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
r1 = rf(user)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ForgeMain provides a mock function with given fields:
func (_m *Manager) ForgeMain() (forge.Forge, error) {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for ForgeMain")
}
var r0 forge.Forge
var r1 error
if rf, ok := ret.Get(0).(func() (forge.Forge, error)); ok {
return rf()
}
if rf, ok := ret.Get(0).(func() forge.Forge); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(forge.Forge)
}
}
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RegistryService provides a mock function with given fields:
func (_m *Manager) RegistryService() registry.Service {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for RegistryService")
}
var r0 registry.Service
if rf, ok := ret.Get(0).(func() registry.Service); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(registry.Service)
}
}
return r0
}
// RegistryServiceFromRepo provides a mock function with given fields: repo
func (_m *Manager) RegistryServiceFromRepo(repo *model.Repo) registry.Service {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for RegistryServiceFromRepo")
}
var r0 registry.Service
if rf, ok := ret.Get(0).(func(*model.Repo) registry.Service); ok {
r0 = rf(repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(registry.Service)
}
}
return r0
}
// SecretService provides a mock function with given fields:
func (_m *Manager) SecretService() secret.Service {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for SecretService")
}
var r0 secret.Service
if rf, ok := ret.Get(0).(func() secret.Service); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(secret.Service)
}
}
return r0
}
// SecretServiceFromRepo provides a mock function with given fields: repo
func (_m *Manager) SecretServiceFromRepo(repo *model.Repo) secret.Service {
ret := _m.Called(repo)
if len(ret) == 0 {
panic("no return value specified for SecretServiceFromRepo")
}
var r0 secret.Service
if rf, ok := ret.Get(0).(func(*model.Repo) secret.Service); ok {
r0 = rf(repo)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(secret.Service)
}
}
return r0
}
// SignaturePublicKey provides a mock function with given fields:
func (_m *Manager) SignaturePublicKey() crypto.PublicKey {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for SignaturePublicKey")
}
var r0 crypto.PublicKey
if rf, ok := ret.Get(0).(func() crypto.PublicKey); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(crypto.PublicKey)
}
}
return r0
}
// NewManager creates a new instance of Manager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewManager(t interface {
mock.TestingT
Cleanup(func())
}) *Manager {
mock := &Manager{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -25,6 +25,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
"go.woodpecker-ci.org/woodpecker/v2/server/services/config" "go.woodpecker-ci.org/woodpecker/v2/server/services/config"
"go.woodpecker-ci.org/woodpecker/v2/server/services/registry" "go.woodpecker-ci.org/woodpecker/v2/server/services/registry"
"go.woodpecker-ci.org/woodpecker/v2/server/services/secret" "go.woodpecker-ci.org/woodpecker/v2/server/services/secret"
@ -93,3 +94,70 @@ func setupSignatureKeys(_store store.Store) (crypto.PrivateKey, crypto.PublicKey
privateKey := ed25519.PrivateKey(privKeyStr) privateKey := ed25519.PrivateKey(privKeyStr)
return privateKey, privateKey.Public(), nil return privateKey, privateKey.Public(), nil
} }
func setupForgeService(c *cli.Context, _store store.Store) error {
_forge, err := _store.ForgeGet(1)
if err != nil && !errors.Is(err, types.RecordNotExist) {
return err
}
forgeExists := err == nil
if _forge == nil {
_forge = &model.Forge{
ID: 0,
}
}
if _forge.AdditionalOptions == nil {
_forge.AdditionalOptions = make(map[string]any)
}
_forge.Client = c.String("forge-oauth-client")
_forge.ClientSecret = c.String("forge-oauth-secret")
_forge.URL = c.String("forge-url")
_forge.SkipVerify = c.Bool("forge-skip-verify")
switch {
case c.String("addon-forge") != "":
_forge.Type = model.ForgeTypeAddon
_forge.AdditionalOptions["executable"] = c.String("addon-forge")
case c.Bool("github"):
_forge.Type = model.ForgeTypeGithub
_forge.AdditionalOptions["merge-ref"] = c.Bool("github-merge-ref")
_forge.AdditionalOptions["public-only"] = c.Bool("github-public-only")
if _forge.URL == "" {
_forge.URL = "https://github.com"
}
case c.Bool("gitlab"):
_forge.Type = model.ForgeTypeGitlab
if _forge.URL == "" {
_forge.URL = "https://gitlab.com"
}
case c.Bool("gitea"):
_forge.Type = model.ForgeTypeGitea
_forge.AdditionalOptions["oauth-server"] = c.String("gitea-oauth-server")
if _forge.URL == "" {
_forge.URL = "https://try.gitea.com"
}
case c.Bool("bitbucket"):
_forge.Type = model.ForgeTypeBitbucket
case c.Bool("bitbucket-dc"):
_forge.Type = model.ForgeTypeBitbucketDatacenter
_forge.AdditionalOptions["git-username"] = c.String("bitbucket-dc-git-username")
_forge.AdditionalOptions["git-password"] = c.String("bitbucket-dc-git-password")
default:
return errors.New("forge not configured")
}
if forgeExists {
err := _store.ForgeUpdate(_forge)
if err != nil {
return err
}
} else {
err := _store.ForgeCreate(_forge)
if err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,54 @@
// Copyright 2022 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package datastore
import (
"go.woodpecker-ci.org/woodpecker/v2/server/model"
)
func (s storage) ForgeGet(id int64) (*model.Forge, error) {
forge := new(model.Forge)
return forge, wrapGet(s.engine.ID(id).Get(forge))
}
func (s storage) ForgeList(p *model.ListOptions) ([]*model.Forge, error) {
forges := make([]*model.Forge, 0, 10)
return forges, s.paginate(p).Find(&forges)
}
func (s storage) ForgeCreate(forge *model.Forge) error {
// only Insert set auto created ID back to object
_, err := s.engine.Insert(forge)
return err
}
func (s storage) ForgeUpdate(forge *model.Forge) error {
_, err := s.engine.ID(forge.ID).AllCols().Update(forge)
return err
}
func (s storage) ForgeDelete(forge *model.Forge) error {
sess := s.engine.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
if _, err := sess.ID(forge.ID).Delete(new(model.Forge)); err != nil {
return err
}
return sess.Commit()
}

View file

@ -0,0 +1,71 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package datastore
import (
"testing"
"github.com/stretchr/testify/assert"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
)
func TestForgeCRUD(t *testing.T) {
store, closer := newTestStore(t, new(model.Forge), new(model.Repo), new(model.User))
defer closer()
forge1 := &model.Forge{
Type: "github",
URL: "https://github.com",
Client: "client",
ClientSecret: "secret",
SkipVerify: false,
AdditionalOptions: map[string]any{
"foo": "bar",
},
}
// create first forge to play with
assert.NoError(t, store.ForgeCreate(forge1))
assert.EqualValues(t, "github", forge1.Type)
// retrieve it
forgeOne, err := store.ForgeGet(forge1.ID)
assert.NoError(t, err)
assert.EqualValues(t, forge1, forgeOne)
// change type
assert.NoError(t, store.ForgeUpdate(&model.Forge{ID: forge1.ID, Type: "gitlab"}))
// find updated forge by id
forgeOne, err = store.ForgeGet(forge1.ID)
assert.NoError(t, err)
assert.EqualValues(t, "gitlab", forgeOne.Type)
// create two more forges and repos
someUser := &model.Forge{Type: "bitbucket"}
assert.NoError(t, store.ForgeCreate(someUser))
assert.NoError(t, store.ForgeCreate(&model.Forge{Type: "gitea"}))
// get all repos for a specific forge
forges, err := store.ForgeList(&model.ListOptions{All: true})
assert.NoError(t, err)
assert.Len(t, forges, 3)
// delete an forge and check if it's gone
assert.NoError(t, store.ForgeDelete(forge1))
_, err = store.ForgeGet(forge1.ID)
assert.Error(t, err)
}

View file

@ -0,0 +1,46 @@
// 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 migration
import (
"fmt"
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
)
var setForgeID = xormigrate.Migration{
ID: "set-forge-id",
MigrateSession: func(sess *xorm.Session) (err error) {
if err := sess.Sync(new(model.User), new(model.Repo), new(model.Forge), new(model.Org)); err != nil {
return fmt.Errorf("sync new models failed: %w", err)
}
_, err = sess.Exec(fmt.Sprintf("UPDATE `%s` SET forge_id=1;", model.User{}.TableName()))
if err != nil {
return err
}
_, err = sess.Exec(fmt.Sprintf("UPDATE `%s` SET forge_id=1;", model.Org{}.TableName()))
if err != nil {
return err
}
_, err = sess.Exec(fmt.Sprintf("UPDATE `%s` SET forge_id=1;", model.Repo{}.TableName()))
return err
},
}

View file

@ -59,6 +59,7 @@ var migrationTasks = []*xormigrate.Migration{
&convertToNewPipelineErrorFormat, &convertToNewPipelineErrorFormat,
&renameLinkToURL, &renameLinkToURL,
&cleanRegistryPipeline, &cleanRegistryPipeline,
&setForgeID,
} }
var allBeans = []any{ var allBeans = []any{
@ -77,6 +78,7 @@ var allBeans = []any{
new(model.ServerConfig), new(model.ServerConfig),
new(model.Cron), new(model.Cron),
new(model.Redirection), new(model.Redirection),
new(model.Forge),
new(model.Workflow), new(model.Workflow),
new(model.Org), new(model.Org),
} }

View file

@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT. // Code generated by mockery. DO NOT EDIT.
package mocks package mocks
@ -539,6 +539,152 @@ func (_m *Store) DeleteUser(_a0 *model.User) error {
return r0 return r0
} }
// ForgeCreate provides a mock function with given fields: _a0
func (_m *Store) ForgeCreate(_a0 *model.Forge) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(*model.Forge) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// ForgeDelete provides a mock function with given fields: _a0
func (_m *Store) ForgeDelete(_a0 *model.Forge) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(*model.Forge) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// ForgeFindByRepo provides a mock function with given fields: _a0
func (_m *Store) ForgeFindByRepo(_a0 *model.Repo) (*model.Forge, error) {
ret := _m.Called(_a0)
var r0 *model.Forge
var r1 error
if rf, ok := ret.Get(0).(func(*model.Repo) (*model.Forge, error)); ok {
return rf(_a0)
}
if rf, ok := ret.Get(0).(func(*model.Repo) *model.Forge); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Forge)
}
}
if rf, ok := ret.Get(1).(func(*model.Repo) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ForgeFindByUser provides a mock function with given fields: _a0
func (_m *Store) ForgeFindByUser(_a0 *model.User) (*model.Forge, error) {
ret := _m.Called(_a0)
var r0 *model.Forge
var r1 error
if rf, ok := ret.Get(0).(func(*model.User) (*model.Forge, error)); ok {
return rf(_a0)
}
if rf, ok := ret.Get(0).(func(*model.User) *model.Forge); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Forge)
}
}
if rf, ok := ret.Get(1).(func(*model.User) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ForgeGet provides a mock function with given fields: _a0
func (_m *Store) ForgeGet(_a0 int64) (*model.Forge, error) {
ret := _m.Called(_a0)
var r0 *model.Forge
var r1 error
if rf, ok := ret.Get(0).(func(int64) (*model.Forge, error)); ok {
return rf(_a0)
}
if rf, ok := ret.Get(0).(func(int64) *model.Forge); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.Forge)
}
}
if rf, ok := ret.Get(1).(func(int64) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ForgeList provides a mock function with given fields: p
func (_m *Store) ForgeList(p *model.ListOptions) ([]*model.Forge, error) {
ret := _m.Called(p)
var r0 []*model.Forge
var r1 error
if rf, ok := ret.Get(0).(func(*model.ListOptions) ([]*model.Forge, error)); ok {
return rf(p)
}
if rf, ok := ret.Get(0).(func(*model.ListOptions) []*model.Forge); ok {
r0 = rf(p)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.Forge)
}
}
if rf, ok := ret.Get(1).(func(*model.ListOptions) error); ok {
r1 = rf(p)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ForgeUpdate provides a mock function with given fields: _a0
func (_m *Store) ForgeUpdate(_a0 *model.Forge) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(*model.Forge) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetActivePipelineList provides a mock function with given fields: repo // GetActivePipelineList provides a mock function with given fields: repo
func (_m *Store) GetActivePipelineList(repo *model.Repo) ([]*model.Pipeline, error) { func (_m *Store) GetActivePipelineList(repo *model.Repo) ([]*model.Pipeline, error) {
ret := _m.Called(repo) ret := _m.Called(repo)

View file

@ -14,7 +14,6 @@
package store package store
//go:generate go install github.com/vektra/mockery/v2@latest
//go:generate mockery --name Store --output mocks --case underscore //go:generate mockery --name Store --output mocks --case underscore
import ( import (
@ -160,6 +159,13 @@ type Store interface {
CronListNextExecute(int64, int64) ([]*model.Cron, error) CronListNextExecute(int64, int64) ([]*model.Cron, error)
CronGetLock(*model.Cron, int64) (bool, error) CronGetLock(*model.Cron, int64) (bool, error)
// Forge
ForgeCreate(*model.Forge) error
ForgeGet(int64) (*model.Forge, error)
ForgeList(p *model.ListOptions) ([]*model.Forge, error)
ForgeUpdate(*model.Forge) error
ForgeDelete(*model.Forge) error
// Agent // Agent
AgentCreate(*model.Agent) error AgentCreate(*model.Agent) error
AgentFind(int64) (*model.Agent, error) AgentFind(int64) (*model.Agent, error)

View file

@ -39,12 +39,20 @@ func Config(c *gin.Context) {
).Sign(user.Hash) ).Sign(user.Hash)
} }
// TODO: remove this and use the forge type from the corresponding repo
mainForge, err := server.Config.Services.Manager.ForgeMain()
if err != nil {
log.Error().Err(err).Msg("could not get main forge")
c.AbortWithStatus(http.StatusInternalServerError)
return
}
configData := map[string]any{ configData := map[string]any{
"user": user, "user": user,
"csrf": csrf, "csrf": csrf,
"version": version.String(), "version": version.String(),
"skip_version_check": server.Config.WebUI.SkipVersionCheck, "skip_version_check": server.Config.WebUI.SkipVersionCheck,
"forge": server.Config.Services.Forge.Name(), "forge": mainForge.Name(),
"root_path": server.Config.Server.RootPath, "root_path": server.Config.Server.RootPath,
"enable_swagger": server.Config.WebUI.EnableSwagger, "enable_swagger": server.Config.WebUI.EnableSwagger,
} }

View file

@ -135,7 +135,7 @@ devDependencies:
version: 5.4.3 version: 5.4.3
typescript-eslint: typescript-eslint:
specifier: ^7.6.0 specifier: ^7.6.0
version: 7.6.0(eslint@9.0.0)(typescript@5.4.3) version: 7.7.0(eslint@9.0.0)(typescript@5.4.3)
unplugin-icons: unplugin-icons:
specifier: ^0.18.2 specifier: ^0.18.2
version: 0.18.5(@vue/compiler-sfc@3.4.21) version: 0.18.5(@vue/compiler-sfc@3.4.21)
@ -1022,8 +1022,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/eslint-plugin@7.6.0(@typescript-eslint/parser@7.6.0)(eslint@9.0.0)(typescript@5.4.3): /@typescript-eslint/eslint-plugin@7.7.0(@typescript-eslint/parser@7.7.0)(eslint@9.0.0)(typescript@5.4.3):
resolution: {integrity: sha512-gKmTNwZnblUdnTIJu3e9kmeRRzV2j1a/LUO27KNNAnIC5zjy1aSvXSRp4rVNlmAoHlQ7HzX42NbKpcSr4jF80A==} resolution: {integrity: sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': ^7.0.0 '@typescript-eslint/parser': ^7.0.0
@ -1034,11 +1034,11 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@eslint-community/regexpp': 4.10.0 '@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 7.6.0(eslint@9.0.0)(typescript@5.4.3) '@typescript-eslint/parser': 7.7.0(eslint@9.0.0)(typescript@5.4.3)
'@typescript-eslint/scope-manager': 7.6.0 '@typescript-eslint/scope-manager': 7.7.0
'@typescript-eslint/type-utils': 7.6.0(eslint@9.0.0)(typescript@5.4.3) '@typescript-eslint/type-utils': 7.7.0(eslint@9.0.0)(typescript@5.4.3)
'@typescript-eslint/utils': 7.6.0(eslint@9.0.0)(typescript@5.4.3) '@typescript-eslint/utils': 7.7.0(eslint@9.0.0)(typescript@5.4.3)
'@typescript-eslint/visitor-keys': 7.6.0 '@typescript-eslint/visitor-keys': 7.7.0
debug: 4.3.4 debug: 4.3.4
eslint: 9.0.0 eslint: 9.0.0
graphemer: 1.4.0 graphemer: 1.4.0
@ -1072,8 +1072,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/parser@7.6.0(eslint@9.0.0)(typescript@5.4.3): /@typescript-eslint/parser@7.7.0(eslint@9.0.0)(typescript@5.4.3):
resolution: {integrity: sha512-usPMPHcwX3ZoPWnBnhhorc14NJw9J4HpSXQX4urF2TPKG0au0XhJoZyX62fmvdHONUkmyUe74Hzm1//XA+BoYg==} resolution: {integrity: sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
@ -1082,10 +1082,10 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/scope-manager': 7.6.0 '@typescript-eslint/scope-manager': 7.7.0
'@typescript-eslint/types': 7.6.0 '@typescript-eslint/types': 7.7.0
'@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.3) '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.3)
'@typescript-eslint/visitor-keys': 7.6.0 '@typescript-eslint/visitor-keys': 7.7.0
debug: 4.3.4 debug: 4.3.4
eslint: 9.0.0 eslint: 9.0.0
typescript: 5.4.3 typescript: 5.4.3
@ -1101,12 +1101,12 @@ packages:
'@typescript-eslint/visitor-keys': 7.4.0 '@typescript-eslint/visitor-keys': 7.4.0
dev: true dev: true
/@typescript-eslint/scope-manager@7.6.0: /@typescript-eslint/scope-manager@7.7.0:
resolution: {integrity: sha512-ngttyfExA5PsHSx0rdFgnADMYQi+Zkeiv4/ZxGYUWd0nLs63Ha0ksmp8VMxAIC0wtCFxMos7Lt3PszJssG/E6w==} resolution: {integrity: sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 7.6.0 '@typescript-eslint/types': 7.7.0
'@typescript-eslint/visitor-keys': 7.6.0 '@typescript-eslint/visitor-keys': 7.7.0
dev: true dev: true
/@typescript-eslint/type-utils@7.4.0(eslint@9.0.0)(typescript@5.4.3): /@typescript-eslint/type-utils@7.4.0(eslint@9.0.0)(typescript@5.4.3):
@ -1129,8 +1129,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/type-utils@7.6.0(eslint@9.0.0)(typescript@5.4.3): /@typescript-eslint/type-utils@7.7.0(eslint@9.0.0)(typescript@5.4.3):
resolution: {integrity: sha512-NxAfqAPNLG6LTmy7uZgpK8KcuiS2NZD/HlThPXQRGwz6u7MDBWRVliEEl1Gj6U7++kVJTpehkhZzCJLMK66Scw==} resolution: {integrity: sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
@ -1139,8 +1139,8 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.3) '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.3)
'@typescript-eslint/utils': 7.6.0(eslint@9.0.0)(typescript@5.4.3) '@typescript-eslint/utils': 7.7.0(eslint@9.0.0)(typescript@5.4.3)
debug: 4.3.4 debug: 4.3.4
eslint: 9.0.0 eslint: 9.0.0
ts-api-utils: 1.3.0(typescript@5.4.3) ts-api-utils: 1.3.0(typescript@5.4.3)
@ -1154,8 +1154,8 @@ packages:
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
dev: true dev: true
/@typescript-eslint/types@7.6.0: /@typescript-eslint/types@7.7.0:
resolution: {integrity: sha512-h02rYQn8J+MureCvHVVzhl69/GAfQGPQZmOMjG1KfCl7o3HtMSlPaPUAPu6lLctXI5ySRGIYk94clD/AUMCUgQ==} resolution: {integrity: sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
dev: true dev: true
@ -1181,8 +1181,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/typescript-estree@7.6.0(typescript@5.4.3): /@typescript-eslint/typescript-estree@7.7.0(typescript@5.4.3):
resolution: {integrity: sha512-+7Y/GP9VuYibecrCQWSKgl3GvUM5cILRttpWtnAu8GNL9j11e4tbuGZmZjJ8ejnKYyBRb2ddGQ3rEFCq3QjMJw==} resolution: {integrity: sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@ -1190,8 +1190,8 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/types': 7.6.0 '@typescript-eslint/types': 7.7.0
'@typescript-eslint/visitor-keys': 7.6.0 '@typescript-eslint/visitor-keys': 7.7.0
debug: 4.3.4 debug: 4.3.4
globby: 11.1.0 globby: 11.1.0
is-glob: 4.0.3 is-glob: 4.0.3
@ -1222,8 +1222,8 @@ packages:
- typescript - typescript
dev: true dev: true
/@typescript-eslint/utils@7.6.0(eslint@9.0.0)(typescript@5.4.3): /@typescript-eslint/utils@7.7.0(eslint@9.0.0)(typescript@5.4.3):
resolution: {integrity: sha512-x54gaSsRRI+Nwz59TXpCsr6harB98qjXYzsRxGqvA5Ue3kQH+FxS7FYU81g/omn22ML2pZJkisy6Q+ElK8pBCA==} resolution: {integrity: sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
@ -1231,9 +1231,9 @@ packages:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.0.0) '@eslint-community/eslint-utils': 4.4.0(eslint@9.0.0)
'@types/json-schema': 7.0.15 '@types/json-schema': 7.0.15
'@types/semver': 7.5.8 '@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 7.6.0 '@typescript-eslint/scope-manager': 7.7.0
'@typescript-eslint/types': 7.6.0 '@typescript-eslint/types': 7.7.0
'@typescript-eslint/typescript-estree': 7.6.0(typescript@5.4.3) '@typescript-eslint/typescript-estree': 7.7.0(typescript@5.4.3)
eslint: 9.0.0 eslint: 9.0.0
semver: 7.6.0 semver: 7.6.0
transitivePeerDependencies: transitivePeerDependencies:
@ -1249,11 +1249,11 @@ packages:
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
dev: true dev: true
/@typescript-eslint/visitor-keys@7.6.0: /@typescript-eslint/visitor-keys@7.7.0:
resolution: {integrity: sha512-4eLB7t+LlNUmXzfOu1VAIAdkjbu5xNSerURS9X/S5TUKWFRpXRQZbmtPqgKmYx8bj3J0irtQXSiWAOY82v+cgw==} resolution: {integrity: sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 7.6.0 '@typescript-eslint/types': 7.7.0
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
dev: true dev: true
@ -3825,8 +3825,8 @@ packages:
possible-typed-array-names: 1.0.0 possible-typed-array-names: 1.0.0
dev: true dev: true
/typescript-eslint@7.6.0(eslint@9.0.0)(typescript@5.4.3): /typescript-eslint@7.7.0(eslint@9.0.0)(typescript@5.4.3):
resolution: {integrity: sha512-LY6vH6F1l5jpGqRtU+uK4+mOecIb4Cd4kaz1hAiJrgnNiHUA8wiw8BkJyYS+MRLM69F1QuSKwtGlQqnGl1Rc6w==} resolution: {integrity: sha512-wZZ+7mTQJCn4mGAvzdERtL4vwKGM/mF9cMSMeKUllz3Hgbd1Mdd5L60Q+nJmCio9RB4OyMMr0EX4Ry2Q7jiAyw==}
engines: {node: ^18.18.0 || >=20.0.0} engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies: peerDependencies:
eslint: ^8.56.0 eslint: ^8.56.0
@ -3835,9 +3835,9 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 7.6.0(@typescript-eslint/parser@7.6.0)(eslint@9.0.0)(typescript@5.4.3) '@typescript-eslint/eslint-plugin': 7.7.0(@typescript-eslint/parser@7.7.0)(eslint@9.0.0)(typescript@5.4.3)
'@typescript-eslint/parser': 7.6.0(eslint@9.0.0)(typescript@5.4.3) '@typescript-eslint/parser': 7.7.0(eslint@9.0.0)(typescript@5.4.3)
'@typescript-eslint/utils': 7.6.0(eslint@9.0.0)(typescript@5.4.3) '@typescript-eslint/utils': 7.7.0(eslint@9.0.0)(typescript@5.4.3)
eslint: 9.0.0 eslint: 9.0.0
typescript: 5.4.3 typescript: 5.4.3
transitivePeerDependencies: transitivePeerDependencies:

View file

@ -260,7 +260,6 @@
"log_auto_scroll_off": "Turn off automatic scrolling" "log_auto_scroll_off": "Turn off automatic scrolling"
}, },
"protected": { "protected": {
"review": "Review changes",
"awaits": "This pipeline is awaiting approval by a maintainer!", "awaits": "This pipeline is awaiting approval by a maintainer!",
"approve": "Approve", "approve": "Approve",
"decline": "Decline", "decline": "Decline",

View file

@ -14,9 +14,9 @@
"desc": "Le nombre maximum de pipelines exécutées en parallèle par cet agent." "desc": "Le nombre maximum de pipelines exécutées en parallèle par cet agent."
}, },
"created": "Agent crée", "created": "Agent crée",
"delete_agent": "Effacer l'agent", "delete_agent": "Supprimer l'agent",
"delete_confirm": "Voulez vous vraiment effacer cet agent ? Il ne pourra plus se connecter sur le serveur.", "delete_confirm": "Voulez vous vraiment supprimer cet agent ? Il ne pourra plus se connecter sur le serveur.",
"deleted": "Agent effacé", "deleted": "Agent supprimé",
"desc": "Agents enregistrés sur ce serveur", "desc": "Agents enregistrés sur ce serveur",
"edit_agent": "Éditer l'agent", "edit_agent": "Éditer l'agent",
"id": "ID", "id": "ID",
@ -43,9 +43,9 @@
}, },
"not_allowed": "Vous n'êtes pas autorisé à accéder aux réglages du serveur", "not_allowed": "Vous n'êtes pas autorisé à accéder aux réglages du serveur",
"orgs": { "orgs": {
"delete_confirm": "Voulez-vous vraiment effacer cette organisation ? Cela supprimera tous les dépôts que possède cette organisation.", "delete_confirm": "Voulez-vous vraiment supprimer cette organisation ? Cela supprimera tous les dépôts que possède cette organisation.",
"delete_org": "Effacer l'organisation", "delete_org": "Supprimer l'organisation",
"deleted": "Organisation effacée", "deleted": "Organisation supprimée",
"desc": "Organisations possédant des dépôts sur ce serveur", "desc": "Organisations possédant des dépôts sur ce serveur",
"none": "Il n'y a pas encore d'organisation.", "none": "Il n'y a pas encore d'organisation.",
"org_settings": "Réglages de l'organisation", "org_settings": "Réglages de l'organisation",
@ -88,7 +88,7 @@
"secrets": { "secrets": {
"add": "Ajouter un secret", "add": "Ajouter un secret",
"created": "Secret global crée", "created": "Secret global crée",
"deleted": "Secret global effacé", "deleted": "Secret global supprimé",
"desc": "Les secrets globaux sont transmis sous forme de variable denvironnement lors de lexécution de toutes les étapes d'un pipeline.", "desc": "Les secrets globaux sont transmis sous forme de variable denvironnement lors de lexécution de toutes les étapes d'un pipeline.",
"events": { "events": {
"events": "Disponible pour les événements suivants", "events": "Disponible pour les événements suivants",
@ -119,8 +119,8 @@
"cancel": "Annuler", "cancel": "Annuler",
"created": "Compte utilisateur créé", "created": "Compte utilisateur créé",
"delete_confirm": "Voulez vous vraiment supprimer ce compte utilisateur ? Cela supprimera tout les dépôts que possède ce compte utilisateur.", "delete_confirm": "Voulez vous vraiment supprimer ce compte utilisateur ? Cela supprimera tout les dépôts que possède ce compte utilisateur.",
"delete_user": "Effacer le compte utilisateur", "delete_user": "Supprimer le compte utilisateur",
"deleted": "Compte utilisateur effacé", "deleted": "Compte utilisateur supprimé",
"desc": "Utilisateurs enregistrés sur le serveur", "desc": "Utilisateurs enregistrés sur le serveur",
"edit_user": "Éditer le compte utilisateur", "edit_user": "Éditer le compte utilisateur",
"email": "Courriel", "email": "Courriel",
@ -156,7 +156,7 @@
"secrets": { "secrets": {
"add": "Ajouter un secret", "add": "Ajouter un secret",
"created": "Secret d'organisation crée", "created": "Secret d'organisation crée",
"deleted": "Secret d'organisation effacé", "deleted": "Secret d'organisation supprimé",
"desc": "Les secrets d'organisation sont transmis sous forme de variable denvironnement lors de lexécution de chaque étape d'un pipeline de tout les dépôts de l'organisation.", "desc": "Les secrets d'organisation sont transmis sous forme de variable denvironnement lors de lexécution de chaque étape d'un pipeline de tout les dépôts de l'organisation.",
"events": { "events": {
"events": "Disponible pour les événements suivants", "events": "Disponible pour les événements suivants",
@ -195,7 +195,7 @@
"name": "Nom de la variable", "name": "Nom de la variable",
"title": "Variables additionnelles du pipeline", "title": "Variables additionnelles du pipeline",
"value": "Valeur de la variable", "value": "Valeur de la variable",
"delete": "Effacer la variable" "delete": "Supprimer la variable"
} }
}, },
"enable": { "enable": {
@ -216,7 +216,7 @@
"name": "Nom de la variable", "name": "Nom de la variable",
"title": "Variables de pipeline supplémentaire", "title": "Variables de pipeline supplémentaire",
"value": "Valeur de la variable", "value": "Valeur de la variable",
"delete": "Effacer la variable" "delete": "Supprimer la variable"
} }
}, },
"not_allowed": "Vous n'êtes pas autorisé à accéder à ce dépôt", "not_allowed": "Vous n'êtes pas autorisé à accéder à ce dépôt",
@ -231,7 +231,8 @@
"log_auto_scroll_off": "Désactiver le défilement automatique", "log_auto_scroll_off": "Désactiver le défilement automatique",
"log_download": "Télécharger", "log_download": "Télécharger",
"restart": "Redémarrer", "restart": "Redémarrer",
"restart_success": "Pipeline redémarré" "restart_success": "Pipeline redémarré",
"log_delete": "Supprimer"
}, },
"config": "Configuration", "config": "Configuration",
"errors": "Erreurs ({count})", "errors": "Erreurs ({count})",
@ -281,7 +282,9 @@
"step_not_started": "L'étape n'a pas démarré encore.", "step_not_started": "L'étape n'a pas démarré encore.",
"tasks": "Tâches", "tasks": "Tâches",
"warnings": "Avertissements ({count})", "warnings": "Avertissements ({count})",
"we_got_some_errors": "Oh non, il y a des erreurs!" "we_got_some_errors": "Oh non, il y a des erreurs!",
"log_delete_error": "Il y a eu une erreur lors de la suppression des logs",
"log_delete_confirm": "Voulez vous vraiment supprimer les logs de cette étape ?"
}, },
"pull_requests": "Pull requests", "pull_requests": "Pull requests",
"settings": { "settings": {
@ -289,8 +292,8 @@
"actions": "Actions", "actions": "Actions",
"delete": { "delete": {
"confirm": "Toutes les données vont être perdues aprés cette action!!!\n\nVoulez vous vraiment continuer?", "confirm": "Toutes les données vont être perdues aprés cette action!!!\n\nVoulez vous vraiment continuer?",
"delete": "Effacer le dépôt", "delete": "Supprimer le dépôt",
"success": "Dépôt effacé" "success": "Dépôt supprimé"
}, },
"disable": { "disable": {
"disable": "Désactiver le dépôt", "disable": "Désactiver le dépôt",
@ -321,8 +324,8 @@
}, },
"created": "Tâche planifiée crée", "created": "Tâche planifiée crée",
"crons": "Tâches planifiées", "crons": "Tâches planifiées",
"delete": "Effacer la tâche planifiée", "delete": "Supprimer la tâche planifiée",
"deleted": "Tâche planifiée effacée", "deleted": "Tâche planifiée supprimée",
"desc": "Les tâches planifiées peuvent déclencher des pipelines à intervalles réguliers.", "desc": "Les tâches planifiées peuvent déclencher des pipelines à intervalles réguliers.",
"edit": "Modifier la tâche planifiée", "edit": "Modifier la tâche planifiée",
"name": { "name": {
@ -405,8 +408,8 @@
}, },
"created": "Authentifiant de connexion à un registre crée", "created": "Authentifiant de connexion à un registre crée",
"credentials": "Authentifiants de connexion à un registre", "credentials": "Authentifiants de connexion à un registre",
"delete": "Effacer le registre", "delete": "Supprimer le registre",
"deleted": "Authentifiant de connexion à un registre effacé", "deleted": "Authentifiant de connexion à un registre supprimé",
"desc": "Des authentifiants de connexion pour les registres peuvent être ajouté pour permettre d'utiliser des images privées pour vos pipelines.", "desc": "Des authentifiants de connexion pour les registres peuvent être ajouté pour permettre d'utiliser des images privées pour vos pipelines.",
"edit": "Modifier le registre", "edit": "Modifier le registre",
"none": "Il n'y a pas dauthentifiant de connexion à un registre pour le moment.", "none": "Il n'y a pas dauthentifiant de connexion à un registre pour le moment.",
@ -418,9 +421,9 @@
"secrets": { "secrets": {
"add": "Ajouter un secret", "add": "Ajouter un secret",
"created": "Secret crée", "created": "Secret crée",
"delete": "Effacer le secret", "delete": "Supprimer le secret",
"delete_confirm": "Voulez vous vraiment effacer ce secret ?", "delete_confirm": "Voulez vous vraiment supprimer ce secret ?",
"deleted": "Secret effacé", "deleted": "Secret supprimé",
"desc": "Les secrets sont transmis sous forme de variable denvironnement lors de lexécution d'une étape d'un pipeline.", "desc": "Les secrets sont transmis sous forme de variable denvironnement lors de lexécution d'une étape d'un pipeline.",
"edit": "Modifier le secret", "edit": "Modifier le secret",
"events": { "events": {
@ -490,7 +493,7 @@
"secrets": { "secrets": {
"add": "Ajouter un secret", "add": "Ajouter un secret",
"created": "Secret d'utilisateur crée", "created": "Secret d'utilisateur crée",
"deleted": "Secret d'utilisateur effacé", "deleted": "Secret d'utilisateur supprimé",
"desc": "Les secrets d'utilisateur peuvent être passés à toutes les étapes du pipeline des dépôts de l'utilisateur sous forme de variables d'environnement.", "desc": "Les secrets d'utilisateur peuvent être passés à toutes les étapes du pipeline des dépôts de l'utilisateur sous forme de variables d'environnement.",
"events": { "events": {
"events": "Disponible pour les événements suivants", "events": "Disponible pour les événements suivants",

View file

@ -77,7 +77,7 @@ const router = useRouter();
const i18n = useI18n(); const i18n = useI18n();
const config = useConfig(); const config = useConfig();
const { forge } = useConfig(); const { forge } = useConfig(); // TODO: remove this and use the forge type from the corresponding repo
const repo = repoStore.getRepo(repositoryId); const repo = repoStore.getRepo(repositoryId);
const repoPermissions = ref<RepoPermissions>(); const repoPermissions = ref<RepoPermissions>();
const pipelines = pipelineStore.getRepoPipelines(repositoryId); const pipelines = pipelineStore.getRepoPipelines(repositoryId);

View file

@ -34,13 +34,6 @@
<Icon name="status-blocked" class="w-16 h-16" /> <Icon name="status-blocked" class="w-16 h-16" />
<span class="text-xl">{{ $t('repo.pipeline.protected.awaits') }}</span> <span class="text-xl">{{ $t('repo.pipeline.protected.awaits') }}</span>
<div v-if="repoPermissions.push" class="flex gap-2 flex-wrap items-center justify-center"> <div v-if="repoPermissions.push" class="flex gap-2 flex-wrap items-center justify-center">
<Button
color="blue"
:start-icon="forge ?? 'repo'"
:text="$t('repo.pipeline.protected.review')"
:to="pipeline.forge_url"
:title="message"
/>
<Button <Button
color="green" color="green"
:text="$t('repo.pipeline.protected.approve')" :text="$t('repo.pipeline.protected.approve')"
@ -90,9 +83,7 @@ import PipelineLog from '~/components/repo/pipeline/PipelineLog.vue';
import PipelineStepList from '~/components/repo/pipeline/PipelineStepList.vue'; import PipelineStepList from '~/components/repo/pipeline/PipelineStepList.vue';
import useApiClient from '~/compositions/useApiClient'; import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction'; import { useAsyncAction } from '~/compositions/useAsyncAction';
import useConfig from '~/compositions/useConfig';
import useNotifications from '~/compositions/useNotifications'; import useNotifications from '~/compositions/useNotifications';
import usePipeline from '~/compositions/usePipeline';
import { Pipeline, PipelineStep, Repo, RepoPermissions } from '~/lib/api/types'; import { Pipeline, PipelineStep, Repo, RepoPermissions } from '~/lib/api/types';
import { findStep } from '~/utils/helpers'; import { findStep } from '~/utils/helpers';
@ -156,9 +147,6 @@ const selectedStepId = computed({
}, },
}); });
const { forge } = useConfig();
const { message } = usePipeline(pipeline);
const selectedStep = computed(() => findStep(pipeline.value.workflows || [], selectedStepId.value || -1)); const selectedStep = computed(() => findStep(pipeline.value.workflows || [], selectedStepId.value || -1));
const { doSubmit: approvePipeline, isLoading: isApprovingPipeline } = useAsyncAction(async () => { const { doSubmit: approvePipeline, isLoading: isApprovingPipeline } = useAsyncAction(async () => {