From 00f0fcd41643fc92de304be19b72a0ea69b64a58 Mon Sep 17 00:00:00 2001 From: qwerty287 <80460567+qwerty287@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:04:21 +0200 Subject: [PATCH] Rework addons (use rpc) (#3268) Co-authored-by: Anbraten <6918444+anbraten@users.noreply.github.com> --- cli/lint/lint.go | 2 +- cmd/server/docs/docs.go | 80 ++-- cmd/server/flags.go | 8 +- cmd/server/setup.go | 13 +- .../51-plugins/20-creating-plugins.md | 4 +- .../30-administration/10-server-config.md | 12 +- .../30-administration/11-forges/100-addon.md | 68 ++++ .../75-addons/20-creating-addons.md | 97 ----- .../75-addons/75-overview.md | 40 -- .../75-addons/_category_.yaml | 6 - docs/docs/92-development/02-core-ideas.md | 2 +- go.mod | 6 +- go.sum | 48 +++ pipeline/errors/error.go | 38 +- pipeline/errors/error_test.go | 37 +- pipeline/errors/types/errors.go | 24 ++ pipeline/frontend/yaml/linter/error.go | 7 +- pipeline/frontend/yaml/linter/linter.go | 33 +- pipeline/frontend/yaml/matrix/matrix.go | 6 +- server/forge/addon/args.go | 154 +++++++ server/forge/addon/client.go | 377 ++++++++++++++++++ server/forge/addon/logger.go | 165 ++++++++ server/forge/addon/plugin.go | 43 ++ server/forge/addon/server.go | 278 +++++++++++++ server/model/pipeline.go | 64 +-- server/pipeline/stepbuilder/stepBuilder.go | 3 +- ...7_convert_to_new_pipeline_errors_format.go | 10 +- shared/addon/addon.go | 62 --- shared/addon/types/types.go | 7 - 29 files changed, 1309 insertions(+), 385 deletions(-) create mode 100644 docs/docs/30-administration/11-forges/100-addon.md delete mode 100644 docs/docs/30-administration/75-addons/20-creating-addons.md delete mode 100644 docs/docs/30-administration/75-addons/75-overview.md delete mode 100644 docs/docs/30-administration/75-addons/_category_.yaml create mode 100644 pipeline/errors/types/errors.go create mode 100644 server/forge/addon/args.go create mode 100644 server/forge/addon/client.go create mode 100644 server/forge/addon/logger.go create mode 100644 server/forge/addon/plugin.go create mode 100644 server/forge/addon/server.go delete mode 100644 shared/addon/addon.go delete mode 100644 shared/addon/types/types.go diff --git a/cli/lint/lint.go b/cli/lint/lint.go index 7d2a59d74..a11927d77 100644 --- a/cli/lint/lint.go +++ b/cli/lint/lint.go @@ -114,7 +114,7 @@ func lintFile(_ *cli.Context, file string) error { hasErrors = true } - if data := err.GetLinterData(); data != nil { + if data := pipeline_errors.GetLinterData(err); data != nil { line = fmt.Sprintf("%s %s\t%s", line, output.String(data.Field).Bold(), err.Message) } else { line = fmt.Sprintf("%s %s", line, err.Message) diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index 3514a4a76..51cfe5587 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -3969,7 +3969,7 @@ const docTemplate = `{ "errors": { "type": "array", "items": { - "$ref": "#/definitions/errors.PipelineError" + "$ref": "#/definitions/types.PipelineError" } }, "event": { @@ -4455,45 +4455,6 @@ const docTemplate = `{ "EventManual" ] }, - "errors.PipelineError": { - "type": "object", - "properties": { - "data": {}, - "is_warning": { - "type": "boolean" - }, - "message": { - "type": "string" - }, - "type": { - "$ref": "#/definitions/errors.PipelineErrorType" - } - } - }, - "errors.PipelineErrorType": { - "type": "string", - "enum": [ - "linter", - "deprecation", - "compiler", - "generic", - "bad_habit" - ], - "x-enum-comments": { - "PipelineErrorTypeBadHabit": "some bad-habit error", - "PipelineErrorTypeCompiler": "some error with the config semantics", - "PipelineErrorTypeDeprecation": "using some deprecated feature", - "PipelineErrorTypeGeneric": "some generic error", - "PipelineErrorTypeLinter": "some error with the config syntax" - }, - "x-enum-varnames": [ - "PipelineErrorTypeLinter", - "PipelineErrorTypeDeprecation", - "PipelineErrorTypeCompiler", - "PipelineErrorTypeGeneric", - "PipelineErrorTypeBadHabit" - ] - }, "model.Workflow": { "type": "object", "properties": { @@ -4540,6 +4501,45 @@ const docTemplate = `{ "$ref": "#/definitions/StatusValue" } } + }, + "types.PipelineError": { + "type": "object", + "properties": { + "data": {}, + "is_warning": { + "type": "boolean" + }, + "message": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/types.PipelineErrorType" + } + } + }, + "types.PipelineErrorType": { + "type": "string", + "enum": [ + "linter", + "deprecation", + "compiler", + "generic", + "bad_habit" + ], + "x-enum-comments": { + "PipelineErrorTypeBadHabit": "some bad-habit error", + "PipelineErrorTypeCompiler": "some error with the config semantics", + "PipelineErrorTypeDeprecation": "using some deprecated feature", + "PipelineErrorTypeGeneric": "some generic error", + "PipelineErrorTypeLinter": "some error with the config syntax" + }, + "x-enum-varnames": [ + "PipelineErrorTypeLinter", + "PipelineErrorTypeDeprecation", + "PipelineErrorTypeCompiler", + "PipelineErrorTypeGeneric", + "PipelineErrorTypeBadHabit" + ] } } }` diff --git a/cmd/server/flags.go b/cmd/server/flags.go index dd7eefe52..11d7ff2f3 100644 --- a/cmd/server/flags.go +++ b/cmd/server/flags.go @@ -246,10 +246,10 @@ var flags = append([]cli.Flag{ Usage: "Disable version check in admin web ui.", Name: "skip-version-check", }, - &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_ADDONS"}, - Name: "addons", - Usage: "list of addon files", + &cli.StringFlag{ + EnvVars: []string{"WOODPECKER_ADDON_FORGE"}, + Name: "addon-forge", + Usage: "forge addon", }, // // backend options for pipeline compiler diff --git a/cmd/server/setup.go b/cmd/server/setup.go index be63d5c32..e2fa13e25 100644 --- a/cmd/server/setup.go +++ b/cmd/server/setup.go @@ -32,6 +32,7 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/server" "go.woodpecker-ci.org/woodpecker/v2/server/cache" "go.woodpecker-ci.org/woodpecker/v2/server/forge" + "go.woodpecker-ci.org/woodpecker/v2/server/forge/addon" "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket" "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucketdatacenter" "go.woodpecker-ci.org/woodpecker/v2/server/forge/gitea" @@ -40,8 +41,6 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/server/queue" "go.woodpecker-ci.org/woodpecker/v2/server/store" "go.woodpecker-ci.org/woodpecker/v2/server/store/datastore" - "go.woodpecker-ci.org/woodpecker/v2/shared/addon" - addonTypes "go.woodpecker-ci.org/woodpecker/v2/shared/addon/types" ) func setupStore(c *cli.Context) (store.Store, error) { @@ -107,15 +106,9 @@ func setupMembershipService(_ *cli.Context, r forge.Forge) cache.MembershipServi // setupForge helper function to set up the forge from the CLI arguments. func setupForge(c *cli.Context) (forge.Forge, error) { - addonForge, err := addon.Load[forge.Forge](c.StringSlice("addons"), addonTypes.TypeForge) - if err != nil { - return nil, err - } - if addonForge != nil { - return addonForge.Value, nil - } - switch { + case c.String("addon-forge") != "": + return addon.Load(c.String("addon-forge")) case c.Bool("github"): return setupGitHub(c) case c.Bool("gitlab"): diff --git a/docs/docs/20-usage/51-plugins/20-creating-plugins.md b/docs/docs/20-usage/51-plugins/20-creating-plugins.md index 44e0167ee..8a0ea5920 100644 --- a/docs/docs/20-usage/51-plugins/20-creating-plugins.md +++ b/docs/docs/20-usage/51-plugins/20-creating-plugins.md @@ -135,5 +135,5 @@ docker run --rm \ These should also be built for different OS/architectures. - Use [built-in env vars](../50-environment.md#built-in-environment-variables) where possible. - Do not use any configuration except settings (and internal env vars). This means: Don't require using [`environment`](../50-environment.md) and don't require specific secret names. -- Add a `docs.md` file, listing all your settings and plugin metadata ([example](https://codeberg.org/woodpecker-plugins/plugin-docker-buildx/src/branch/main/docs.md)). -- Add your plugin to the [plugin index](/plugins) using your `docs.md` ([the example above in the index](https://woodpecker-ci.org/plugins/Docker%20Buildx)). +- Add a `docs.md` file, listing all your settings and plugin metadata ([example](https://github.com/woodpecker-ci/plugin-git/blob/main/docs.md)). +- Add your plugin to the [plugin index](/plugins) using your `docs.md` ([the example above in the index](https://woodpecker-ci.org/plugins/Git%20Clone)). diff --git a/docs/docs/30-administration/10-server-config.md b/docs/docs/30-administration/10-server-config.md index 9f50065ed..70a262c85 100644 --- a/docs/docs/30-administration/10-server-config.md +++ b/docs/docs/30-administration/10-server-config.md @@ -473,12 +473,6 @@ Supported variables: - `owner`: the repo's owner - `repo`: the repo's name -### `WOODPECKER_ADDONS` - -> Default: empty - -List of addon files. See [addons](./75-addons/75-overview.md). - --- ### `WOODPECKER_LIMIT_MEM_SWAP` @@ -559,4 +553,8 @@ See [Bitbucket configuration](./11-forges/50-bitbucket.md#configuration) ### `WOODPECKER_GITLAB_...` -See [Gitlab configuration](./11-forges/40-gitlab.md#configuration) +See [GitLab configuration](./11-forges/40-gitlab.md#configuration) + +### `WOODPECKER_ADDON_FORGE` + +See [addon forges](./11-forges/100-addon.md). diff --git a/docs/docs/30-administration/11-forges/100-addon.md b/docs/docs/30-administration/11-forges/100-addon.md new file mode 100644 index 000000000..5c98149ac --- /dev/null +++ b/docs/docs/30-administration/11-forges/100-addon.md @@ -0,0 +1,68 @@ +# Addon forges + +If the forge you're using does not comply with [Woodpecker's requirements](../../92-development/02-core-ideas.md#forge) or your setup is too specific to be added to Woodpecker's core, you can write your own forge using an addon forge. + +:::warning +Addon forges are still experimental. Their implementation can change and break at any time. +::: + +:::danger +You need to trust the author of the addon forge you use. It can access authentication codes and other possibly sensitive information. +::: + +## Usage + +To use an addon forge, download the correct addon version. Then, you can add the following to your configuration: + +```ini +WOODPECKER_ADDON_FORGE=/path/to/your/addon/forge/file +``` + +In case you run Woodpecker as container, you probably want to mount the addon binary to `/opt/addons/`. + +### Bug reports + +If you experience bugs, please check which component has the issue. If it's the addon, **do not raise an issue in the main repository**, but rather use the separate addon repositories. To check which component is responsible for the bug, look at the logs. Logs from addons are marked with a special field `addon` containing their addon file name. + +## List of addon forges + +If you wrote or found an addon forge, please add it here so others can find it! + +_Be the first one to add your addon forge!_ + +## Creating addon forges + +Addons use RPC to communicate to the server and are implemented using the [`go-plugin` library](https://github.com/hashicorp/go-plugin). + +### Writing your code + +This example will use the Go language. + +Directly import Woodpecker's Go packages (`go.woodpecker-ci.org/woodpecker/woodpecker/v2`) and use the interfaces and types defined there. + +In the `main` function, just call `"go.woodpecker-ci.org/woodpecker/v2/server/forge/addon".Serve` with a `"go.woodpecker-ci.org/woodpecker/v2/server/forge".Forge` as argument. +This will take care of connecting the addon forge to the server. + +### Example structure + +```go +package main + +import ( + "context" + "net/http" + + "go.woodpecker-ci.org/woodpecker/v2/server/forge/addon" + forgeTypes "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v2/server/model" +) + +func main() { + addon.Serve(config{}) +} + +type config struct { +} + +// `config` must implement `"go.woodpecker-ci.org/woodpecker/v2/server/forge".Forge`. You must directly use Woodpecker's packages - see imports above. +``` diff --git a/docs/docs/30-administration/75-addons/20-creating-addons.md b/docs/docs/30-administration/75-addons/20-creating-addons.md deleted file mode 100644 index 283c456f4..000000000 --- a/docs/docs/30-administration/75-addons/20-creating-addons.md +++ /dev/null @@ -1,97 +0,0 @@ -# Creating addons - -Addons are written in Go. - -## Writing your code - -An addon consists of two variables/functions in Go. - -1. The `Type` variable. Specifies the type of the addon and must be directly accessed from `shared/addons/types/types.go`. -2. The `Addon` function which is the main point of your addon. - This function takes the `zerolog` logger you should use to log errors, warnings, etc. as argument. - - It returns two values: - - 1. The actual addon. For type reference see [table below](#return-types). - 2. An error. If this error is not `nil`, Woodpecker exits. - -Directly import Woodpecker's Go package (`go.woodpecker-ci.org/woodpecker/woodpecker/v2`) and use the interfaces and types defined there. - -### Return types - -| Addon type | Return type | -| ---------- | -------------------------------------------------------------------- | -| `Forge` | `"go.woodpecker-ci.org/woodpecker/woodpecker/v2/server/forge".Forge` | - -### Using configurations - -If you write a plugin for the server (`Forge` and the services), you can access the server config. - -Therefore, use the `"go.woodpecker-ci.org/woodpecker/v2/server".Config` variable. - -:::warning -The config is not available when your addon is initialized, i.e., the `Addon` function is called. -Only use the config in the interface methods. -::: - -## Compiling - -After you write your addon code, compile your addon: - -```sh -go build -buildmode plugin -``` - -The output file is your addon that is now ready to be used. - -## Restrictions - -Addons must directly depend on Woodpecker's core (`go.woodpecker-ci.org/woodpecker/woodpecker/v2`). -The addon must have been built with **exactly the same code** as the Woodpecker instance you'd like to use it on. This means: If you build your addon with a specific commit from Woodpecker `next`, you can likely only use it with the Woodpecker version compiled from this commit. -Also, if you change something inside Woodpecker without committing, it might fail because you need to recompile your addon with this code first. - -In addition to this, addons are only supported on Linux, FreeBSD, and macOS. - -:::info -It is recommended to at least support the latest version of Woodpecker. -::: - -### Compile for different versions - -As long as there are no changes to Woodpecker's interfaces, -or they are backwards-compatible, you can compile the addon for multiple versions -by changing the version of `go.woodpecker-ci.org/woodpecker/woodpecker/v2` using `go get` before compiling. - -## Logging - -The entrypoint receives a `zerolog.Logger` as input. **Do not use any other logging solution.** This logger follows the configuration of the Woodpecker instance and adds a special field `addon` to the log entries which allows users to find out which component is writing the log messages. - -## Example structure - -```go -package main - -import ( - "context" - "net/http" - - "github.com/rs/zerolog" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - addon_types "go.woodpecker-ci.org/woodpecker/v2/shared/addon/types" -) - -var Type = addon_types.TypeForge - -func Addon(logger zerolog.Logger) (forge.Forge, error) { - logger.Info().Msg("hello world from addon") - return &config{l: logger}, nil -} - -type config struct { - l zerolog.Logger -} - -// In this case, `config` must implement `forge.Forge`. You must directly use Woodpecker's packages - see imports above. -``` diff --git a/docs/docs/30-administration/75-addons/75-overview.md b/docs/docs/30-administration/75-addons/75-overview.md deleted file mode 100644 index 747dc4b36..000000000 --- a/docs/docs/30-administration/75-addons/75-overview.md +++ /dev/null @@ -1,40 +0,0 @@ -# Addons - -:::warning -Addons are still experimental. Their implementation can change and break at any time. -::: - -:::danger -You need to trust the author of the addons you use. Depending on their type, addons can access forge authentication codes, your secrets or other sensitive information. -::: - -To adapt Woodpecker to your needs beyond the [configuration](../10-server-config.md), Woodpecker has its own **addon** system, built ontop of [Go's internal plugin system](https://go.dev/pkg/plugin). - -Addons can be used for: - -- Forges - -## Restrictions - -Addons are restricted by how Go plugins work. This includes the following restrictions: - -- only supported on Linux, FreeBSD, and macOS -- addons must have been built for the correct Woodpecker version. If an addon is not provided specifically for this version, you likely won't be able to use it. - -## Usage - -To use an addon, download the addon version built for your Woodpecker version. Then, you can add the following to your configuration: - -```ini -WOODPECKER_ADDONS=/path/to/your/addon/file.so -``` - -In case you run Woodpecker as container, you probably want to mount the addon binaries to `/opt/addons/`. - -You can list multiple addons, Woodpecker will automatically determine their type. If you specify multiple addons with the same type, only the first one will be used. - -Using an addon always overwrites Woodpecker's internal setup. This means, that a forge addon will be used if specified, no matter what's configured for the forges natively supported by Woodpecker. - -### Bug reports - -If you experience bugs, please check which component has the issue. If it's the addon, **do not raise an issue in the main repository**, but rather use the separate addon repositories. To check which component is responsible for the bug, look at the logs. Logs from addons are marked with a special field `addon` containing their addon file name. diff --git a/docs/docs/30-administration/75-addons/_category_.yaml b/docs/docs/30-administration/75-addons/_category_.yaml deleted file mode 100644 index 4cd7380c5..000000000 --- a/docs/docs/30-administration/75-addons/_category_.yaml +++ /dev/null @@ -1,6 +0,0 @@ -label: 'Addons' -collapsible: true -collapsed: true -link: - type: 'doc' - id: 'overview' diff --git a/docs/docs/92-development/02-core-ideas.md b/docs/docs/92-development/02-core-ideas.md index 2f80661f4..8e0d6e292 100644 --- a/docs/docs/92-development/02-core-ideas.md +++ b/docs/docs/92-development/02-core-ideas.md @@ -8,7 +8,7 @@ ## Addons and extensions If you are wondering whether your contribution will be accepted to be merged in the Woodpecker core, or whether it's better to write an -[addon](../30-administration/75-addons/75-overview.md), [extension](../30-administration/100-external-configuration-api.md) or an +[addon forge](../30-administration/11-forges/100-addon.md), [extension](../30-administration/100-external-configuration-api.md) or an [external custom backend](../30-administration/22-backends/50-custom-backends.md), please check these points: - Is your change very specific to your setup and unlikely to be used by anyone else? diff --git a/go.mod b/go.mod index 45ce43c98..6d19445c7 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,8 @@ require ( github.com/google/go-github/v61 v61.0.0 github.com/google/tink/go v1.7.0 github.com/gorilla/securecookie v1.1.2 + github.com/hashicorp/go-hclog v1.2.0 + github.com/hashicorp/go-plugin v1.4.3 github.com/jellydator/ttlcache/v3 v3.2.0 github.com/joho/godotenv v1.5.1 github.com/kinbiko/jsonassert v1.1.1 @@ -119,9 +121,9 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -137,6 +139,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mholt/acmez v1.2.0 // indirect github.com/miekg/dns v1.1.57 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect @@ -144,6 +147,7 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oklog/run v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect diff --git a/go.sum b/go.sum index be0b36f2a..f1b7585a9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= code.gitea.io/sdk/gitea v0.17.1 h1:3jCPOG2ojbl8AcfaUCRYLT5MUcBMFwS0OSK2mA5Zok8= code.gitea.io/sdk/gitea v0.17.1/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM= codeberg.org/6543/go-yaml2json v1.0.0 h1:heGqo9VEi7gY2yNqjj7X4ADs5nzlFIbGsJtgYDLrnig= @@ -44,6 +45,7 @@ github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= @@ -66,6 +68,7 @@ github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= @@ -108,6 +111,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/expr-lang/expr v1.16.3 h1:NLldf786GffptcXNxxJx5dQ+FzeWDKChBDqOOwyK8to= github.com/expr-lang/expr v1.16.3/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -173,7 +178,12 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -211,12 +221,17 @@ github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pw github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= +github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= @@ -263,6 +278,8 @@ github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= @@ -337,6 +354,9 @@ github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/moby v24.0.9+incompatible h1:Z/hFbZJqC5Fmuf6jesMLdHU71CMAgdiSJ1ZYey+bFmg= @@ -363,6 +383,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/neticdk/go-bitbucket v1.0.0 h1:FPvHEgPHoDwD2VHbpyu2R2gnoWQ867RxZd2FivS4wSw= github.com/neticdk/go-bitbucket v1.0.0/go.mod h1:IrHeWO1CrNi0DlOvfhAA9bGRSeNSUB6/SAfzmwbA5aU= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -388,6 +410,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= @@ -519,6 +542,10 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -528,7 +555,11 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -544,9 +575,11 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -554,6 +587,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -609,8 +643,11 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -631,10 +668,19 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= @@ -664,6 +710,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw= k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80= diff --git a/pipeline/errors/error.go b/pipeline/errors/error.go index 1a0ae167c..f8bba4c67 100644 --- a/pipeline/errors/error.go +++ b/pipeline/errors/error.go @@ -2,28 +2,12 @@ package errors import ( "errors" - "fmt" "go.uber.org/multierr" + + "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" ) -type PipelineErrorType string - -const ( - PipelineErrorTypeLinter PipelineErrorType = "linter" // some error with the config syntax - PipelineErrorTypeDeprecation PipelineErrorType = "deprecation" // using some deprecated feature - PipelineErrorTypeCompiler PipelineErrorType = "compiler" // some error with the config semantics - PipelineErrorTypeGeneric PipelineErrorType = "generic" // some generic error - PipelineErrorTypeBadHabit PipelineErrorType = "bad_habit" // some bad-habit error -) - -type PipelineError struct { - Type PipelineErrorType `json:"type"` - Message string `json:"message"` - IsWarning bool `json:"is_warning"` - Data any `json:"data"` -} - type LinterErrorData struct { File string `json:"file"` Field string `json:"field"` @@ -35,12 +19,8 @@ type DeprecationErrorData struct { Docs string `json:"docs"` } -func (e *PipelineError) Error() string { - return fmt.Sprintf("[%s] %s", e.Type, e.Message) -} - -func (e *PipelineError) GetLinterData() *LinterErrorData { - if e.Type != PipelineErrorTypeLinter { +func GetLinterData(e *types.PipelineError) *LinterErrorData { + if e.Type != types.PipelineErrorTypeLinter { return nil } @@ -51,16 +31,16 @@ func (e *PipelineError) GetLinterData() *LinterErrorData { return nil } -func GetPipelineErrors(err error) []*PipelineError { - var pipelineErrors []*PipelineError +func GetPipelineErrors(err error) []*types.PipelineError { + var pipelineErrors []*types.PipelineError for _, _err := range multierr.Errors(err) { - var err *PipelineError + var err *types.PipelineError if errors.As(_err, &err) { pipelineErrors = append(pipelineErrors, err) } else { - pipelineErrors = append(pipelineErrors, &PipelineError{ + pipelineErrors = append(pipelineErrors, &types.PipelineError{ Message: _err.Error(), - Type: PipelineErrorTypeGeneric, + Type: types.PipelineErrorTypeGeneric, }) } } diff --git a/pipeline/errors/error_test.go b/pipeline/errors/error_test.go index b6b328e7e..8648c4048 100644 --- a/pipeline/errors/error_test.go +++ b/pipeline/errors/error_test.go @@ -8,6 +8,7 @@ import ( "go.uber.org/multierr" pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" ) func TestGetPipelineErrors(t *testing.T) { @@ -16,7 +17,7 @@ func TestGetPipelineErrors(t *testing.T) { tests := []struct { title string err error - expected []*pipeline_errors.PipelineError + expected []*types.PipelineError }{ { title: "nil error", @@ -25,10 +26,10 @@ func TestGetPipelineErrors(t *testing.T) { }, { title: "warning", - err: &pipeline_errors.PipelineError{ + err: &types.PipelineError{ IsWarning: true, }, - expected: []*pipeline_errors.PipelineError{ + expected: []*types.PipelineError{ { IsWarning: true, }, @@ -36,10 +37,10 @@ func TestGetPipelineErrors(t *testing.T) { }, { title: "pipeline error", - err: &pipeline_errors.PipelineError{ + err: &types.PipelineError{ IsWarning: false, }, - expected: []*pipeline_errors.PipelineError{ + expected: []*types.PipelineError{ { IsWarning: false, }, @@ -48,14 +49,14 @@ func TestGetPipelineErrors(t *testing.T) { { title: "multiple warnings", err: multierr.Combine( - &pipeline_errors.PipelineError{ + &types.PipelineError{ IsWarning: true, }, - &pipeline_errors.PipelineError{ + &types.PipelineError{ IsWarning: true, }, ), - expected: []*pipeline_errors.PipelineError{ + expected: []*types.PipelineError{ { IsWarning: true, }, @@ -67,15 +68,15 @@ func TestGetPipelineErrors(t *testing.T) { { title: "multiple errors and warnings", err: multierr.Combine( - &pipeline_errors.PipelineError{ + &types.PipelineError{ IsWarning: true, }, - &pipeline_errors.PipelineError{ + &types.PipelineError{ IsWarning: false, }, errors.New("some error"), ), - expected: []*pipeline_errors.PipelineError{ + expected: []*types.PipelineError{ { IsWarning: true, }, @@ -83,7 +84,7 @@ func TestGetPipelineErrors(t *testing.T) { IsWarning: false, }, { - Type: pipeline_errors.PipelineErrorTypeGeneric, + Type: types.PipelineErrorTypeGeneric, IsWarning: false, Message: "some error", }, @@ -111,14 +112,14 @@ func TestHasBlockingErrors(t *testing.T) { }, { title: "warning", - err: &pipeline_errors.PipelineError{ + err: &types.PipelineError{ IsWarning: true, }, expected: false, }, { title: "pipeline error", - err: &pipeline_errors.PipelineError{ + err: &types.PipelineError{ IsWarning: false, }, expected: true, @@ -126,10 +127,10 @@ func TestHasBlockingErrors(t *testing.T) { { title: "multiple warnings", err: multierr.Combine( - &pipeline_errors.PipelineError{ + &types.PipelineError{ IsWarning: true, }, - &pipeline_errors.PipelineError{ + &types.PipelineError{ IsWarning: true, }, ), @@ -138,10 +139,10 @@ func TestHasBlockingErrors(t *testing.T) { { title: "multiple errors and warnings", err: multierr.Combine( - &pipeline_errors.PipelineError{ + &types.PipelineError{ IsWarning: true, }, - &pipeline_errors.PipelineError{ + &types.PipelineError{ IsWarning: false, }, errors.New("some error"), diff --git a/pipeline/errors/types/errors.go b/pipeline/errors/types/errors.go new file mode 100644 index 000000000..752eb6904 --- /dev/null +++ b/pipeline/errors/types/errors.go @@ -0,0 +1,24 @@ +package types + +import "fmt" + +type PipelineErrorType string + +const ( + PipelineErrorTypeLinter PipelineErrorType = "linter" // some error with the config syntax + PipelineErrorTypeDeprecation PipelineErrorType = "deprecation" // using some deprecated feature + PipelineErrorTypeCompiler PipelineErrorType = "compiler" // some error with the config semantics + PipelineErrorTypeGeneric PipelineErrorType = "generic" // some generic error + PipelineErrorTypeBadHabit PipelineErrorType = "bad_habit" // some bad-habit error +) + +type PipelineError struct { + Type PipelineErrorType `json:"type"` + Message string `json:"message"` + IsWarning bool `json:"is_warning"` + Data any `json:"data"` +} + +func (e *PipelineError) Error() string { + return fmt.Sprintf("[%s] %s", e.Type, e.Message) +} diff --git a/pipeline/frontend/yaml/linter/error.go b/pipeline/frontend/yaml/linter/error.go index c28a44ae8..09e243016 100644 --- a/pipeline/frontend/yaml/linter/error.go +++ b/pipeline/frontend/yaml/linter/error.go @@ -16,11 +16,12 @@ package linter import ( "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" + errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" ) -func newLinterError(message, file, field string, isWarning bool) *errors.PipelineError { - return &errors.PipelineError{ - Type: errors.PipelineErrorTypeLinter, +func newLinterError(message, file, field string, isWarning bool) *errorTypes.PipelineError { + return &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeLinter, Message: message, Data: &errors.LinterErrorData{File: file, Field: field}, IsWarning: isWarning, diff --git a/pipeline/frontend/yaml/linter/linter.go b/pipeline/frontend/yaml/linter/linter.go index dd22c9fff..646091972 100644 --- a/pipeline/frontend/yaml/linter/linter.go +++ b/pipeline/frontend/yaml/linter/linter.go @@ -21,6 +21,7 @@ import ( "go.uber.org/multierr" "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" + errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter/schema" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types" ) @@ -210,8 +211,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) { } if parsed.PipelineDoNotUseIt.ContainerList != nil { - err = multierr.Append(err, &errors.PipelineError{ - Type: errors.PipelineErrorTypeDeprecation, + err = multierr.Append(err, &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeDeprecation, Message: "Please use 'steps:' instead of deprecated 'pipeline:' list", Data: errors.DeprecationErrorData{ File: config.File, @@ -223,8 +224,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) { } if parsed.PlatformDoNotUseIt != "" { - err = multierr.Append(err, &errors.PipelineError{ - Type: errors.PipelineErrorTypeDeprecation, + err = multierr.Append(err, &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeDeprecation, Message: "Please use labels instead of deprecated 'platform' filters", Data: errors.DeprecationErrorData{ File: config.File, @@ -236,8 +237,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) { } if parsed.BranchesDoNotUseIt != nil { - err = multierr.Append(err, &errors.PipelineError{ - Type: errors.PipelineErrorTypeDeprecation, + err = multierr.Append(err, &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeDeprecation, Message: "Please use global when instead of deprecated 'branches' filter", Data: errors.DeprecationErrorData{ File: config.File, @@ -250,8 +251,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) { for _, step := range parsed.Steps.ContainerList { if step.Group != "" { - err = multierr.Append(err, &errors.PipelineError{ - Type: errors.PipelineErrorTypeDeprecation, + err = multierr.Append(err, &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeDeprecation, Message: "Please use depends_on instead of deprecated 'group' setting", Data: errors.DeprecationErrorData{ File: config.File, @@ -265,8 +266,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) { for i, c := range parsed.When.Constraints { if len(c.Event.Exclude) != 0 { - err = multierr.Append(err, &errors.PipelineError{ - Type: errors.PipelineErrorTypeDeprecation, + err = multierr.Append(err, &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeDeprecation, Message: "Please only use allow lists for events", Data: errors.DeprecationErrorData{ File: config.File, @@ -281,8 +282,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) { for _, step := range parsed.Steps.ContainerList { for i, c := range step.When.Constraints { if len(c.Event.Exclude) != 0 { - err = multierr.Append(err, &errors.PipelineError{ - Type: errors.PipelineErrorTypeDeprecation, + err = multierr.Append(err, &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeDeprecation, Message: "Please only use allow lists for events", Data: errors.DeprecationErrorData{ File: config.File, @@ -298,8 +299,8 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) { for _, step := range parsed.Steps.ContainerList { for i, c := range step.Secrets.Secrets { if c.Source != c.Target { - err = multierr.Append(err, &errors.PipelineError{ - Type: errors.PipelineErrorTypeDeprecation, + err = multierr.Append(err, &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeDeprecation, Message: "Secrets alternative names are deprecated, use environment with from_secret", Data: errors.DeprecationErrorData{ File: config.File, @@ -348,8 +349,8 @@ func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) { } } if field != "" { - err = multierr.Append(err, &errors.PipelineError{ - Type: errors.PipelineErrorTypeBadHabit, + err = multierr.Append(err, &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeBadHabit, Message: "Please set an event filter on all when branches", Data: errors.LinterErrorData{ File: config.File, diff --git a/pipeline/frontend/yaml/matrix/matrix.go b/pipeline/frontend/yaml/matrix/matrix.go index b89dfc5d9..373c7bec9 100644 --- a/pipeline/frontend/yaml/matrix/matrix.go +++ b/pipeline/frontend/yaml/matrix/matrix.go @@ -19,7 +19,7 @@ import ( "codeberg.org/6543/xyaml" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" + errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" ) const ( @@ -116,7 +116,7 @@ func parse(raw []byte) (Matrix, error) { Matrix map[string][]string }{} if err := xyaml.Unmarshal(raw, &data); err != nil { - return nil, &errors.PipelineError{Message: err.Error(), Type: errors.PipelineErrorTypeCompiler} + return nil, &errorTypes.PipelineError{Message: err.Error(), Type: errorTypes.PipelineErrorTypeCompiler} } return data.Matrix, nil } @@ -129,7 +129,7 @@ func parseList(raw []byte) ([]Axis, error) { }{} if err := xyaml.Unmarshal(raw, &data); err != nil { - return nil, &errors.PipelineError{Message: err.Error(), Type: errors.PipelineErrorTypeCompiler} + return nil, &errorTypes.PipelineError{Message: err.Error(), Type: errorTypes.PipelineErrorTypeCompiler} } return data.Matrix.Include, nil } diff --git a/server/forge/addon/args.go b/server/forge/addon/args.go new file mode 100644 index 000000000..038e8b0e0 --- /dev/null +++ b/server/forge/addon/args.go @@ -0,0 +1,154 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package addon + +import ( + "go.woodpecker-ci.org/woodpecker/v2/server/model" +) + +type argumentsAuth struct { + Token string `json:"token"` + Secret string `json:"secret"` +} + +type argumentsRepo struct { + U *modelUser `json:"u"` + RemoteID model.ForgeRemoteID `json:"remote_id"` + Owner string `json:"owner"` + Name string `json:"name"` +} + +type argumentsFileDir struct { + U *modelUser `json:"u"` + R *modelRepo `json:"r"` + B *model.Pipeline `json:"b"` + F string `json:"f"` +} + +type argumentsStatus struct { + U *modelUser `json:"u"` + R *modelRepo `json:"r"` + B *model.Pipeline `json:"b"` + P *model.Workflow `json:"p"` +} + +type argumentsNetrc struct { + U *modelUser `json:"u"` + R *modelRepo `json:"r"` +} + +type argumentsActivateDeactivate struct { + U *modelUser `json:"u"` + R *modelRepo `json:"r"` + Link string `json:"link"` +} + +type argumentsBranchesPullRequests struct { + U *modelUser `json:"u"` + R *modelRepo `json:"r"` + P *model.ListOptions `json:"p"` +} + +type argumentsBranchHead struct { + U *modelUser `json:"u"` + R *modelRepo `json:"r"` + Branch string `json:"branch"` +} + +type argumentsOrgMembershipOrg struct { + U *modelUser `json:"u"` + Org string `json:"org"` +} + +type responseHook struct { + Repo *modelRepo `json:"repo"` + Pipeline *model.Pipeline `json:"pipeline"` +} + +type responseLogin struct { + User *modelUser `json:"user"` + RedirectURL string `json:"redirect_url"` +} + +type httpRequest struct { + Method string `json:"method"` + URL string `json:"url"` + Header map[string][]string `json:"header"` + Form map[string][]string `json:"form"` + Body []byte `json:"body"` +} + +// modelUser is an extension of model.User to marshal all fields to JSON +type modelUser struct { + User *model.User `json:"user"` + + ForgeRemoteID model.ForgeRemoteID `json:"forge_remote_id"` + + // Token is the oauth2 token. + Token string `json:"token"` + + // Secret is the oauth2 token secret. + Secret string `json:"secret"` + + // Expiry is the token and secret expiration timestamp. + Expiry int64 `json:"expiry"` + + // Hash is a unique token used to sign tokens. + Hash string `json:"hash"` +} + +func (m *modelUser) asModel() *model.User { + m.User.ForgeRemoteID = m.ForgeRemoteID + m.User.Token = m.Token + m.User.Secret = m.Secret + m.User.Expiry = m.Expiry + m.User.Hash = m.Hash + return m.User +} + +func modelUserFromModel(u *model.User) *modelUser { + return &modelUser{ + User: u, + ForgeRemoteID: u.ForgeRemoteID, + Token: u.Token, + Secret: u.Secret, + Expiry: u.Expiry, + Hash: u.Hash, + } +} + +// modelRepo is an extension of model.Repo to marshal all fields to JSON +type modelRepo struct { + Repo *model.Repo `json:"repo"` + UserID int64 `json:"user_id"` + Hash string `json:"hash"` + Perm *model.Perm `json:"perm"` +} + +func (m *modelRepo) asModel() *model.Repo { + m.Repo.UserID = m.UserID + m.Repo.Hash = m.Hash + m.Repo.Perm = m.Perm + return m.Repo +} + +func modelRepoFromModel(r *model.Repo) *modelRepo { + return &modelRepo{ + Repo: r, + UserID: r.UserID, + Hash: r.Hash, + Perm: r.Perm, + } +} diff --git a/server/forge/addon/client.go b/server/forge/addon/client.go new file mode 100644 index 000000000..323b0e8ab --- /dev/null +++ b/server/forge/addon/client.go @@ -0,0 +1,377 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package addon + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/rpc" + "os/exec" + + "github.com/hashicorp/go-plugin" + "github.com/rs/zerolog/log" + + "go.woodpecker-ci.org/woodpecker/v2/server/forge" + "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v2/server/model" +) + +// make sure RPC implements forge.Forge +var _ forge.Forge = new(RPC) + +func Load(file string) (forge.Forge, error) { + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: HandshakeConfig, + Plugins: map[string]plugin.Plugin{ + pluginKey: &Plugin{}, + }, + Cmd: exec.Command(file), + Logger: &clientLogger{ + logger: log.With().Str("addon", file).Logger(), + }, + }) + // TODO defer client.Kill() + + rpcClient, err := client.Client() + if err != nil { + return nil, err + } + + raw, err := rpcClient.Dispense(pluginKey) + if err != nil { + return nil, err + } + + extension, _ := raw.(forge.Forge) + return extension, nil +} + +type RPC struct { + client *rpc.Client +} + +func (g *RPC) Name() string { + var resp string + _ = g.client.Call("Plugin.Name", nil, &resp) + return resp +} + +func (g *RPC) URL() string { + var resp string + _ = g.client.Call("Plugin.URL", nil, &resp) + return resp +} + +func (g *RPC) Login(_ context.Context, r *types.OAuthRequest) (*model.User, string, error) { + args, err := json.Marshal(r) + if err != nil { + return nil, "", err + } + var jsonResp []byte + err = g.client.Call("Plugin.Login", args, &jsonResp) + if err != nil { + return nil, "", err + } + + var resp responseLogin + err = json.Unmarshal(jsonResp, &resp) + if err != nil { + return nil, "", err + } + + return resp.User.asModel(), resp.RedirectURL, nil +} + +func (g *RPC) Auth(_ context.Context, token, secret string) (string, error) { + args, err := json.Marshal(&argumentsAuth{ + Token: token, + Secret: secret, + }) + if err != nil { + return "", err + } + var resp string + return resp, g.client.Call("Plugin.Auth", args, &resp) +} + +func (g *RPC) Teams(_ context.Context, u *model.User) ([]*model.Team, error) { + args, err := json.Marshal(modelUserFromModel(u)) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.Teams", args, &jsonResp) + if err != nil { + return nil, err + } + + var resp []*model.Team + return resp, json.Unmarshal(jsonResp, &resp) +} + +func (g *RPC) Repo(_ context.Context, u *model.User, remoteID model.ForgeRemoteID, owner, name string) (*model.Repo, error) { + args, err := json.Marshal(&argumentsRepo{ + U: modelUserFromModel(u), + RemoteID: remoteID, + Owner: owner, + Name: name, + }) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.Repo", args, &jsonResp) + if err != nil { + return nil, err + } + + var resp *modelRepo + err = json.Unmarshal(jsonResp, resp) + if err != nil { + return nil, err + } + return resp.asModel(), nil +} + +func (g *RPC) Repos(_ context.Context, u *model.User) ([]*model.Repo, error) { + args, err := json.Marshal(modelUserFromModel(u)) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.Repos", args, &jsonResp) + if err != nil { + return nil, err + } + + var resp []*modelRepo + err = json.Unmarshal(jsonResp, &resp) + if err != nil { + return nil, err + } + var modelRepos []*model.Repo + for _, repo := range resp { + modelRepos = append(modelRepos, repo.asModel()) + } + return modelRepos, nil +} + +func (g *RPC) File(_ context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]byte, error) { + args, err := json.Marshal(&argumentsFileDir{ + U: modelUserFromModel(u), + R: modelRepoFromModel(r), + B: b, + F: f, + }) + if err != nil { + return nil, err + } + var resp []byte + return resp, g.client.Call("Plugin.File", args, &resp) +} + +func (g *RPC) Dir(_ context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]*types.FileMeta, error) { + args, err := json.Marshal(&argumentsFileDir{ + U: modelUserFromModel(u), + R: modelRepoFromModel(r), + B: b, + F: f, + }) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.Dir", args, &jsonResp) + if err != nil { + return nil, err + } + var resp []*types.FileMeta + return resp, json.Unmarshal(jsonResp, &resp) +} + +func (g *RPC) Status(_ context.Context, u *model.User, r *model.Repo, b *model.Pipeline, p *model.Workflow) error { + args, err := json.Marshal(&argumentsStatus{ + U: modelUserFromModel(u), + R: modelRepoFromModel(r), + B: b, + P: p, + }) + if err != nil { + return err + } + var jsonResp []byte + return g.client.Call("Plugin.Status", args, &jsonResp) +} + +func (g *RPC) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) { + args, err := json.Marshal(&argumentsNetrc{ + U: modelUserFromModel(u), + R: modelRepoFromModel(r), + }) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.Netrc", args, &jsonResp) + if err != nil { + return nil, err + } + var resp *model.Netrc + return resp, json.Unmarshal(jsonResp, &resp) +} + +func (g *RPC) Activate(_ context.Context, u *model.User, r *model.Repo, link string) error { + args, err := json.Marshal(&argumentsActivateDeactivate{ + U: modelUserFromModel(u), + R: modelRepoFromModel(r), + Link: link, + }) + if err != nil { + return err + } + var jsonResp []byte + return g.client.Call("Plugin.Activate", args, &jsonResp) +} + +func (g *RPC) Deactivate(_ context.Context, u *model.User, r *model.Repo, link string) error { + args, err := json.Marshal(&argumentsActivateDeactivate{ + U: modelUserFromModel(u), + R: modelRepoFromModel(r), + Link: link, + }) + if err != nil { + return err + } + var jsonResp []byte + return g.client.Call("Plugin.Deactivate", args, &jsonResp) +} + +func (g *RPC) Branches(_ context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]string, error) { + args, err := json.Marshal(&argumentsBranchesPullRequests{ + U: modelUserFromModel(u), + R: modelRepoFromModel(r), + P: p, + }) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.Branches", args, &jsonResp) + if err != nil { + return nil, err + } + var resp []string + return resp, json.Unmarshal(jsonResp, &resp) +} + +func (g *RPC) BranchHead(_ context.Context, u *model.User, r *model.Repo, branch string) (*model.Commit, error) { + args, err := json.Marshal(&argumentsBranchHead{ + U: modelUserFromModel(u), + R: modelRepoFromModel(r), + Branch: branch, + }) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.BranchHead", args, &jsonResp) + if err != nil { + return nil, err + } + var resp *model.Commit + return resp, json.Unmarshal(jsonResp, &resp) +} + +func (g *RPC) PullRequests(_ context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]*model.PullRequest, error) { + args, err := json.Marshal(&argumentsBranchesPullRequests{ + U: modelUserFromModel(u), + R: modelRepoFromModel(r), + P: p, + }) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.PullRequests", args, &jsonResp) + if err != nil { + return nil, err + } + var resp []*model.PullRequest + return resp, json.Unmarshal(jsonResp, &resp) +} + +func (g *RPC) Hook(_ context.Context, r *http.Request) (*model.Repo, *model.Pipeline, error) { + body, err := io.ReadAll(r.Body) + if err != nil { + return nil, nil, err + } + args, err := json.Marshal(&httpRequest{ + Method: r.Method, + URL: r.URL.String(), + Header: r.Header, + Form: r.Form, + Body: body, + }) + if err != nil { + return nil, nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.Hook", args, &jsonResp) + if err != nil { + return nil, nil, err + } + var resp responseHook + err = json.Unmarshal(jsonResp, &resp) + if err != nil { + return nil, nil, err + } + return resp.Repo.asModel(), resp.Pipeline, nil +} + +func (g *RPC) OrgMembership(_ context.Context, u *model.User, org string) (*model.OrgPerm, error) { + args, err := json.Marshal(&argumentsOrgMembershipOrg{ + U: modelUserFromModel(u), + Org: org, + }) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.OrgMembership", args, &jsonResp) + if err != nil { + return nil, err + } + var resp *model.OrgPerm + return resp, json.Unmarshal(jsonResp, &resp) +} + +func (g *RPC) Org(_ context.Context, u *model.User, org string) (*model.Org, error) { + args, err := json.Marshal(&argumentsOrgMembershipOrg{ + U: modelUserFromModel(u), + Org: org, + }) + if err != nil { + return nil, err + } + var jsonResp []byte + err = g.client.Call("Plugin.Org", args, &jsonResp) + if err != nil { + return nil, err + } + var resp *model.Org + return resp, json.Unmarshal(jsonResp, &resp) +} diff --git a/server/forge/addon/logger.go b/server/forge/addon/logger.go new file mode 100644 index 000000000..84ebd71b9 --- /dev/null +++ b/server/forge/addon/logger.go @@ -0,0 +1,165 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package addon + +import ( + "bytes" + "io" + stdlog "log" + + "github.com/hashicorp/go-hclog" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +type clientLogger struct { + logger zerolog.Logger + name string + withArgs []any +} + +func convertLvl(level hclog.Level) zerolog.Level { + switch level { + case hclog.Error: + return zerolog.ErrorLevel + case hclog.Warn: + return zerolog.WarnLevel + case hclog.Info: + return zerolog.InfoLevel + case hclog.Debug: + return zerolog.DebugLevel + case hclog.Trace: + return zerolog.TraceLevel + } + return zerolog.NoLevel +} + +func (c *clientLogger) applyArgs(args []any) *zerolog.Logger { + var key string + logger := c.logger.With() + args = append(args, c.withArgs) + for i, arg := range args { + switch { + case key != "": + logger.Any(key, arg) + key = "" + case i == len(args)-1: + logger.Any(hclog.MissingKey, arg) + default: + + key, _ = arg.(string) + } + } + l := logger.Logger() + return &l +} + +func (c *clientLogger) Log(level hclog.Level, msg string, args ...any) { + c.applyArgs(args).WithLevel(convertLvl(level)).Msg(msg) +} + +func (c *clientLogger) Trace(msg string, args ...any) { + c.applyArgs(args).Trace().Msg(msg) +} + +func (c *clientLogger) Debug(msg string, args ...any) { + c.applyArgs(args).Debug().Msg(msg) +} + +func (c *clientLogger) Info(msg string, args ...any) { + c.applyArgs(args).Info().Msg(msg) +} + +func (c *clientLogger) Warn(msg string, args ...any) { + c.applyArgs(args).Warn().Msg(msg) +} + +func (c *clientLogger) Error(msg string, args ...any) { + c.applyArgs(args).Error().Msg(msg) +} + +func (c *clientLogger) IsTrace() bool { + return log.Logger.GetLevel() >= zerolog.TraceLevel +} + +func (c *clientLogger) IsDebug() bool { + return log.Logger.GetLevel() >= zerolog.DebugLevel +} + +func (c *clientLogger) IsInfo() bool { + return log.Logger.GetLevel() >= zerolog.InfoLevel +} + +func (c *clientLogger) IsWarn() bool { + return log.Logger.GetLevel() >= zerolog.WarnLevel +} + +func (c *clientLogger) IsError() bool { + return log.Logger.GetLevel() >= zerolog.ErrorLevel +} + +func (c *clientLogger) ImpliedArgs() []any { + return c.withArgs +} + +func (c *clientLogger) With(args ...any) hclog.Logger { + return &clientLogger{ + logger: c.logger, + name: c.name, + withArgs: args, + } +} + +func (c *clientLogger) Name() string { + return c.name +} + +func (c *clientLogger) Named(name string) hclog.Logger { + curr := c.name + if curr != "" { + curr = c.name + "." + } + return c.ResetNamed(curr + name) +} + +func (c *clientLogger) ResetNamed(name string) hclog.Logger { + return &clientLogger{ + logger: c.logger, + name: name, + withArgs: c.withArgs, + } +} + +func (c *clientLogger) SetLevel(level hclog.Level) { + c.logger = c.logger.Level(convertLvl(level)) +} + +func (c *clientLogger) StandardLogger(opts *hclog.StandardLoggerOptions) *stdlog.Logger { + return stdlog.New(c.StandardWriter(opts), "", 0) +} + +func (c *clientLogger) StandardWriter(*hclog.StandardLoggerOptions) io.Writer { + return ioAdapter{logger: c.logger} +} + +type ioAdapter struct { + logger zerolog.Logger +} + +func (i ioAdapter) Write(p []byte) (n int, err error) { + str := string(bytes.TrimRight(p, " \t\n")) + i.logger.Log().Msg(str) + return len(p), nil +} diff --git a/server/forge/addon/plugin.go b/server/forge/addon/plugin.go new file mode 100644 index 000000000..21099cf68 --- /dev/null +++ b/server/forge/addon/plugin.go @@ -0,0 +1,43 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package addon + +import ( + "net/rpc" + + "github.com/hashicorp/go-plugin" + + "go.woodpecker-ci.org/woodpecker/v2/server/forge" +) + +const pluginKey = "forge" + +var HandshakeConfig = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "WOODPECKER_FORGE_ADDON_PLUGIN", + MagicCookieValue: "woodpecker-plugin-magic-cookie-value", +} + +type Plugin struct { + Impl forge.Forge +} + +func (p *Plugin) Server(*plugin.MuxBroker) (any, error) { + return &RPCServer{Impl: p.Impl}, nil +} + +func (*Plugin) Client(_ *plugin.MuxBroker, c *rpc.Client) (any, error) { + return &RPC{client: c}, nil +} diff --git a/server/forge/addon/server.go b/server/forge/addon/server.go new file mode 100644 index 000000000..a65967eaa --- /dev/null +++ b/server/forge/addon/server.go @@ -0,0 +1,278 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package addon + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + + "github.com/hashicorp/go-plugin" + + "go.woodpecker-ci.org/woodpecker/v2/server/forge" + "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" +) + +func Serve(impl forge.Forge) { + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: HandshakeConfig, + Plugins: map[string]plugin.Plugin{ + pluginKey: &Plugin{Impl: impl}, + }, + }) +} + +func mkCtx() context.Context { + return context.Background() +} + +type RPCServer struct { + Impl forge.Forge +} + +func (s *RPCServer) Name(_ []byte, resp *string) error { + *resp = s.Impl.Name() + return nil +} + +func (s *RPCServer) URL(_ []byte, resp *string) error { + *resp = s.Impl.URL() + return nil +} + +func (s *RPCServer) Teams(args []byte, resp *[]byte) error { + var a *modelUser + err := json.Unmarshal(args, a) + if err != nil { + return err + } + teams, err := s.Impl.Teams(mkCtx(), a.asModel()) + if err != nil { + return err + } + *resp, err = json.Marshal(teams) + return err +} + +func (s *RPCServer) Repo(args []byte, resp *[]byte) error { + var a argumentsRepo + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + repos, err := s.Impl.Repo(mkCtx(), a.U.asModel(), a.RemoteID, a.Owner, a.Name) + if err != nil { + return err + } + *resp, err = json.Marshal(modelRepoFromModel(repos)) + return err +} + +func (s *RPCServer) Repos(args []byte, resp *[]byte) error { + var a *modelUser + err := json.Unmarshal(args, a) + if err != nil { + return err + } + repos, err := s.Impl.Repos(mkCtx(), a.asModel()) + if err != nil { + return err + } + var modelRepos []*modelRepo + for _, repo := range repos { + modelRepos = append(modelRepos, modelRepoFromModel(repo)) + } + *resp, err = json.Marshal(modelRepos) + return err +} + +func (s *RPCServer) File(args []byte, resp *[]byte) error { + var a argumentsFileDir + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + *resp, err = s.Impl.File(mkCtx(), a.U.asModel(), a.R.asModel(), a.B, a.F) + return err +} + +func (s *RPCServer) Dir(args []byte, resp *[]byte) error { + var a argumentsFileDir + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + meta, err := s.Impl.Dir(mkCtx(), a.U.asModel(), a.R.asModel(), a.B, a.F) + if err != nil { + return err + } + *resp, err = json.Marshal(meta) + return err +} + +func (s *RPCServer) Status(args []byte, resp *[]byte) error { + var a argumentsStatus + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + *resp = []byte{} + return s.Impl.Status(mkCtx(), a.U.asModel(), a.R.asModel(), a.B, a.P) +} + +func (s *RPCServer) Netrc(args []byte, resp *[]byte) error { + var a argumentsNetrc + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + netrc, err := s.Impl.Netrc(a.U.asModel(), a.R.asModel()) + if err != nil { + return err + } + *resp, err = json.Marshal(netrc) + return err +} + +func (s *RPCServer) Activate(args []byte, resp *[]byte) error { + var a argumentsActivateDeactivate + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + *resp = []byte{} + return s.Impl.Activate(mkCtx(), a.U.asModel(), a.R.asModel(), a.Link) +} + +func (s *RPCServer) Deactivate(args []byte, resp *[]byte) error { + var a argumentsActivateDeactivate + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + *resp = []byte{} + return s.Impl.Deactivate(mkCtx(), a.U.asModel(), a.R.asModel(), a.Link) +} + +func (s *RPCServer) Branches(args []byte, resp *[]byte) error { + var a argumentsBranchesPullRequests + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + branches, err := s.Impl.Branches(mkCtx(), a.U.asModel(), a.R.asModel(), a.P) + if err != nil { + return err + } + *resp, err = json.Marshal(branches) + return err +} + +func (s *RPCServer) BranchHead(args []byte, resp *[]byte) error { + var a argumentsBranchHead + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + commit, err := s.Impl.BranchHead(mkCtx(), a.U.asModel(), a.R.asModel(), a.Branch) + if err != nil { + return err + } + *resp, err = json.Marshal(commit) + return err +} + +func (s *RPCServer) PullRequests(args []byte, resp *[]byte) error { + var a argumentsBranchesPullRequests + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + prs, err := s.Impl.PullRequests(mkCtx(), a.U.asModel(), a.R.asModel(), a.P) + if err != nil { + return err + } + *resp, err = json.Marshal(prs) + return err +} + +func (s *RPCServer) OrgMembership(args []byte, resp *[]byte) error { + var a argumentsOrgMembershipOrg + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + org, err := s.Impl.OrgMembership(mkCtx(), a.U.asModel(), a.Org) + if err != nil { + return err + } + *resp, err = json.Marshal(org) + return err +} + +func (s *RPCServer) Org(args []byte, resp *[]byte) error { + var a argumentsOrgMembershipOrg + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + org, err := s.Impl.Org(mkCtx(), a.U.asModel(), a.Org) + if err != nil { + return err + } + *resp, err = json.Marshal(org) + return err +} + +func (s *RPCServer) Hook(args []byte, resp *[]byte) error { + var a httpRequest + err := json.Unmarshal(args, &a) + if err != nil { + return err + } + req, err := http.NewRequest(a.Method, a.URL, bytes.NewBuffer(a.Body)) + if err != nil { + return err + } + req.Header = a.Header + req.Form = a.Form + repo, pipeline, err := s.Impl.Hook(mkCtx(), req) + if err != nil { + return err + } + *resp, err = json.Marshal(&responseHook{ + Repo: modelRepoFromModel(repo), + Pipeline: pipeline, + }) + return err +} + +func (s *RPCServer) Login(args []byte, resp *[]byte) error { + var a *types.OAuthRequest + err := json.Unmarshal(args, a) + if err != nil { + return err + } + user, red, err := s.Impl.Login(mkCtx(), a) + if err != nil { + return err + } + *resp, err = json.Marshal(&responseLogin{ + User: modelUserFromModel(user), + RedirectURL: red, + }) + return err +} diff --git a/server/model/pipeline.go b/server/model/pipeline.go index 1a0a7226d..72fea8192 100644 --- a/server/model/pipeline.go +++ b/server/model/pipeline.go @@ -16,41 +16,41 @@ package model import ( - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" ) type Pipeline struct { - ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"` - RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'pipeline_repo_id'"` - Number int64 `json:"number" xorm:"UNIQUE(s) 'pipeline_number'"` - Author string `json:"author" xorm:"INDEX 'pipeline_author'"` - Parent int64 `json:"parent" xorm:"pipeline_parent"` - Event WebhookEvent `json:"event" xorm:"pipeline_event"` - Status StatusValue `json:"status" xorm:"INDEX 'pipeline_status'"` - Errors []*errors.PipelineError `json:"errors" xorm:"json 'pipeline_errors'"` - Created int64 `json:"created_at" xorm:"pipeline_created"` - Updated int64 `json:"updated_at" xorm:"updated NOT NULL DEFAULT 0 'updated'"` - Started int64 `json:"started_at" xorm:"pipeline_started"` - Finished int64 `json:"finished_at" xorm:"pipeline_finished"` - Deploy string `json:"deploy_to" xorm:"pipeline_deploy"` - Commit string `json:"commit" xorm:"pipeline_commit"` - Branch string `json:"branch" xorm:"pipeline_branch"` - Ref string `json:"ref" xorm:"pipeline_ref"` - Refspec string `json:"refspec" xorm:"pipeline_refspec"` - Title string `json:"title" xorm:"pipeline_title"` - Message string `json:"message" xorm:"TEXT 'pipeline_message'"` - Timestamp int64 `json:"timestamp" xorm:"pipeline_timestamp"` - Sender string `json:"sender" xorm:"pipeline_sender"` // uses reported user for webhooks and name of cron for cron pipelines - Avatar string `json:"author_avatar" xorm:"pipeline_avatar"` - Email string `json:"author_email" xorm:"pipeline_email"` - ForgeURL string `json:"forge_url" xorm:"pipeline_forge_url"` - Reviewer string `json:"reviewed_by" xorm:"pipeline_reviewer"` - Reviewed int64 `json:"reviewed_at" xorm:"pipeline_reviewed"` - Workflows []*Workflow `json:"workflows,omitempty" xorm:"-"` - ChangedFiles []string `json:"changed_files,omitempty" xorm:"LONGTEXT 'changed_files'"` - AdditionalVariables map[string]string `json:"variables,omitempty" xorm:"json 'additional_variables'"` - PullRequestLabels []string `json:"pr_labels,omitempty" xorm:"json 'pr_labels'"` - IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"` + ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"` + RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'pipeline_repo_id'"` + Number int64 `json:"number" xorm:"UNIQUE(s) 'pipeline_number'"` + Author string `json:"author" xorm:"INDEX 'pipeline_author'"` + Parent int64 `json:"parent" xorm:"pipeline_parent"` + Event WebhookEvent `json:"event" xorm:"pipeline_event"` + Status StatusValue `json:"status" xorm:"INDEX 'pipeline_status'"` + Errors []*types.PipelineError `json:"errors" xorm:"json 'pipeline_errors'"` + Created int64 `json:"created_at" xorm:"pipeline_created"` + Updated int64 `json:"updated_at" xorm:"updated NOT NULL DEFAULT 0 'updated'"` + Started int64 `json:"started_at" xorm:"pipeline_started"` + Finished int64 `json:"finished_at" xorm:"pipeline_finished"` + Deploy string `json:"deploy_to" xorm:"pipeline_deploy"` + Commit string `json:"commit" xorm:"pipeline_commit"` + Branch string `json:"branch" xorm:"pipeline_branch"` + Ref string `json:"ref" xorm:"pipeline_ref"` + Refspec string `json:"refspec" xorm:"pipeline_refspec"` + Title string `json:"title" xorm:"pipeline_title"` + Message string `json:"message" xorm:"TEXT 'pipeline_message'"` + Timestamp int64 `json:"timestamp" xorm:"pipeline_timestamp"` + Sender string `json:"sender" xorm:"pipeline_sender"` // uses reported user for webhooks and name of cron for cron pipelines + Avatar string `json:"author_avatar" xorm:"pipeline_avatar"` + Email string `json:"author_email" xorm:"pipeline_email"` + ForgeURL string `json:"forge_url" xorm:"pipeline_forge_url"` + Reviewer string `json:"reviewed_by" xorm:"pipeline_reviewer"` + Reviewed int64 `json:"reviewed_at" xorm:"pipeline_reviewed"` + Workflows []*Workflow `json:"workflows,omitempty" xorm:"-"` + ChangedFiles []string `json:"changed_files,omitempty" xorm:"LONGTEXT 'changed_files'"` + AdditionalVariables map[string]string `json:"variables,omitempty" xorm:"json 'additional_variables'"` + PullRequestLabels []string `json:"pr_labels,omitempty" xorm:"json 'pr_labels'"` + IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"` } // @name Pipeline // TableName return database table name for xorm diff --git a/server/pipeline/stepbuilder/stepBuilder.go b/server/pipeline/stepbuilder/stepBuilder.go index a7075cccf..0bff2050c 100644 --- a/server/pipeline/stepbuilder/stepBuilder.go +++ b/server/pipeline/stepbuilder/stepBuilder.go @@ -26,6 +26,7 @@ import ( backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" + errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml" "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler" @@ -135,7 +136,7 @@ func (b *StepBuilder) genItemForWorkflow(workflow *model.Workflow, axis matrix.A // parse yaml pipeline parsed, err := yaml.ParseString(substituted) if err != nil { - return nil, &pipeline_errors.PipelineError{Message: err.Error(), Type: pipeline_errors.PipelineErrorTypeCompiler} + return nil, &errorTypes.PipelineError{Message: err.Error(), Type: errorTypes.PipelineErrorTypeCompiler} } // lint pipeline diff --git a/server/store/datastore/migration/027_convert_to_new_pipeline_errors_format.go b/server/store/datastore/migration/027_convert_to_new_pipeline_errors_format.go index de6bc229f..fa9994294 100644 --- a/server/store/datastore/migration/027_convert_to_new_pipeline_errors_format.go +++ b/server/store/datastore/migration/027_convert_to_new_pipeline_errors_format.go @@ -18,16 +18,16 @@ import ( "src.techknowlogick.com/xormigrate" "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" + errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" ) // perPage027 set the size of the slice to read per page var perPage027 = 100 type pipeline027 struct { - ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"` - Error string `json:"error" xorm:"LONGTEXT 'pipeline_error'"` // old error format - Errors []*errors.PipelineError `json:"errors" xorm:"json 'pipeline_errors'"` // new error format + ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"` + Error string `json:"error" xorm:"LONGTEXT 'pipeline_error'"` // old error format + Errors []*errorTypes.PipelineError `json:"errors" xorm:"json 'pipeline_errors'"` // new error format } func (pipeline027) TableName() string { @@ -64,7 +64,7 @@ var convertToNewPipelineErrorFormat = xormigrate.Migration{ for _, oldPipeline := range oldPipelines { var newPipeline pipeline027 newPipeline.ID = oldPipeline.ID - newPipeline.Errors = []*errors.PipelineError{{ + newPipeline.Errors = []*errorTypes.PipelineError{{ Type: "generic", Message: oldPipeline.Error, }} diff --git a/shared/addon/addon.go b/shared/addon/addon.go deleted file mode 100644 index 51d668f6c..000000000 --- a/shared/addon/addon.go +++ /dev/null @@ -1,62 +0,0 @@ -package addon - -import ( - "errors" - "plugin" - - "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - - "go.woodpecker-ci.org/woodpecker/v2/shared/addon/types" -) - -var pluginCache = map[string]*plugin.Plugin{} - -type Addon[T any] struct { - Type types.Type - Value T -} - -func Load[T any](files []string, t types.Type) (*Addon[T], error) { - for _, file := range files { - if _, has := pluginCache[file]; !has { - p, err := plugin.Open(file) - if err != nil { - return nil, err - } - pluginCache[file] = p - } - - typeLookup, err := pluginCache[file].Lookup("Type") - if err != nil { - return nil, err - } - if addonType, is := typeLookup.(*types.Type); !is { - return nil, errors.New("addon type is incorrect") - } else if *addonType != t { - continue - } - - mainLookup, err := pluginCache[file].Lookup("Addon") - if err != nil { - return nil, err - } - main, is := mainLookup.(func(zerolog.Logger) (T, error)) - if !is { - return nil, errors.New("addon main function has incorrect type") - } - - logger := log.Logger.With().Str("addon", file).Logger() - - mainOut, err := main(logger) - if err != nil { - return nil, err - } - return &Addon[T]{ - Type: t, - Value: mainOut, - }, nil - } - - return nil, nil -} diff --git a/shared/addon/types/types.go b/shared/addon/types/types.go deleted file mode 100644 index 96331d280..000000000 --- a/shared/addon/types/types.go +++ /dev/null @@ -1,7 +0,0 @@ -package types - -type Type string - -const ( - TypeForge Type = "forge" -)