Rework addons (use rpc) (#3268)

Co-authored-by: Anbraten <6918444+anbraten@users.noreply.github.com>
This commit is contained in:
qwerty287 2024-04-15 10:04:21 +02:00 committed by GitHub
parent b177d82064
commit 00f0fcd416
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 1309 additions and 385 deletions

View file

@ -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)

View file

@ -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"
]
}
}
}`

View file

@ -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

View file

@ -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"):

View file

@ -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)).

View file

@ -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).

View file

@ -0,0 +1,68 @@
# Addon forges
If the forge you're using does not comply with [Woodpecker's requirements](../../92-development/02-core-ideas.md#forge) or your setup is too specific to be added to Woodpecker's core, you can write your own forge using an addon forge.
:::warning
Addon forges are still experimental. Their implementation can change and break at any time.
:::
:::danger
You need to trust the author of the addon forge you use. It can access authentication codes and other possibly sensitive information.
:::
## Usage
To use an addon forge, download the correct addon version. Then, you can add the following to your configuration:
```ini
WOODPECKER_ADDON_FORGE=/path/to/your/addon/forge/file
```
In case you run Woodpecker as container, you probably want to mount the addon binary to `/opt/addons/`.
### Bug reports
If you experience bugs, please check which component has the issue. If it's the addon, **do not raise an issue in the main repository**, but rather use the separate addon repositories. To check which component is responsible for the bug, look at the logs. Logs from addons are marked with a special field `addon` containing their addon file name.
## List of addon forges
If you wrote or found an addon forge, please add it here so others can find it!
_Be the first one to add your addon forge!_
## Creating addon forges
Addons use RPC to communicate to the server and are implemented using the [`go-plugin` library](https://github.com/hashicorp/go-plugin).
### Writing your code
This example will use the Go language.
Directly import Woodpecker's Go packages (`go.woodpecker-ci.org/woodpecker/woodpecker/v2`) and use the interfaces and types defined there.
In the `main` function, just call `"go.woodpecker-ci.org/woodpecker/v2/server/forge/addon".Serve` with a `"go.woodpecker-ci.org/woodpecker/v2/server/forge".Forge` as argument.
This will take care of connecting the addon forge to the server.
### Example structure
```go
package main
import (
"context"
"net/http"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/addon"
forgeTypes "go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
)
func main() {
addon.Serve(config{})
}
type config struct {
}
// `config` must implement `"go.woodpecker-ci.org/woodpecker/v2/server/forge".Forge`. You must directly use Woodpecker's packages - see imports above.
```

View file

@ -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.
```

View file

@ -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.

View file

@ -1,6 +0,0 @@
label: 'Addons'
collapsible: true
collapsed: true
link:
type: 'doc'
id: 'overview'

View file

@ -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?

6
go.mod
View file

@ -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

48
go.sum
View file

@ -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=

View file

@ -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,
})
}
}

View file

@ -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"),

View file

@ -0,0 +1,24 @@
package types
import "fmt"
type PipelineErrorType string
const (
PipelineErrorTypeLinter PipelineErrorType = "linter" // some error with the config syntax
PipelineErrorTypeDeprecation PipelineErrorType = "deprecation" // using some deprecated feature
PipelineErrorTypeCompiler PipelineErrorType = "compiler" // some error with the config semantics
PipelineErrorTypeGeneric PipelineErrorType = "generic" // some generic error
PipelineErrorTypeBadHabit PipelineErrorType = "bad_habit" // some bad-habit error
)
type PipelineError struct {
Type PipelineErrorType `json:"type"`
Message string `json:"message"`
IsWarning bool `json:"is_warning"`
Data any `json:"data"`
}
func (e *PipelineError) Error() string {
return fmt.Sprintf("[%s] %s", e.Type, e.Message)
}

View file

@ -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,

View file

@ -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,

View file

@ -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
}

154
server/forge/addon/args.go Normal file
View file

@ -0,0 +1,154 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package addon
import (
"go.woodpecker-ci.org/woodpecker/v2/server/model"
)
type argumentsAuth struct {
Token string `json:"token"`
Secret string `json:"secret"`
}
type argumentsRepo struct {
U *modelUser `json:"u"`
RemoteID model.ForgeRemoteID `json:"remote_id"`
Owner string `json:"owner"`
Name string `json:"name"`
}
type argumentsFileDir struct {
U *modelUser `json:"u"`
R *modelRepo `json:"r"`
B *model.Pipeline `json:"b"`
F string `json:"f"`
}
type argumentsStatus struct {
U *modelUser `json:"u"`
R *modelRepo `json:"r"`
B *model.Pipeline `json:"b"`
P *model.Workflow `json:"p"`
}
type argumentsNetrc struct {
U *modelUser `json:"u"`
R *modelRepo `json:"r"`
}
type argumentsActivateDeactivate struct {
U *modelUser `json:"u"`
R *modelRepo `json:"r"`
Link string `json:"link"`
}
type argumentsBranchesPullRequests struct {
U *modelUser `json:"u"`
R *modelRepo `json:"r"`
P *model.ListOptions `json:"p"`
}
type argumentsBranchHead struct {
U *modelUser `json:"u"`
R *modelRepo `json:"r"`
Branch string `json:"branch"`
}
type argumentsOrgMembershipOrg struct {
U *modelUser `json:"u"`
Org string `json:"org"`
}
type responseHook struct {
Repo *modelRepo `json:"repo"`
Pipeline *model.Pipeline `json:"pipeline"`
}
type responseLogin struct {
User *modelUser `json:"user"`
RedirectURL string `json:"redirect_url"`
}
type httpRequest struct {
Method string `json:"method"`
URL string `json:"url"`
Header map[string][]string `json:"header"`
Form map[string][]string `json:"form"`
Body []byte `json:"body"`
}
// modelUser is an extension of model.User to marshal all fields to JSON
type modelUser struct {
User *model.User `json:"user"`
ForgeRemoteID model.ForgeRemoteID `json:"forge_remote_id"`
// Token is the oauth2 token.
Token string `json:"token"`
// Secret is the oauth2 token secret.
Secret string `json:"secret"`
// Expiry is the token and secret expiration timestamp.
Expiry int64 `json:"expiry"`
// Hash is a unique token used to sign tokens.
Hash string `json:"hash"`
}
func (m *modelUser) asModel() *model.User {
m.User.ForgeRemoteID = m.ForgeRemoteID
m.User.Token = m.Token
m.User.Secret = m.Secret
m.User.Expiry = m.Expiry
m.User.Hash = m.Hash
return m.User
}
func modelUserFromModel(u *model.User) *modelUser {
return &modelUser{
User: u,
ForgeRemoteID: u.ForgeRemoteID,
Token: u.Token,
Secret: u.Secret,
Expiry: u.Expiry,
Hash: u.Hash,
}
}
// modelRepo is an extension of model.Repo to marshal all fields to JSON
type modelRepo struct {
Repo *model.Repo `json:"repo"`
UserID int64 `json:"user_id"`
Hash string `json:"hash"`
Perm *model.Perm `json:"perm"`
}
func (m *modelRepo) asModel() *model.Repo {
m.Repo.UserID = m.UserID
m.Repo.Hash = m.Hash
m.Repo.Perm = m.Perm
return m.Repo
}
func modelRepoFromModel(r *model.Repo) *modelRepo {
return &modelRepo{
Repo: r,
UserID: r.UserID,
Hash: r.Hash,
Perm: r.Perm,
}
}

View file

@ -0,0 +1,377 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package addon
import (
"context"
"encoding/json"
"io"
"net/http"
"net/rpc"
"os/exec"
"github.com/hashicorp/go-plugin"
"github.com/rs/zerolog/log"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
)
// make sure RPC implements forge.Forge
var _ forge.Forge = new(RPC)
func Load(file string) (forge.Forge, error) {
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig,
Plugins: map[string]plugin.Plugin{
pluginKey: &Plugin{},
},
Cmd: exec.Command(file),
Logger: &clientLogger{
logger: log.With().Str("addon", file).Logger(),
},
})
// TODO defer client.Kill()
rpcClient, err := client.Client()
if err != nil {
return nil, err
}
raw, err := rpcClient.Dispense(pluginKey)
if err != nil {
return nil, err
}
extension, _ := raw.(forge.Forge)
return extension, nil
}
type RPC struct {
client *rpc.Client
}
func (g *RPC) Name() string {
var resp string
_ = g.client.Call("Plugin.Name", nil, &resp)
return resp
}
func (g *RPC) URL() string {
var resp string
_ = g.client.Call("Plugin.URL", nil, &resp)
return resp
}
func (g *RPC) Login(_ context.Context, r *types.OAuthRequest) (*model.User, string, error) {
args, err := json.Marshal(r)
if err != nil {
return nil, "", err
}
var jsonResp []byte
err = g.client.Call("Plugin.Login", args, &jsonResp)
if err != nil {
return nil, "", err
}
var resp responseLogin
err = json.Unmarshal(jsonResp, &resp)
if err != nil {
return nil, "", err
}
return resp.User.asModel(), resp.RedirectURL, nil
}
func (g *RPC) Auth(_ context.Context, token, secret string) (string, error) {
args, err := json.Marshal(&argumentsAuth{
Token: token,
Secret: secret,
})
if err != nil {
return "", err
}
var resp string
return resp, g.client.Call("Plugin.Auth", args, &resp)
}
func (g *RPC) Teams(_ context.Context, u *model.User) ([]*model.Team, error) {
args, err := json.Marshal(modelUserFromModel(u))
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.Teams", args, &jsonResp)
if err != nil {
return nil, err
}
var resp []*model.Team
return resp, json.Unmarshal(jsonResp, &resp)
}
func (g *RPC) Repo(_ context.Context, u *model.User, remoteID model.ForgeRemoteID, owner, name string) (*model.Repo, error) {
args, err := json.Marshal(&argumentsRepo{
U: modelUserFromModel(u),
RemoteID: remoteID,
Owner: owner,
Name: name,
})
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.Repo", args, &jsonResp)
if err != nil {
return nil, err
}
var resp *modelRepo
err = json.Unmarshal(jsonResp, resp)
if err != nil {
return nil, err
}
return resp.asModel(), nil
}
func (g *RPC) Repos(_ context.Context, u *model.User) ([]*model.Repo, error) {
args, err := json.Marshal(modelUserFromModel(u))
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.Repos", args, &jsonResp)
if err != nil {
return nil, err
}
var resp []*modelRepo
err = json.Unmarshal(jsonResp, &resp)
if err != nil {
return nil, err
}
var modelRepos []*model.Repo
for _, repo := range resp {
modelRepos = append(modelRepos, repo.asModel())
}
return modelRepos, nil
}
func (g *RPC) File(_ context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]byte, error) {
args, err := json.Marshal(&argumentsFileDir{
U: modelUserFromModel(u),
R: modelRepoFromModel(r),
B: b,
F: f,
})
if err != nil {
return nil, err
}
var resp []byte
return resp, g.client.Call("Plugin.File", args, &resp)
}
func (g *RPC) Dir(_ context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]*types.FileMeta, error) {
args, err := json.Marshal(&argumentsFileDir{
U: modelUserFromModel(u),
R: modelRepoFromModel(r),
B: b,
F: f,
})
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.Dir", args, &jsonResp)
if err != nil {
return nil, err
}
var resp []*types.FileMeta
return resp, json.Unmarshal(jsonResp, &resp)
}
func (g *RPC) Status(_ context.Context, u *model.User, r *model.Repo, b *model.Pipeline, p *model.Workflow) error {
args, err := json.Marshal(&argumentsStatus{
U: modelUserFromModel(u),
R: modelRepoFromModel(r),
B: b,
P: p,
})
if err != nil {
return err
}
var jsonResp []byte
return g.client.Call("Plugin.Status", args, &jsonResp)
}
func (g *RPC) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
args, err := json.Marshal(&argumentsNetrc{
U: modelUserFromModel(u),
R: modelRepoFromModel(r),
})
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.Netrc", args, &jsonResp)
if err != nil {
return nil, err
}
var resp *model.Netrc
return resp, json.Unmarshal(jsonResp, &resp)
}
func (g *RPC) Activate(_ context.Context, u *model.User, r *model.Repo, link string) error {
args, err := json.Marshal(&argumentsActivateDeactivate{
U: modelUserFromModel(u),
R: modelRepoFromModel(r),
Link: link,
})
if err != nil {
return err
}
var jsonResp []byte
return g.client.Call("Plugin.Activate", args, &jsonResp)
}
func (g *RPC) Deactivate(_ context.Context, u *model.User, r *model.Repo, link string) error {
args, err := json.Marshal(&argumentsActivateDeactivate{
U: modelUserFromModel(u),
R: modelRepoFromModel(r),
Link: link,
})
if err != nil {
return err
}
var jsonResp []byte
return g.client.Call("Plugin.Deactivate", args, &jsonResp)
}
func (g *RPC) Branches(_ context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]string, error) {
args, err := json.Marshal(&argumentsBranchesPullRequests{
U: modelUserFromModel(u),
R: modelRepoFromModel(r),
P: p,
})
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.Branches", args, &jsonResp)
if err != nil {
return nil, err
}
var resp []string
return resp, json.Unmarshal(jsonResp, &resp)
}
func (g *RPC) BranchHead(_ context.Context, u *model.User, r *model.Repo, branch string) (*model.Commit, error) {
args, err := json.Marshal(&argumentsBranchHead{
U: modelUserFromModel(u),
R: modelRepoFromModel(r),
Branch: branch,
})
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.BranchHead", args, &jsonResp)
if err != nil {
return nil, err
}
var resp *model.Commit
return resp, json.Unmarshal(jsonResp, &resp)
}
func (g *RPC) PullRequests(_ context.Context, u *model.User, r *model.Repo, p *model.ListOptions) ([]*model.PullRequest, error) {
args, err := json.Marshal(&argumentsBranchesPullRequests{
U: modelUserFromModel(u),
R: modelRepoFromModel(r),
P: p,
})
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.PullRequests", args, &jsonResp)
if err != nil {
return nil, err
}
var resp []*model.PullRequest
return resp, json.Unmarshal(jsonResp, &resp)
}
func (g *RPC) Hook(_ context.Context, r *http.Request) (*model.Repo, *model.Pipeline, error) {
body, err := io.ReadAll(r.Body)
if err != nil {
return nil, nil, err
}
args, err := json.Marshal(&httpRequest{
Method: r.Method,
URL: r.URL.String(),
Header: r.Header,
Form: r.Form,
Body: body,
})
if err != nil {
return nil, nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.Hook", args, &jsonResp)
if err != nil {
return nil, nil, err
}
var resp responseHook
err = json.Unmarshal(jsonResp, &resp)
if err != nil {
return nil, nil, err
}
return resp.Repo.asModel(), resp.Pipeline, nil
}
func (g *RPC) OrgMembership(_ context.Context, u *model.User, org string) (*model.OrgPerm, error) {
args, err := json.Marshal(&argumentsOrgMembershipOrg{
U: modelUserFromModel(u),
Org: org,
})
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.OrgMembership", args, &jsonResp)
if err != nil {
return nil, err
}
var resp *model.OrgPerm
return resp, json.Unmarshal(jsonResp, &resp)
}
func (g *RPC) Org(_ context.Context, u *model.User, org string) (*model.Org, error) {
args, err := json.Marshal(&argumentsOrgMembershipOrg{
U: modelUserFromModel(u),
Org: org,
})
if err != nil {
return nil, err
}
var jsonResp []byte
err = g.client.Call("Plugin.Org", args, &jsonResp)
if err != nil {
return nil, err
}
var resp *model.Org
return resp, json.Unmarshal(jsonResp, &resp)
}

View file

@ -0,0 +1,165 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package addon
import (
"bytes"
"io"
stdlog "log"
"github.com/hashicorp/go-hclog"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
type clientLogger struct {
logger zerolog.Logger
name string
withArgs []any
}
func convertLvl(level hclog.Level) zerolog.Level {
switch level {
case hclog.Error:
return zerolog.ErrorLevel
case hclog.Warn:
return zerolog.WarnLevel
case hclog.Info:
return zerolog.InfoLevel
case hclog.Debug:
return zerolog.DebugLevel
case hclog.Trace:
return zerolog.TraceLevel
}
return zerolog.NoLevel
}
func (c *clientLogger) applyArgs(args []any) *zerolog.Logger {
var key string
logger := c.logger.With()
args = append(args, c.withArgs)
for i, arg := range args {
switch {
case key != "":
logger.Any(key, arg)
key = ""
case i == len(args)-1:
logger.Any(hclog.MissingKey, arg)
default:
key, _ = arg.(string)
}
}
l := logger.Logger()
return &l
}
func (c *clientLogger) Log(level hclog.Level, msg string, args ...any) {
c.applyArgs(args).WithLevel(convertLvl(level)).Msg(msg)
}
func (c *clientLogger) Trace(msg string, args ...any) {
c.applyArgs(args).Trace().Msg(msg)
}
func (c *clientLogger) Debug(msg string, args ...any) {
c.applyArgs(args).Debug().Msg(msg)
}
func (c *clientLogger) Info(msg string, args ...any) {
c.applyArgs(args).Info().Msg(msg)
}
func (c *clientLogger) Warn(msg string, args ...any) {
c.applyArgs(args).Warn().Msg(msg)
}
func (c *clientLogger) Error(msg string, args ...any) {
c.applyArgs(args).Error().Msg(msg)
}
func (c *clientLogger) IsTrace() bool {
return log.Logger.GetLevel() >= zerolog.TraceLevel
}
func (c *clientLogger) IsDebug() bool {
return log.Logger.GetLevel() >= zerolog.DebugLevel
}
func (c *clientLogger) IsInfo() bool {
return log.Logger.GetLevel() >= zerolog.InfoLevel
}
func (c *clientLogger) IsWarn() bool {
return log.Logger.GetLevel() >= zerolog.WarnLevel
}
func (c *clientLogger) IsError() bool {
return log.Logger.GetLevel() >= zerolog.ErrorLevel
}
func (c *clientLogger) ImpliedArgs() []any {
return c.withArgs
}
func (c *clientLogger) With(args ...any) hclog.Logger {
return &clientLogger{
logger: c.logger,
name: c.name,
withArgs: args,
}
}
func (c *clientLogger) Name() string {
return c.name
}
func (c *clientLogger) Named(name string) hclog.Logger {
curr := c.name
if curr != "" {
curr = c.name + "."
}
return c.ResetNamed(curr + name)
}
func (c *clientLogger) ResetNamed(name string) hclog.Logger {
return &clientLogger{
logger: c.logger,
name: name,
withArgs: c.withArgs,
}
}
func (c *clientLogger) SetLevel(level hclog.Level) {
c.logger = c.logger.Level(convertLvl(level))
}
func (c *clientLogger) StandardLogger(opts *hclog.StandardLoggerOptions) *stdlog.Logger {
return stdlog.New(c.StandardWriter(opts), "", 0)
}
func (c *clientLogger) StandardWriter(*hclog.StandardLoggerOptions) io.Writer {
return ioAdapter{logger: c.logger}
}
type ioAdapter struct {
logger zerolog.Logger
}
func (i ioAdapter) Write(p []byte) (n int, err error) {
str := string(bytes.TrimRight(p, " \t\n"))
i.logger.Log().Msg(str)
return len(p), nil
}

View file

@ -0,0 +1,43 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package addon
import (
"net/rpc"
"github.com/hashicorp/go-plugin"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
)
const pluginKey = "forge"
var HandshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "WOODPECKER_FORGE_ADDON_PLUGIN",
MagicCookieValue: "woodpecker-plugin-magic-cookie-value",
}
type Plugin struct {
Impl forge.Forge
}
func (p *Plugin) Server(*plugin.MuxBroker) (any, error) {
return &RPCServer{Impl: p.Impl}, nil
}
func (*Plugin) Client(_ *plugin.MuxBroker, c *rpc.Client) (any, error) {
return &RPC{client: c}, nil
}

View file

@ -0,0 +1,278 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package addon
import (
"bytes"
"context"
"encoding/json"
"net/http"
"github.com/hashicorp/go-plugin"
"go.woodpecker-ci.org/woodpecker/v2/server/forge"
"go.woodpecker-ci.org/woodpecker/v2/server/forge/types"
)
func Serve(impl forge.Forge) {
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: HandshakeConfig,
Plugins: map[string]plugin.Plugin{
pluginKey: &Plugin{Impl: impl},
},
})
}
func mkCtx() context.Context {
return context.Background()
}
type RPCServer struct {
Impl forge.Forge
}
func (s *RPCServer) Name(_ []byte, resp *string) error {
*resp = s.Impl.Name()
return nil
}
func (s *RPCServer) URL(_ []byte, resp *string) error {
*resp = s.Impl.URL()
return nil
}
func (s *RPCServer) Teams(args []byte, resp *[]byte) error {
var a *modelUser
err := json.Unmarshal(args, a)
if err != nil {
return err
}
teams, err := s.Impl.Teams(mkCtx(), a.asModel())
if err != nil {
return err
}
*resp, err = json.Marshal(teams)
return err
}
func (s *RPCServer) Repo(args []byte, resp *[]byte) error {
var a argumentsRepo
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
repos, err := s.Impl.Repo(mkCtx(), a.U.asModel(), a.RemoteID, a.Owner, a.Name)
if err != nil {
return err
}
*resp, err = json.Marshal(modelRepoFromModel(repos))
return err
}
func (s *RPCServer) Repos(args []byte, resp *[]byte) error {
var a *modelUser
err := json.Unmarshal(args, a)
if err != nil {
return err
}
repos, err := s.Impl.Repos(mkCtx(), a.asModel())
if err != nil {
return err
}
var modelRepos []*modelRepo
for _, repo := range repos {
modelRepos = append(modelRepos, modelRepoFromModel(repo))
}
*resp, err = json.Marshal(modelRepos)
return err
}
func (s *RPCServer) File(args []byte, resp *[]byte) error {
var a argumentsFileDir
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
*resp, err = s.Impl.File(mkCtx(), a.U.asModel(), a.R.asModel(), a.B, a.F)
return err
}
func (s *RPCServer) Dir(args []byte, resp *[]byte) error {
var a argumentsFileDir
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
meta, err := s.Impl.Dir(mkCtx(), a.U.asModel(), a.R.asModel(), a.B, a.F)
if err != nil {
return err
}
*resp, err = json.Marshal(meta)
return err
}
func (s *RPCServer) Status(args []byte, resp *[]byte) error {
var a argumentsStatus
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
*resp = []byte{}
return s.Impl.Status(mkCtx(), a.U.asModel(), a.R.asModel(), a.B, a.P)
}
func (s *RPCServer) Netrc(args []byte, resp *[]byte) error {
var a argumentsNetrc
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
netrc, err := s.Impl.Netrc(a.U.asModel(), a.R.asModel())
if err != nil {
return err
}
*resp, err = json.Marshal(netrc)
return err
}
func (s *RPCServer) Activate(args []byte, resp *[]byte) error {
var a argumentsActivateDeactivate
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
*resp = []byte{}
return s.Impl.Activate(mkCtx(), a.U.asModel(), a.R.asModel(), a.Link)
}
func (s *RPCServer) Deactivate(args []byte, resp *[]byte) error {
var a argumentsActivateDeactivate
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
*resp = []byte{}
return s.Impl.Deactivate(mkCtx(), a.U.asModel(), a.R.asModel(), a.Link)
}
func (s *RPCServer) Branches(args []byte, resp *[]byte) error {
var a argumentsBranchesPullRequests
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
branches, err := s.Impl.Branches(mkCtx(), a.U.asModel(), a.R.asModel(), a.P)
if err != nil {
return err
}
*resp, err = json.Marshal(branches)
return err
}
func (s *RPCServer) BranchHead(args []byte, resp *[]byte) error {
var a argumentsBranchHead
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
commit, err := s.Impl.BranchHead(mkCtx(), a.U.asModel(), a.R.asModel(), a.Branch)
if err != nil {
return err
}
*resp, err = json.Marshal(commit)
return err
}
func (s *RPCServer) PullRequests(args []byte, resp *[]byte) error {
var a argumentsBranchesPullRequests
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
prs, err := s.Impl.PullRequests(mkCtx(), a.U.asModel(), a.R.asModel(), a.P)
if err != nil {
return err
}
*resp, err = json.Marshal(prs)
return err
}
func (s *RPCServer) OrgMembership(args []byte, resp *[]byte) error {
var a argumentsOrgMembershipOrg
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
org, err := s.Impl.OrgMembership(mkCtx(), a.U.asModel(), a.Org)
if err != nil {
return err
}
*resp, err = json.Marshal(org)
return err
}
func (s *RPCServer) Org(args []byte, resp *[]byte) error {
var a argumentsOrgMembershipOrg
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
org, err := s.Impl.Org(mkCtx(), a.U.asModel(), a.Org)
if err != nil {
return err
}
*resp, err = json.Marshal(org)
return err
}
func (s *RPCServer) Hook(args []byte, resp *[]byte) error {
var a httpRequest
err := json.Unmarshal(args, &a)
if err != nil {
return err
}
req, err := http.NewRequest(a.Method, a.URL, bytes.NewBuffer(a.Body))
if err != nil {
return err
}
req.Header = a.Header
req.Form = a.Form
repo, pipeline, err := s.Impl.Hook(mkCtx(), req)
if err != nil {
return err
}
*resp, err = json.Marshal(&responseHook{
Repo: modelRepoFromModel(repo),
Pipeline: pipeline,
})
return err
}
func (s *RPCServer) Login(args []byte, resp *[]byte) error {
var a *types.OAuthRequest
err := json.Unmarshal(args, a)
if err != nil {
return err
}
user, red, err := s.Impl.Login(mkCtx(), a)
if err != nil {
return err
}
*resp, err = json.Marshal(&responseLogin{
User: modelUserFromModel(user),
RedirectURL: red,
})
return err
}

View file

@ -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

View file

@ -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

View file

@ -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,
}}

View file

@ -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
}

View file

@ -1,7 +0,0 @@
package types
type Type string
const (
TypeForge Type = "forge"
)