Merge branch 'main' into git-only

This commit is contained in:
qwerty287 2024-11-18 18:19:03 +02:00
commit 9ad32b6941
No known key found for this signature in database
61 changed files with 755 additions and 518 deletions

View file

@ -65,6 +65,7 @@ Visibility: {{ .Visibility }}
Private: {{ .IsSCMPrivate }}
Trusted: {{ .IsTrusted }}
Gated: {{ .IsGated }}
Require approval for: {{ .RequireApproval }}
Clone url: {{ .Clone }}
Allow pull-requests: {{ .AllowPullRequests }}
`

View file

@ -39,6 +39,10 @@ var repoUpdateCmd = &cli.Command{
Name: "gated",
Usage: "repository is gated",
},
&cli.StringFlag{
Name: "require-approval",
Usage: "repository requires approval for",
},
&cli.DurationFlag{
Name: "timeout",
Usage: "repository timeout",
@ -79,6 +83,7 @@ func repoUpdate(ctx context.Context, c *cli.Command) error {
timeout = c.Duration("timeout")
trusted = c.Bool("trusted")
gated = c.Bool("gated")
requireApproval = c.String("require-approval")
pipelineCounter = int(c.Int("pipeline-counter"))
unsafe = c.Bool("unsafe")
)
@ -87,8 +92,29 @@ func repoUpdate(ctx context.Context, c *cli.Command) error {
if c.IsSet("trusted") {
patch.IsTrusted = &trusted
}
// TODO: remove isGated in next major release
if c.IsSet("gated") {
patch.IsGated = &gated
if gated {
patch.RequireApproval = &woodpecker.RequireApprovalAllEvents
} else {
patch.RequireApproval = &woodpecker.RequireApprovalNone
}
}
if c.IsSet("require-approval") {
if mode := woodpecker.ApprovalMode(requireApproval); mode.Valid() {
patch.RequireApproval = &mode
} else {
return fmt.Errorf("update approval mode failed: '%s' is no valid mode", mode)
}
// TODO: remove isGated in next major release
if requireApproval == string(woodpecker.RequireApprovalAllEvents) {
trueBool := true
patch.IsGated = &trueBool
} else if requireApproval == string(woodpecker.RequireApprovalNone) {
falseBool := false
patch.IsGated = &falseBool
}
}
if c.IsSet("timeout") {
v := int64(timeout / time.Minute)

View file

@ -4905,6 +4905,9 @@ const docTemplate = `{
"forge_url": {
"type": "string"
},
"from_fork": {
"type": "boolean"
},
"id": {
"type": "integer"
},
@ -5068,9 +5071,6 @@ const docTemplate = `{
"full_name": {
"type": "string"
},
"gated": {
"type": "boolean"
},
"id": {
"type": "integer"
},
@ -5092,6 +5092,9 @@ const docTemplate = `{
"private": {
"type": "boolean"
},
"require_approval": {
"$ref": "#/definitions/model.ApprovalMode"
},
"scm": {
"$ref": "#/definitions/SCMKind"
},
@ -5125,11 +5128,15 @@ const docTemplate = `{
"type": "string"
},
"gated": {
"description": "TODO: deprecated in favor of RequireApproval =\u003e Remove in next major release",
"type": "boolean"
},
"netrc_only_trusted": {
"type": "boolean"
},
"require_approval": {
"type": "string"
},
"timeout": {
"type": "integer"
},
@ -5621,6 +5628,27 @@ const docTemplate = `{
}
}
},
"model.ApprovalMode": {
"type": "string",
"enum": [
"none",
"forks",
"pull_requests",
"all_events"
],
"x-enum-comments": {
"RequireApprovalAllEvents": "require approval for all external events",
"RequireApprovalForks": "require approval for PRs from forks (default)",
"RequireApprovalNone": "require approval for no events",
"RequireApprovalPullRequests": "require approval for all PRs"
},
"x-enum-varnames": [
"RequireApprovalNone",
"RequireApprovalForks",
"RequireApprovalPullRequests",
"RequireApprovalAllEvents"
]
},
"model.ForgeType": {
"type": "string",
"enum": [

View file

@ -25,10 +25,9 @@ Only activate this option if you trust all users who have push access to your re
Otherwise, these users will be able to steal secrets that are only available for `deploy` events.
:::
## Protected
## Require approval for
Every pipeline initiated by an webhook event needs to be approved by a project members with push permissions before being executed.
The protected option can be used as an additional review process before running potentially harmful pipelines. Especially if pipelines can be executed by third-parties through pull-requests.
To prevent malicious pipelines from extracting secrets or running harmful commands or to prevent accidental pipeline runs, you can require approval for an additional review process. Depending on the enabled option, a pipeline will be put on hold after creation and will only continue after approval. The default restrictive setting is `Approvals for forked repositories`.
## Trusted

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 353 KiB

View file

@ -224,6 +224,11 @@
"name": "EditorConfig Checker",
"docs": "https://codeberg.org/woodpecker-plugins/editorconfig-checker/raw/branch/main/docs.md",
"verified": true
},
{
"name": "Microsoft Teams Notify",
"docs": "https://raw.githubusercontent.com/GECO-IT/woodpecker-plugin-teams-notify/refs/heads/main/docs.md",
"verified": false
}
]
}

View file

@ -6864,7 +6864,7 @@ snapshots:
'@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/logger': 3.6.1
'@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/theme-common': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
@ -6904,48 +6904,6 @@ snapshots:
- webpack-cli
'@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)':
dependencies:
'@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/logger': 3.6.1
'@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/module-type-aliases': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/theme-common': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/types': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/utils-validation': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@types/react-router-config': 5.0.11
combine-promises: 1.2.0
fs-extra: 11.2.0
js-yaml: 4.1.0
lodash: 4.17.21
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tslib: 2.8.1
utility-types: 3.11.0
webpack: 5.96.1
transitivePeerDependencies:
- '@docusaurus/faster'
- '@mdx-js/react'
- '@parcel/css'
- '@rspack/core'
- '@swc/core'
- '@swc/css'
- acorn
- bufferutil
- csso
- debug
- esbuild
- eslint
- lightningcss
- supports-color
- typescript
- uglify-js
- utf-8-validate
- vue-template-compiler
- webpack-cli
'@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)':
dependencies:
'@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/logger': 3.6.1
@ -7177,7 +7135,7 @@ snapshots:
dependencies:
'@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-blog': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-pages': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-debug': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-google-analytics': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
@ -7226,7 +7184,7 @@ snapshots:
'@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/module-type-aliases': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-blog': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-pages': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/theme-common': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/theme-translations': 3.6.1
@ -7270,37 +7228,11 @@ snapshots:
- vue-template-compiler
- webpack-cli
'@docusaurus/theme-common@3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)':
dependencies:
'@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/module-type-aliases': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/history': 4.7.11
'@types/react': 18.3.12
'@types/react-router-config': 5.0.11
clsx: 2.1.1
parse-numeric-range: 1.3.0
prism-react-renderer: 2.4.0(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tslib: 2.8.1
utility-types: 3.11.0
transitivePeerDependencies:
- '@swc/core'
- acorn
- esbuild
- supports-color
- typescript
- uglify-js
- webpack-cli
'@docusaurus/theme-common@3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)':
dependencies:
'@docusaurus/mdx-loader': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/module-type-aliases': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/utils-common': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/history': 4.7.11
@ -7327,7 +7259,7 @@ snapshots:
'@docsearch/react': 3.8.0(@algolia/client-search@5.14.2)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.17.2)
'@docusaurus/core': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/logger': 3.6.1
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/plugin-content-docs': 3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(debug@4.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/theme-common': 3.6.1(@docusaurus/plugin-content-docs@3.6.1(@mdx-js/react@3.1.0(@types/react@18.3.12)(react@18.3.1))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3))(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)
'@docusaurus/theme-translations': 3.6.1
'@docusaurus/utils': 3.6.1(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)

View file

@ -38,6 +38,7 @@
addlicense
protoc-gen-go
protoc-gen-go-grpc
gcc
];
CFLAGS = "-I${pkgs.glibc.dev}/include";
LDFLAGS = "-L${pkgs.glibc}/lib";

View file

@ -91,6 +91,7 @@ func PostRepo(c *gin.Context) {
repo.Update(from)
} else {
repo = from
repo.RequireApproval = model.RequireApprovalForks
repo.AllowPull = true
repo.AllowDeploy = false
repo.NetrcOnlyTrusted = true
@ -250,8 +251,20 @@ func PatchRepo(c *gin.Context) {
if in.AllowDeploy != nil {
repo.AllowDeploy = *in.AllowDeploy
}
if in.IsGated != nil {
repo.IsGated = *in.IsGated
if in.RequireApproval != nil {
if mode := model.ApprovalMode(*in.RequireApproval); mode.Valid() {
repo.RequireApproval = mode
} else {
c.String(http.StatusBadRequest, "Invalid require-approval setting")
return
}
} else if in.IsGated != nil { // TODO: remove isGated in next major release
if *in.IsGated {
repo.RequireApproval = model.RequireApprovalAllEvents
} else {
repo.RequireApproval = model.RequireApprovalForks
}
}
if in.Timeout != nil {
repo.Timeout = *in.Timeout

View file

@ -179,6 +179,7 @@ func convertPullHook(from *internal.PullRequestHook) *model.Pipeline {
Author: from.Actor.Login,
Sender: from.Actor.Login,
Timestamp: from.PullRequest.Updated.UTC().Unix(),
FromFork: from.PullRequest.Source.Repo.UUID != from.PullRequest.Dest.Repo.UUID,
}
if from.PullRequest.State == stateClosed {

View file

@ -122,6 +122,7 @@ func convertPullRequestEvent(ev *bb.PullRequestEvent, baseURL string) *model.Pip
Ref: fmt.Sprintf("refs/pull-requests/%d/from", ev.PullRequest.ID),
ForgeURL: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.PullRequest.Source.Repository.Project.Key, ev.PullRequest.Source.Repository.Slug, ev.PullRequest.Source.Latest),
Refspec: fmt.Sprintf("%s:%s", ev.PullRequest.Source.DisplayID, ev.PullRequest.Target.DisplayID),
FromFork: ev.PullRequest.Source.Repository.ID != ev.PullRequest.Target.Repository.ID,
}
if ev.EventKey == bb.EventKeyPullRequestMerged || ev.EventKey == bb.EventKeyPullRequestDeclined || ev.EventKey == bb.EventKeyPullRequestDeleted {

View file

@ -170,6 +170,7 @@ func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline {
hook.PullRequest.Base.Ref,
),
PullRequestLabels: convertLabels(hook.PullRequest.Labels),
FromFork: hook.PullRequest.Head.RepoID != hook.PullRequest.Base.RepoID,
}
return pipeline

View file

@ -171,6 +171,7 @@ func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline {
hook.PullRequest.Base.Ref,
),
PullRequestLabels: convertLabels(hook.PullRequest.Labels),
FromFork: hook.PullRequest.Head.RepoID != hook.PullRequest.Base.RepoID,
}
return pipeline

View file

@ -157,6 +157,8 @@ func parsePullHook(hook *github.PullRequestEvent, merge bool) (*github.PullReque
event = model.EventPullClosed
}
fromFork := hook.GetPullRequest().GetHead().GetRepo().GetID() != hook.GetPullRequest().GetBase().GetRepo().GetID()
pipeline := &model.Pipeline{
Event: event,
Commit: hook.GetPullRequest().GetHead().GetSHA(),
@ -173,6 +175,7 @@ func parsePullHook(hook *github.PullRequestEvent, merge bool) (*github.PullReque
hook.GetPullRequest().GetBase().GetRef(),
),
PullRequestLabels: convertLabels(hook.GetPullRequest().Labels),
FromFork: fromFork,
}
if merge {
pipeline.Ref = fmt.Sprintf(mergeRefs, hook.GetPullRequest().GetNumber())

View file

@ -138,6 +138,7 @@ func convertMergeRequestHook(hook *gitlab.MergeEvent, req *http.Request) (int, *
pipeline.Title = obj.Title
pipeline.ForgeURL = obj.URL
pipeline.PullRequestLabels = convertLabels(hook.Labels)
pipeline.FromFork = target.PathWithNamespace != source.PathWithNamespace
return obj.IID, repo, pipeline, nil
}

View file

@ -52,6 +52,7 @@ type Pipeline struct {
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"`
FromFork bool `json:"from_fork,omitempty" xorm:"from_fork"`
} // @name Pipeline
// TableName return database table name for xorm.

View file

@ -20,6 +20,27 @@ import (
"strings"
)
type ApprovalMode string
const (
RequireApprovalNone ApprovalMode = "none" // require approval for no events
RequireApprovalForks ApprovalMode = "forks" // require approval for PRs from forks (default)
RequireApprovalPullRequests ApprovalMode = "pull_requests" // require approval for all PRs
RequireApprovalAllEvents ApprovalMode = "all_events" // require approval for all external events
)
func (mode ApprovalMode) Valid() bool {
switch mode {
case RequireApprovalNone,
RequireApprovalForks,
RequireApprovalPullRequests,
RequireApprovalAllEvents:
return true
default:
return false
}
}
// Repo represents a repository.
type Repo struct {
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'id'"`
@ -41,7 +62,7 @@ type Repo struct {
Visibility RepoVisibility `json:"visibility" xorm:"varchar(10) 'visibility'"`
IsSCMPrivate bool `json:"private" xorm:"private"`
Trusted TrustedConfiguration `json:"trusted" xorm:"json 'trusted'"`
IsGated bool `json:"gated" xorm:"gated"`
RequireApproval ApprovalMode `json:"require_approval" xorm:"varchar(50) require_approval"`
IsActive bool `json:"active" xorm:"active"`
AllowPull bool `json:"allow_pr" xorm:"allow_pr"`
AllowDeploy bool `json:"allow_deploy" xorm:"allow_deploy"`
@ -107,7 +128,8 @@ func (r *Repo) Update(from *Repo) {
// RepoPatch represents a repository patch object.
type RepoPatch struct {
Config *string `json:"config_file,omitempty"`
IsGated *bool `json:"gated,omitempty"`
IsGated *bool `json:"gated,omitempty"` // TODO: deprecated in favor of RequireApproval => Remove in next major release
RequireApproval *string `json:"require_approval,omitempty"`
Timeout *int64 `json:"timeout,omitempty"`
Visibility *string `json:"visibility,omitempty"`
AllowPull *bool `json:"allow_pr,omitempty"`

View file

@ -27,8 +27,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/server/store"
)
// Approve update the status to pending for a blocked pipeline because of a gated repo
// and start them afterward.
// Approve update the status to pending for a blocked pipeline so it can be executed.
func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipeline, user *model.User, repo *model.Repo) (*model.Pipeline, error) {
if currentPipeline.Status != model.StatusBlocked {
return nil, ErrBadRequest{Msg: fmt.Sprintf("cannot approve a pipeline with status %s", currentPipeline.Status)}

View file

@ -68,7 +68,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
// update some pipeline fields
pipeline.RepoID = repo.ID
pipeline.Status = model.StatusCreated
setGatedState(repo, pipeline)
setApprovalState(repo, pipeline)
err = _store.CreatePipeline(pipeline)
if err != nil {
msg := fmt.Errorf("failed to save pipeline for %s", repo.FullName)

View file

@ -26,7 +26,7 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/server/store"
)
// Decline updates the status to declined for blocked pipelines because of a gated repo.
// Decline updates the status to declined for blocked pipelines.
func Decline(ctx context.Context, store store.Store, pipeline *model.Pipeline, user *model.User, repo *model.Repo) (*model.Pipeline, error) {
forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
if err != nil {

View file

@ -16,11 +16,40 @@ package pipeline
import "go.woodpecker-ci.org/woodpecker/v2/server/model"
func setGatedState(repo *model.Repo, pipeline *model.Pipeline) {
// TODO(336): extend gated feature with an allow/block List
if repo.IsGated &&
// events created by woodpecker itself should run right away
pipeline.Event != model.EventCron && pipeline.Event != model.EventManual {
pipeline.Status = model.StatusBlocked
func setApprovalState(repo *model.Repo, pipeline *model.Pipeline) {
if !needsApproval(repo, pipeline) {
return
}
// set pipeline status to blocked and require approval
pipeline.Status = model.StatusBlocked
}
func needsApproval(repo *model.Repo, pipeline *model.Pipeline) bool {
// skip events created by woodpecker itself
if pipeline.Event == model.EventCron || pipeline.Event == model.EventManual {
return false
}
// repository allows all events without approval
if repo.RequireApproval == model.RequireApprovalNone {
return false
}
// repository requires approval for pull requests from forks
if pipeline.Event == model.EventPull && pipeline.FromFork {
return true
}
// repository requires approval for pull requests
if pipeline.Event == model.EventPull && repo.RequireApproval == model.RequireApprovalPullRequests {
return true
}
// repository requires approval for all events
if repo.RequireApproval == model.RequireApprovalAllEvents {
return true
}
return false
}

View file

@ -0,0 +1,78 @@
package pipeline
import (
"testing"
"github.com/stretchr/testify/assert"
"go.woodpecker-ci.org/woodpecker/v2/server/model"
)
func TestSetGatedState(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
repo *model.Repo
pipeline *model.Pipeline
expectBlocked bool
}{
{
name: "by-pass for cron",
repo: &model.Repo{
RequireApproval: model.RequireApprovalAllEvents,
},
pipeline: &model.Pipeline{
Event: model.EventCron,
},
expectBlocked: false,
},
{
name: "by-pass for manual pipeline",
repo: &model.Repo{
RequireApproval: model.RequireApprovalAllEvents,
},
pipeline: &model.Pipeline{
Event: model.EventManual,
},
expectBlocked: false,
},
{
name: "require approval for fork PRs",
repo: &model.Repo{
RequireApproval: model.RequireApprovalForks,
},
pipeline: &model.Pipeline{
Event: model.EventPull,
FromFork: true,
},
expectBlocked: true,
},
{
name: "require approval for PRs",
repo: &model.Repo{
RequireApproval: model.RequireApprovalPullRequests,
},
pipeline: &model.Pipeline{
Event: model.EventPull,
FromFork: false,
},
expectBlocked: true,
},
{
name: "require approval for everything",
repo: &model.Repo{
RequireApproval: model.RequireApprovalAllEvents,
},
pipeline: &model.Pipeline{
Event: model.EventPush,
},
expectBlocked: true,
},
}
for _, tc := range testCases {
setApprovalState(tc.repo, tc.pipeline)
assert.Equal(t, tc.expectBlocked, tc.pipeline.Status == model.StatusBlocked)
}
}

View file

@ -16,6 +16,7 @@ package queue
import (
"context"
"errors"
"fmt"
"sync"
"testing"
@ -26,64 +27,85 @@ import (
"go.woodpecker-ci.org/woodpecker/v2/server/model"
)
var filterFnTrue = func(*model.Task) (bool, int) { return true, 1 }
var (
filterFnTrue = func(*model.Task) (bool, int) { return true, 1 }
genDummyTask = func() *model.Task {
return &model.Task{
ID: "1",
Data: []byte("{}"),
}
}
waitForProcess = func() { time.Sleep(processTimeInterval + 10*time.Millisecond) }
)
func TestFifo(t *testing.T) {
want := &model.Task{ID: "1"}
ctx := context.Background()
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
q := NewMemoryQueue(ctx)
assert.NoError(t, q.Push(ctx, want))
dummyTask := genDummyTask()
assert.NoError(t, q.Push(ctx, dummyTask))
waitForProcess()
info := q.Info(ctx)
assert.Len(t, info.Pending, 1, "expect task in pending queue")
got, err := q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, want, got)
assert.Equal(t, dummyTask, got)
waitForProcess()
info = q.Info(ctx)
assert.Len(t, info.Pending, 0, "expect task removed from pending queue")
assert.Len(t, info.Running, 1, "expect task in running queue")
assert.NoError(t, q.Done(ctx, got.ID, model.StatusSuccess))
waitForProcess()
info = q.Info(ctx)
assert.Len(t, info.Pending, 0, "expect task removed from pending queue")
assert.Len(t, info.Running, 0, "expect task removed from running queue")
}
func TestFifoExpire(t *testing.T) {
want := &model.Task{ID: "1"}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
dummyTask := genDummyTask()
q.extension = 0
assert.NoError(t, q.Push(ctx, want))
assert.NoError(t, q.Push(ctx, dummyTask))
waitForProcess()
info := q.Info(ctx)
assert.Len(t, info.Pending, 1, "expect task in pending queue")
got, err := q.Poll(ctx, 1, filterFnTrue)
waitForProcess()
assert.NoError(t, err)
assert.Equal(t, want, got)
assert.Equal(t, dummyTask, got)
// cancel the context to let the process func end
go func() {
time.Sleep(time.Millisecond)
cancel(nil)
}()
q.process()
info = q.Info(ctx)
assert.Len(t, info.Pending, 1, "expect task re-added to pending queue")
}
func TestFifoWait(t *testing.T) {
want := &model.Task{ID: "1"}
ctx := context.Background()
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NoError(t, q.Push(ctx, want))
assert.NotNil(t, q)
dummyTask := genDummyTask()
assert.NoError(t, q.Push(ctx, dummyTask))
waitForProcess()
got, err := q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, want, got)
assert.Equal(t, dummyTask, got)
var wg sync.WaitGroup
wg.Add(1)
@ -98,27 +120,34 @@ func TestFifoWait(t *testing.T) {
}
func TestFifoEvict(t *testing.T) {
t1 := &model.Task{ID: "1"}
ctx := context.Background()
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
q := NewMemoryQueue(ctx)
assert.NoError(t, q.Push(ctx, t1))
dummyTask := genDummyTask()
assert.NoError(t, q.Push(ctx, dummyTask))
waitForProcess()
info := q.Info(ctx)
assert.Len(t, info.Pending, 1, "expect task in pending queue")
err := q.Evict(ctx, t1.ID)
err := q.Evict(ctx, dummyTask.ID)
assert.NoError(t, err)
waitForProcess()
info = q.Info(ctx)
assert.Len(t, info.Pending, 0)
err = q.Evict(ctx, t1.ID)
err = q.Evict(ctx, dummyTask.ID)
assert.ErrorIs(t, err, ErrNotFound)
}
func TestFifoDependencies(t *testing.T) {
ctx := context.Background()
task1 := &model.Task{
ID: "1",
}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
task1 := genDummyTask()
task2 := &model.Task{
ID: "2",
Dependencies: []string{"1"},
@ -126,31 +155,34 @@ func TestFifoDependencies(t *testing.T) {
}
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task1}))
waitForProcess()
got, err := q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, task1, got)
waitForProcess()
assert.NoError(t, q.Done(ctx, got.ID, model.StatusSuccess))
waitForProcess()
got, err = q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, task2, got)
}
func TestFifoErrors(t *testing.T) {
ctx := context.Background()
task1 := &model.Task{
ID: "1",
}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
task1 := genDummyTask()
task2 := &model.Task{
ID: "2",
Dependencies: []string{"1"},
DepStatus: make(map[string]model.StatusValue),
}
task3 := &model.Task{
ID: "3",
Dependencies: []string{"1"},
@ -159,19 +191,24 @@ func TestFifoErrors(t *testing.T) {
}
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
waitForProcess()
got, err := q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, task1, got)
assert.NoError(t, q.Error(ctx, got.ID, fmt.Errorf("exit code 1, there was an error")))
waitForProcess()
got, err = q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, task2, got)
assert.False(t, got.ShouldRun())
waitForProcess()
got, err = q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, task3, got)
@ -179,15 +216,13 @@ func TestFifoErrors(t *testing.T) {
}
func TestFifoErrors2(t *testing.T) {
ctx := context.Background()
task1 := &model.Task{
ID: "1",
}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
task1 := genDummyTask()
task2 := &model.Task{
ID: "2",
}
task3 := &model.Task{
ID: "3",
Dependencies: []string{"1", "2"},
@ -195,9 +230,12 @@ func TestFifoErrors2(t *testing.T) {
}
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
for i := 0; i < 2; i++ {
waitForProcess()
got, err := q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.False(t, got != task1 && got != task2, "expect task1 or task2 returned from queue as task3 depends on them")
@ -210,6 +248,7 @@ func TestFifoErrors2(t *testing.T) {
}
}
waitForProcess()
got, err := q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, task3, got)
@ -217,17 +256,15 @@ func TestFifoErrors2(t *testing.T) {
}
func TestFifoErrorsMultiThread(t *testing.T) {
ctx := context.Background()
task1 := &model.Task{
ID: "1",
}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
task1 := genDummyTask()
task2 := &model.Task{
ID: "2",
Dependencies: []string{"1"},
DepStatus: make(map[string]model.StatusValue),
}
task3 := &model.Task{
ID: "3",
Dependencies: []string{"1", "2"},
@ -235,15 +272,21 @@ func TestFifoErrorsMultiThread(t *testing.T) {
}
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
obtainedWorkCh := make(chan *model.Task)
defer func() { close(obtainedWorkCh) }()
for i := 0; i < 10; i++ {
go func(i int) {
for {
fmt.Printf("Worker %d started\n", i)
got, err := q.Poll(ctx, 1, filterFnTrue)
if err != nil && errors.Is(err, context.Canceled) {
return
}
assert.NoError(t, err)
obtainedWorkCh <- got
}
@ -266,7 +309,11 @@ func TestFifoErrorsMultiThread(t *testing.T) {
go func() {
for {
fmt.Printf("Worker spawned\n")
got, _ := q.Poll(ctx, 1, filterFnTrue)
got, err := q.Poll(ctx, 1, filterFnTrue)
if err != nil && errors.Is(err, context.Canceled) {
return
}
assert.NoError(t, err)
obtainedWorkCh <- got
}
}()
@ -277,7 +324,11 @@ func TestFifoErrorsMultiThread(t *testing.T) {
go func() {
for {
fmt.Printf("Worker spawned\n")
got, _ := q.Poll(ctx, 1, filterFnTrue)
got, err := q.Poll(ctx, 1, filterFnTrue)
if err != nil && errors.Is(err, context.Canceled) {
return
}
assert.NoError(t, err)
obtainedWorkCh <- got
}
}()
@ -297,17 +348,15 @@ func TestFifoErrorsMultiThread(t *testing.T) {
}
func TestFifoTransitiveErrors(t *testing.T) {
ctx := context.Background()
task1 := &model.Task{
ID: "1",
}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
task1 := genDummyTask()
task2 := &model.Task{
ID: "2",
Dependencies: []string{"1"},
DepStatus: make(map[string]model.StatusValue),
}
task3 := &model.Task{
ID: "3",
Dependencies: []string{"2"},
@ -315,19 +364,24 @@ func TestFifoTransitiveErrors(t *testing.T) {
}
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
waitForProcess()
got, err := q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, task1, got)
assert.NoError(t, q.Error(ctx, got.ID, fmt.Errorf("exit code 1, there was an error")))
waitForProcess()
got, err = q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, task2, got)
assert.False(t, got.ShouldRun(), "expect task2 should not run, since task1 failed")
assert.NoError(t, q.Done(ctx, got.ID, model.StatusSkipped))
waitForProcess()
got, err = q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, err)
assert.Equal(t, task3, got)
@ -335,17 +389,15 @@ func TestFifoTransitiveErrors(t *testing.T) {
}
func TestFifoCancel(t *testing.T) {
ctx := context.Background()
task1 := &model.Task{
ID: "1",
}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
task1 := genDummyTask()
task2 := &model.Task{
ID: "2",
Dependencies: []string{"1"},
DepStatus: make(map[string]model.StatusValue),
}
task3 := &model.Task{
ID: "3",
Dependencies: []string{"1"},
@ -354,24 +406,33 @@ func TestFifoCancel(t *testing.T) {
}
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
_, _ = q.Poll(ctx, 1, filterFnTrue)
assert.NoError(t, q.Error(ctx, task1.ID, fmt.Errorf("canceled")))
assert.NoError(t, q.Error(ctx, task2.ID, fmt.Errorf("canceled")))
assert.NoError(t, q.Error(ctx, task3.ID, fmt.Errorf("canceled")))
info := q.Info(ctx)
assert.Len(t, info.Pending, 0, "all pipelines should be canceled")
time.Sleep(processTimeInterval * 2)
info = q.Info(ctx)
assert.Len(t, info.Pending, 2, "canceled are rescheduled")
assert.Len(t, info.Running, 0, "canceled are rescheduled")
assert.Len(t, info.WaitingOnDeps, 0, "canceled are rescheduled")
}
func TestFifoPause(t *testing.T) {
ctx := context.Background()
task1 := &model.Task{
ID: "1",
}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
dummyTask := genDummyTask()
var wg sync.WaitGroup
wg.Add(1)
go func() {
@ -381,8 +442,8 @@ func TestFifoPause(t *testing.T) {
q.Pause()
t0 := time.Now()
assert.NoError(t, q.Push(ctx, task1))
time.Sleep(20 * time.Millisecond)
assert.NoError(t, q.Push(ctx, dummyTask))
waitForProcess()
q.Resume()
wg.Wait()
@ -391,37 +452,37 @@ func TestFifoPause(t *testing.T) {
assert.Greater(t, t1.Sub(t0), 20*time.Millisecond, "should have waited til resume")
q.Pause()
assert.NoError(t, q.Push(ctx, task1))
assert.NoError(t, q.Push(ctx, dummyTask))
q.Resume()
_, _ = q.Poll(ctx, 1, filterFnTrue)
}
func TestFifoPauseResume(t *testing.T) {
ctx := context.Background()
task1 := &model.Task{
ID: "1",
}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
dummyTask := genDummyTask()
q.Pause()
assert.NoError(t, q.Push(ctx, task1))
assert.NoError(t, q.Push(ctx, dummyTask))
q.Resume()
_, _ = q.Poll(ctx, 1, filterFnTrue)
}
func TestWaitingVsPending(t *testing.T) {
ctx := context.Background()
task1 := &model.Task{
ID: "1",
}
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
task1 := genDummyTask()
task2 := &model.Task{
ID: "2",
Dependencies: []string{"1"},
DepStatus: make(map[string]model.StatusValue),
}
task3 := &model.Task{
ID: "3",
Dependencies: []string{"1"},
@ -430,10 +491,13 @@ func TestWaitingVsPending(t *testing.T) {
}
q, _ := NewMemoryQueue(ctx).(*fifo)
assert.NotNil(t, q)
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
got, _ := q.Poll(ctx, 1, filterFnTrue)
waitForProcess()
info := q.Info(ctx)
assert.Equal(t, 2, info.Stats.WaitingOnDeps)
@ -442,6 +506,7 @@ func TestWaitingVsPending(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, task2, got)
waitForProcess()
info = q.Info(ctx)
assert.Equal(t, 0, info.Stats.WaitingOnDeps)
assert.Equal(t, 1, info.Stats.Pending)
@ -518,7 +583,9 @@ func TestShouldRun(t *testing.T) {
}
func TestFifoWithScoring(t *testing.T) {
ctx := context.Background()
ctx, cancel := context.WithCancelCause(context.Background())
t.Cleanup(func() { cancel(nil) })
q := NewMemoryQueue(ctx)
// Create tasks with different labels
@ -530,9 +597,7 @@ func TestFifoWithScoring(t *testing.T) {
{ID: "5", Labels: map[string]string{"org-id": "*", "platform": "linux"}},
}
for _, task := range tasks {
assert.NoError(t, q.Push(ctx, task))
}
assert.NoError(t, q.PushAtOnce(ctx, tasks))
// Create filter functions for different workers
filters := map[int]FilterFn{

View file

@ -0,0 +1,72 @@
// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package migration
import (
"fmt"
"src.techknowlogick.com/xormigrate"
"xorm.io/builder"
"xorm.io/xorm"
)
var gatedToRequireApproval = xormigrate.Migration{
ID: "gated-to-require-approval",
MigrateSession: func(sess *xorm.Session) (err error) {
const (
RequireApprovalNone string = "none"
RequireApprovalForks string = "forks"
RequireApprovalPullRequests string = "pull_requests"
RequireApprovalAllEvents string = "all_events"
)
type repos struct {
ID int64 `xorm:"pk autoincr 'id'"`
IsGated bool `xorm:"gated"`
RequireApproval string `xorm:"require_approval"`
Visibility string `xorm:"varchar(10) 'visibility'"`
}
if err := sess.Sync(new(repos)); err != nil {
return fmt.Errorf("sync new models failed: %w", err)
}
// migrate gated repos
if _, err := sess.Exec(
builder.Update(builder.Eq{"require_approval": RequireApprovalAllEvents}).
From("repos").
Where(builder.Eq{"gated": true})); err != nil {
return err
}
// migrate public repos to new default require approval
if _, err := sess.Exec(
builder.Update(builder.Eq{"require_approval": RequireApprovalForks}).
From("repos").
Where(builder.Eq{"gated": false, "visibility": "public"})); err != nil {
return err
}
// migrate private repos to new default require approval
if _, err := sess.Exec(
builder.Update(builder.Eq{"require_approval": RequireApprovalNone}).
From("repos").
Where(builder.Eq{"gated": false}.And(builder.Neq{"visibility": "public"}))); err != nil {
return err
}
return dropTableColumns(sess, "repos", "gated")
},
}

View file

@ -47,6 +47,7 @@ var migrationTasks = []*xormigrate.Migration{
&addCustomLabelsToAgent,
&splitTrusted,
&correctPotentialCorruptOrgsUsersRelation,
&gatedToRequireApproval,
&removeRepoScm,
}

View file

@ -216,36 +216,32 @@ packages:
resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
engines: {node: '>=6.9.0'}
'@babel/compat-data@7.25.7':
resolution: {integrity: sha512-9ickoLz+hcXCeh7jrcin+/SLWm+GkxE2kTvoYyp38p4WkdFXfQJxDFGWp/YHjiKLPx06z2A7W8XKuqbReXDzsw==}
'@babel/compat-data@7.26.2':
resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==}
engines: {node: '>=6.9.0'}
'@babel/core@7.25.7':
resolution: {integrity: sha512-yJ474Zv3cwiSOO9nXJuqzvwEeM+chDuQ8GJirw+pZ91sCGCyOZ3dJkVE09fTV0VEVzXyLWhh3G/AolYTPX7Mow==}
'@babel/core@7.26.0':
resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==}
engines: {node: '>=6.9.0'}
'@babel/generator@7.26.2':
resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==}
engines: {node: '>=6.9.0'}
'@babel/helper-compilation-targets@7.25.7':
resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==}
'@babel/helper-compilation-targets@7.25.9':
resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-module-imports@7.25.7':
resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==}
'@babel/helper-module-imports@7.25.9':
resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
engines: {node: '>=6.9.0'}
'@babel/helper-module-transforms@7.25.7':
resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==}
'@babel/helper-module-transforms@7.26.0':
resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0
'@babel/helper-simple-access@7.25.7':
resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-string-parser@7.25.9':
resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
engines: {node: '>=6.9.0'}
@ -254,12 +250,12 @@ packages:
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
engines: {node: '>=6.9.0'}
'@babel/helper-validator-option@7.25.7':
resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==}
'@babel/helper-validator-option@7.25.9':
resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
engines: {node: '>=6.9.0'}
'@babel/helpers@7.25.7':
resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==}
'@babel/helpers@7.26.0':
resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==}
engines: {node: '>=6.9.0'}
'@babel/parser@7.26.2':
@ -267,8 +263,8 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
'@babel/runtime@7.25.7':
resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==}
'@babel/runtime@7.26.0':
resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
engines: {node: '>=6.9.0'}
'@babel/template@7.25.9':
@ -443,12 +439,6 @@ packages:
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
'@eslint-community/eslint-utils@4.4.0':
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/eslint-utils@4.4.1':
resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@ -476,10 +466,6 @@ packages:
resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/eslintrc@3.1.0':
resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@eslint/eslintrc@3.2.0':
resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -559,8 +545,8 @@ packages:
resolution: {integrity: sha512-AFbhEo10DP095/45EauinQJ5hJ3rJUmuuqltGguvc3WsvezZN+g8qNHLGWKu60FHQVizMrQY7VJ+zVlBXlQQkQ==}
engines: {node: '>= 16'}
'@intlify/message-compiler@11.0.0-beta.0':
resolution: {integrity: sha512-HWHv6jj7wJmHY5I73k80lBffDfnpqjx5vvn965YJB4lLvo0zkP3H15WGkwrLa/OR6fyYoP0DJVUKj9g2q7QJCA==}
'@intlify/message-compiler@11.0.0-beta.1':
resolution: {integrity: sha512-yMXfN4hg/EeSdtWfmoMrwB9X4TXwkBoZlTIpNydQaW9y0tSJHGnUPRoahtkbsyACCm9leSJINLY4jQ0rK6BK0Q==}
engines: {node: '>= 16'}
'@intlify/message-compiler@9.14.1':
@ -571,8 +557,8 @@ packages:
resolution: {integrity: sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==}
engines: {node: '>= 16'}
'@intlify/shared@11.0.0-beta.0':
resolution: {integrity: sha512-v/IAS+BBeaWIKPI4CgSKsil2vJ64naIUUENha3e7jfhq1CxinXQquQYUM2GcCC86USxzTGgu67nafbaYzHS3vA==}
'@intlify/shared@11.0.0-beta.1':
resolution: {integrity: sha512-Md/4T/QOx7wZ7zqVzSsMx2M/9Mx/1nsgsjXS5SFIowFKydqUhMz7K+y7pMFh781aNYz+rGXYwad8E9/+InK9SA==}
engines: {node: '>= 16'}
'@intlify/shared@9.14.1':
@ -663,8 +649,8 @@ packages:
resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
'@rollup/pluginutils@5.1.2':
resolution: {integrity: sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==}
'@rollup/pluginutils@5.1.3':
resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
@ -1080,11 +1066,6 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
browserslist@4.24.0:
resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
browserslist@4.24.2:
resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@ -1102,9 +1083,6 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
caniuse-lite@1.0.30001667:
resolution: {integrity: sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==}
caniuse-lite@1.0.30001680:
resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==}
@ -1305,9 +1283,6 @@ packages:
engines: {node: '>=14'}
hasBin: true
electron-to-chromium@1.5.32:
resolution: {integrity: sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw==}
electron-to-chromium@1.5.62:
resolution: {integrity: sha512-t8c+zLmJHa9dJy96yBZRXGQYoiCEnHYgFwn1asvSPZSUdVxnB62A4RASd7k41ytG3ErFBA0TpHlKg9D9SQBmLg==}
@ -1366,8 +1341,8 @@ packages:
peerDependencies:
eslint: '>=6.0.0'
eslint-compat-utils@0.6.0:
resolution: {integrity: sha512-1vVBdI/HLS6HTHVQCJGlN+LOF0w1Rs/WB9se23mQr84cRM0iMM8PulMFFhQdQ1BvS0cGwjpis4xziI91Rk0l6g==}
eslint-compat-utils@0.6.3:
resolution: {integrity: sha512-9IDdksh5pUYP2ZLi7mOdROxVjLY8gY2qKxprmrJ/5Dyqud7M/IFKxF3o0VLlRhITm1pK6Fk7NiBxE39M/VlUcw==}
engines: {node: '>=12'}
peerDependencies:
eslint: '>=6.0.0'
@ -1635,8 +1610,8 @@ packages:
resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==}
engines: {node: '>=14'}
form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
form-data@4.0.1:
resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==}
engines: {node: '>= 6'}
fs.realpath@1.0.0:
@ -1693,10 +1668,6 @@ packages:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
globals@15.10.0:
resolution: {integrity: sha512-tqFIbz83w4Y5TCbtgjZjApohbuh7K9BxGYFm7ifwDR240tvdb7P9x+/9VvUKlmkPoiknoJtanI8UOrqxS3a7lQ==}
engines: {node: '>=18'}
globals@15.12.0:
resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==}
engines: {node: '>=18'}
@ -2174,8 +2145,8 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
parse5@7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
parse5@7.2.1:
resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==}
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
@ -2206,9 +2177,6 @@ packages:
resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
engines: {node: '>= 14.16'}
picocolors@1.1.0:
resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -2259,10 +2227,6 @@ packages:
resolution: {integrity: sha512-8I7Cd8sxiEITIp32xBK4K/Aj1ukX6vuWnx8oY/oAH35NfQI4OZaY5nd68Yx8HeN5S49uhQ6DL0rNk0ZBu/TaLg==}
engines: {node: ^8.10.0 || ^10.13.0 || ^11.10.1 || >=12.13.0}
postcss@8.4.47:
resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.4.49:
resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
engines: {node: ^10 || ^12 || >=14}
@ -2488,10 +2452,6 @@ packages:
resolution: {integrity: sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==}
engines: {node: '>=12.20'}
synckit@0.9.1:
resolution: {integrity: sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==}
engines: {node: ^14.18.0 || >=16.0.0}
synckit@0.9.2:
resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==}
engines: {node: ^14.18.0 || >=16.0.0}
@ -2521,11 +2481,11 @@ packages:
resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
engines: {node: '>=14.0.0'}
tldts-core@6.1.50:
resolution: {integrity: sha512-na2EcZqmdA2iV9zHV7OHQDxxdciEpxrjbkp+aHmZgnZKHzoElLajP59np5/4+sare9fQBfixgvXKx8ev1d7ytw==}
tldts-core@6.1.61:
resolution: {integrity: sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==}
tldts@6.1.50:
resolution: {integrity: sha512-q9GOap6q3KCsLMdOjXhWU5jVZ8/1dIib898JBRLsN+tBhENpBDcAVQbE0epADOjw11FhQQy9AcbqKGBQPUfTQA==}
tldts@6.1.61:
resolution: {integrity: sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==}
hasBin: true
to-regex-range@5.0.1:
@ -2544,21 +2504,12 @@ packages:
resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
engines: {node: '>=18'}
ts-api-utils@1.3.0:
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
engines: {node: '>=16'}
peerDependencies:
typescript: '>=4.2.0'
ts-api-utils@1.4.0:
resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==}
engines: {node: '>=16'}
peerDependencies:
typescript: '>=4.2.0'
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@ -2605,14 +2556,9 @@ packages:
unist-util-visit@5.0.0:
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
unplugin@1.14.1:
resolution: {integrity: sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w==}
unplugin@1.16.0:
resolution: {integrity: sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
webpack-sources: ^3
peerDependenciesMeta:
webpack-sources:
optional: true
update-browserslist-db@1.1.1:
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
@ -2707,8 +2653,8 @@ packages:
vscode-uri@3.0.8:
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
vue-component-type-helpers@2.1.6:
resolution: {integrity: sha512-ng11B8B/ZADUMMOsRbqv0arc442q7lifSubD0v8oDXIFoMg/mXwAPUunrroIDkY+mcD0dHKccdaznSVp8EoX3w==}
vue-component-type-helpers@2.1.10:
resolution: {integrity: sha512-lfgdSLQKrUmADiSV6PbBvYgQ33KF3Ztv6gP85MfGaGaSGMTXORVaHT1EHfsqCgzRNBstPKYDmvAV9Do5CmJ07A==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
@ -2926,16 +2872,16 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
'@babel/compat-data@7.25.7': {}
'@babel/compat-data@7.26.2': {}
'@babel/core@7.25.7':
'@babel/core@7.26.0':
dependencies:
'@ampproject/remapping': 2.3.0
'@babel/code-frame': 7.26.2
'@babel/generator': 7.26.2
'@babel/helper-compilation-targets': 7.25.7
'@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.7)
'@babel/helpers': 7.25.7
'@babel/helper-compilation-targets': 7.25.9
'@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0)
'@babel/helpers': 7.26.0
'@babel/parser': 7.26.2
'@babel/template': 7.25.9
'@babel/traverse': 7.25.9
@ -2956,45 +2902,37 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.25
jsesc: 3.0.2
'@babel/helper-compilation-targets@7.25.7':
'@babel/helper-compilation-targets@7.25.9':
dependencies:
'@babel/compat-data': 7.25.7
'@babel/helper-validator-option': 7.25.7
browserslist: 4.24.0
'@babel/compat-data': 7.26.2
'@babel/helper-validator-option': 7.25.9
browserslist: 4.24.2
lru-cache: 5.1.1
semver: 7.6.3
'@babel/helper-module-imports@7.25.7':
'@babel/helper-module-imports@7.25.9':
dependencies:
'@babel/traverse': 7.25.9
'@babel/types': 7.26.0
transitivePeerDependencies:
- supports-color
'@babel/helper-module-transforms@7.25.7(@babel/core@7.25.7)':
'@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)':
dependencies:
'@babel/core': 7.25.7
'@babel/helper-module-imports': 7.25.7
'@babel/helper-simple-access': 7.25.7
'@babel/core': 7.26.0
'@babel/helper-module-imports': 7.25.9
'@babel/helper-validator-identifier': 7.25.9
'@babel/traverse': 7.25.9
transitivePeerDependencies:
- supports-color
'@babel/helper-simple-access@7.25.7':
dependencies:
'@babel/traverse': 7.25.9
'@babel/types': 7.26.0
transitivePeerDependencies:
- supports-color
'@babel/helper-string-parser@7.25.9': {}
'@babel/helper-validator-identifier@7.25.9': {}
'@babel/helper-validator-option@7.25.7': {}
'@babel/helper-validator-option@7.25.9': {}
'@babel/helpers@7.25.7':
'@babel/helpers@7.26.0':
dependencies:
'@babel/template': 7.25.9
'@babel/types': 7.26.0
@ -3003,7 +2941,7 @@ snapshots:
dependencies:
'@babel/types': 7.26.0
'@babel/runtime@7.25.7':
'@babel/runtime@7.26.0':
dependencies:
regenerator-runtime: 0.14.1
@ -3128,11 +3066,6 @@ snapshots:
eslint: 9.15.0(jiti@1.21.6)
ignore: 5.3.2
'@eslint-community/eslint-utils@4.4.0(eslint@9.15.0(jiti@1.21.6))':
dependencies:
eslint: 9.15.0(jiti@1.21.6)
eslint-visitor-keys: 3.4.3
'@eslint-community/eslint-utils@4.4.1(eslint@9.15.0(jiti@1.21.6))':
dependencies:
eslint: 9.15.0(jiti@1.21.6)
@ -3154,20 +3087,6 @@ snapshots:
'@eslint/core@0.9.0': {}
'@eslint/eslintrc@3.1.0':
dependencies:
ajv: 6.12.6
debug: 4.3.7
espree: 10.3.0
globals: 14.0.0
ignore: 5.3.2
import-fresh: 3.3.0
js-yaml: 4.1.0
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
'@eslint/eslintrc@3.2.0':
dependencies:
ajv: 6.12.6
@ -3227,8 +3146,8 @@ snapshots:
'@intlify/bundle-utils@10.0.0(vue-i18n@10.0.4(vue@3.5.13(typescript@5.6.3)))':
dependencies:
'@intlify/message-compiler': 11.0.0-beta.0
'@intlify/shared': 11.0.0-beta.0
'@intlify/message-compiler': 11.0.0-beta.1
'@intlify/shared': 11.0.0-beta.1
acorn: 8.14.0
escodegen: 2.1.0
estree-walker: 2.0.2
@ -3251,14 +3170,14 @@ snapshots:
'@intlify/eslint-plugin-vue-i18n@3.0.0(eslint@9.15.0(jiti@1.21.6))':
dependencies:
'@eslint/eslintrc': 3.1.0
'@eslint/eslintrc': 3.2.0
'@intlify/core-base': 9.14.1
'@intlify/message-compiler': 9.14.1
debug: 4.3.7
eslint: 9.15.0(jiti@1.21.6)
eslint-compat-utils: 0.5.1(eslint@9.15.0(jiti@1.21.6))
glob: 10.4.5
globals: 15.10.0
globals: 15.12.0
ignore: 5.3.2
import-fresh: 3.3.0
is-language-code: 3.1.0
@ -3266,9 +3185,9 @@ snapshots:
json5: 2.2.3
jsonc-eslint-parser: 2.4.0
lodash: 4.17.21
parse5: 7.1.2
parse5: 7.2.1
semver: 7.6.3
synckit: 0.9.1
synckit: 0.9.2
vue-eslint-parser: 9.4.3(eslint@9.15.0(jiti@1.21.6))
yaml-eslint-parser: 1.2.3
transitivePeerDependencies:
@ -3279,9 +3198,9 @@ snapshots:
'@intlify/shared': 10.0.4
source-map-js: 1.2.1
'@intlify/message-compiler@11.0.0-beta.0':
'@intlify/message-compiler@11.0.0-beta.1':
dependencies:
'@intlify/shared': 11.0.0-beta.0
'@intlify/shared': 11.0.0-beta.1
source-map-js: 1.2.1
'@intlify/message-compiler@9.14.1':
@ -3291,17 +3210,17 @@ snapshots:
'@intlify/shared@10.0.4': {}
'@intlify/shared@11.0.0-beta.0': {}
'@intlify/shared@11.0.0-beta.1': {}
'@intlify/shared@9.14.1': {}
'@intlify/unplugin-vue-i18n@6.0.0(@vue/compiler-dom@3.5.13)(eslint@9.15.0(jiti@1.21.6))(rollup@4.27.2)(typescript@5.6.3)(vue-i18n@10.0.4(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3))':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.15.0(jiti@1.21.6))
'@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6))
'@intlify/bundle-utils': 10.0.0(vue-i18n@10.0.4(vue@3.5.13(typescript@5.6.3)))
'@intlify/shared': 10.0.4
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@10.0.4)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.4(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3))
'@rollup/pluginutils': 5.1.2(rollup@4.27.2)
'@rollup/pluginutils': 5.1.3(rollup@4.27.2)
'@typescript-eslint/scope-manager': 8.14.0
'@typescript-eslint/typescript-estree': 8.14.0(typescript@5.6.3)
debug: 4.3.7
@ -3311,7 +3230,7 @@ snapshots:
pathe: 1.1.2
picocolors: 1.1.1
source-map-js: 1.2.1
unplugin: 1.14.1
unplugin: 1.16.0
vue: 3.5.13(typescript@5.6.3)
optionalDependencies:
vue-i18n: 10.0.4(vue@3.5.13(typescript@5.6.3))
@ -3321,7 +3240,6 @@ snapshots:
- rollup
- supports-color
- typescript
- webpack-sources
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@10.0.4)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.4(vue@3.5.13(typescript@5.6.3)))(vue@3.5.13(typescript@5.6.3))':
dependencies:
@ -3383,11 +3301,11 @@ snapshots:
'@pkgr/core@0.1.1': {}
'@rollup/pluginutils@5.1.2(rollup@4.27.2)':
'@rollup/pluginutils@5.1.3(rollup@4.27.2)':
dependencies:
'@types/estree': 1.0.6
estree-walker: 2.0.2
picomatch: 2.3.1
picomatch: 4.0.2
optionalDependencies:
rollup: 4.27.2
@ -3561,7 +3479,7 @@ snapshots:
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.6.3
ts-api-utils: 1.3.0(typescript@5.6.3)
ts-api-utils: 1.4.0(typescript@5.6.3)
optionalDependencies:
typescript: 5.6.3
transitivePeerDependencies:
@ -3725,7 +3643,7 @@ snapshots:
'@vue/test-utils@2.4.6':
dependencies:
js-beautify: 1.15.1
vue-component-type-helpers: 2.1.6
vue-component-type-helpers: 2.1.10
'@vueuse/core@11.2.0(vue@3.5.13(typescript@5.6.3))':
dependencies:
@ -3832,13 +3750,6 @@ snapshots:
dependencies:
fill-range: 7.1.1
browserslist@4.24.0:
dependencies:
caniuse-lite: 1.0.30001667
electron-to-chromium: 1.5.32
node-releases: 2.0.18
update-browserslist-db: 1.1.1(browserslist@4.24.0)
browserslist@4.24.2:
dependencies:
caniuse-lite: 1.0.30001680
@ -3852,8 +3763,6 @@ snapshots:
callsites@3.1.0: {}
caniuse-lite@1.0.30001667: {}
caniuse-lite@1.0.30001680: {}
ccount@2.0.1: {}
@ -4036,8 +3945,6 @@ snapshots:
minimatch: 9.0.1
semver: 7.6.3
electron-to-chromium@1.5.32: {}
electron-to-chromium@1.5.62: {}
emoji-regex@8.0.0: {}
@ -4106,7 +4013,7 @@ snapshots:
eslint: 9.15.0(jiti@1.21.6)
semver: 7.6.3
eslint-compat-utils@0.6.0(eslint@9.15.0(jiti@1.21.6)):
eslint-compat-utils@0.6.3(eslint@9.15.0(jiti@1.21.6)):
dependencies:
eslint: 9.15.0(jiti@1.21.6)
semver: 7.6.3
@ -4194,7 +4101,7 @@ snapshots:
dependencies:
'@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6))
eslint: 9.15.0(jiti@1.21.6)
eslint-compat-utils: 0.6.0(eslint@9.15.0(jiti@1.21.6))
eslint-compat-utils: 0.6.3(eslint@9.15.0(jiti@1.21.6))
eslint-json-compat-utils: 0.2.1(eslint@9.15.0(jiti@1.21.6))(jsonc-eslint-parser@2.4.0)
espree: 9.6.1
graphemer: 1.4.0
@ -4284,13 +4191,13 @@ snapshots:
eslint-plugin-vue-scoped-css@2.8.1(eslint@9.15.0(jiti@1.21.6))(vue-eslint-parser@9.4.3(eslint@9.15.0(jiti@1.21.6))):
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@9.15.0(jiti@1.21.6))
'@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0(jiti@1.21.6))
eslint: 9.15.0(jiti@1.21.6)
eslint-compat-utils: 0.5.1(eslint@9.15.0(jiti@1.21.6))
lodash: 4.17.21
postcss: 8.4.47
postcss-safe-parser: 6.0.0(postcss@8.4.47)
postcss-scss: 4.0.9(postcss@8.4.47)
postcss: 8.4.49
postcss-safe-parser: 6.0.0(postcss@8.4.49)
postcss-scss: 4.0.9(postcss@8.4.49)
postcss-selector-parser: 6.1.2
postcss-styl: 0.12.3
vue-eslint-parser: 9.4.3(eslint@9.15.0(jiti@1.21.6))
@ -4468,7 +4375,7 @@ snapshots:
cross-spawn: 7.0.5
signal-exit: 4.1.0
form-data@4.0.0:
form-data@4.0.1:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
@ -4525,8 +4432,6 @@ snapshots:
globals@14.0.0: {}
globals@15.10.0: {}
globals@15.12.0: {}
graceful-fs@4.2.11: {}
@ -4605,7 +4510,7 @@ snapshots:
is-language-code@3.1.0:
dependencies:
'@babel/runtime': 7.25.7
'@babel/runtime': 7.26.0
is-number@7.0.0: {}
@ -4646,13 +4551,13 @@ snapshots:
cssstyle: 4.1.0
data-urls: 5.0.0
decimal.js: 10.4.3
form-data: 4.0.0
form-data: 4.0.1
html-encoding-sniffer: 4.0.0
http-proxy-agent: 7.0.2
https-proxy-agent: 7.0.5
is-potential-custom-element-name: 1.0.1
nwsapi: 2.2.13
parse5: 7.1.2
parse5: 7.2.1
rrweb-cssom: 0.7.1
saxes: 6.0.0
symbol-tree: 3.2.4
@ -5162,7 +5067,7 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
parse5@7.1.2:
parse5@7.2.1:
dependencies:
entities: 4.5.0
@ -5185,8 +5090,6 @@ snapshots:
pathval@2.0.0: {}
picocolors@1.1.0: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@ -5209,13 +5112,13 @@ snapshots:
pluralize@8.0.0: {}
postcss-safe-parser@6.0.0(postcss@8.4.47):
postcss-safe-parser@6.0.0(postcss@8.4.49):
dependencies:
postcss: 8.4.47
postcss: 8.4.49
postcss-scss@4.0.9(postcss@8.4.47):
postcss-scss@4.0.9(postcss@8.4.49):
dependencies:
postcss: 8.4.47
postcss: 8.4.49
postcss-selector-parser@6.1.2:
dependencies:
@ -5227,17 +5130,11 @@ snapshots:
debug: 4.3.7
fast-diff: 1.3.0
lodash.sortedlastindex: 4.1.0
postcss: 8.4.47
postcss: 8.4.49
stylus: 0.57.0
transitivePeerDependencies:
- supports-color
postcss@8.4.47:
dependencies:
nanoid: 3.3.7
picocolors: 1.1.1
source-map-js: 1.2.1
postcss@8.4.49:
dependencies:
nanoid: 3.3.7
@ -5459,7 +5356,7 @@ snapshots:
css-tree: 2.3.1
css-what: 6.1.0
csso: 5.0.5
picocolors: 1.1.0
picocolors: 1.1.1
symbol-tree@3.2.4: {}
@ -5467,11 +5364,6 @@ snapshots:
dependencies:
tslib: 2.8.1
synckit@0.9.1:
dependencies:
'@pkgr/core': 0.1.1
tslib: 2.7.0
synckit@0.9.2:
dependencies:
'@pkgr/core': 0.1.1
@ -5491,11 +5383,11 @@ snapshots:
tinyspy@3.0.2: {}
tldts-core@6.1.50: {}
tldts-core@6.1.61: {}
tldts@6.1.50:
tldts@6.1.61:
dependencies:
tldts-core: 6.1.50
tldts-core: 6.1.61
to-regex-range@5.0.1:
dependencies:
@ -5507,22 +5399,16 @@ snapshots:
tough-cookie@5.0.0:
dependencies:
tldts: 6.1.50
tldts: 6.1.61
tr46@5.0.0:
dependencies:
punycode: 2.3.1
ts-api-utils@1.3.0(typescript@5.6.3):
dependencies:
typescript: 5.6.3
ts-api-utils@1.4.0(typescript@5.6.3):
dependencies:
typescript: 5.6.3
tslib@2.7.0: {}
tslib@2.8.1: {}
type-check@0.4.0:
@ -5562,17 +5448,11 @@ snapshots:
unist-util-is: 6.0.0
unist-util-visit-parents: 6.0.1
unplugin@1.14.1:
unplugin@1.16.0:
dependencies:
acorn: 8.14.0
webpack-virtual-modules: 0.6.2
update-browserslist-db@1.1.1(browserslist@4.24.0):
dependencies:
browserslist: 4.24.0
escalade: 3.2.0
picocolors: 1.1.1
update-browserslist-db@1.1.1(browserslist@4.24.2):
dependencies:
browserslist: 4.24.2
@ -5610,7 +5490,7 @@ snapshots:
vite-plugin-prismjs@0.0.11(prismjs@1.29.0):
dependencies:
'@babel/core': 7.25.7
'@babel/core': 7.26.0
babel-plugin-prismjs: 2.1.0(prismjs@1.29.0)
transitivePeerDependencies:
- prismjs
@ -5679,7 +5559,7 @@ snapshots:
vscode-uri@3.0.8: {}
vue-component-type-helpers@2.1.6: {}
vue-component-type-helpers@2.1.10: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.6.3)):
dependencies:

View file

@ -88,10 +88,6 @@
"allow": "Allow deployments",
"desc": "Allow deployments from successful pipelines. Only use if you trust all users with push access."
},
"protected": {
"protected": "Protected",
"desc": "Every pipeline needs to be approved before being executed."
},
"netrc_only_trusted": {
"netrc_only_trusted": "Only inject netrc credentials into trusted clone plugins",
"desc": "If enabled, git netrc credentials are only available for trusted clone plugins set in `WOODPECKER_PLUGINS_TRUSTED_CLONE`. Otherwise, all clone plugins can use the netrc credentials. This option has no effect on non-clone steps."
@ -504,5 +500,14 @@
"internal_error": "Some internal error occurred",
"registration_closed": "The registration is closed",
"access_denied": "You are not allowed to access this instance",
"invalid_state": "The OAuth state is invalid"
"invalid_state": "The OAuth state is invalid",
"require_approval": {
"require_approval_for": "Require approval for",
"none": "No approval required",
"none_desc": "This setting can be dangerous and should only be used on private forges where all users are trusted.",
"forks": "Pull request from forked repositories",
"pull_requests": "All pull requests",
"all_events": "All events from forge",
"desc": "Prevent malicious pipelines from exposing secrets or running harmful tasks by approving them before execution."
}
}

View file

@ -1,6 +1,6 @@
<template>
<AgentManager
:desc="$t('admin.settings.agents.desc')"
:description="$t('admin.settings.agents.desc')"
:load-agents="loadAgents"
:create-agent="createAgent"
:update-agent="updateAgent"

View file

@ -1,5 +1,5 @@
<template>
<Settings :title="$t('admin.settings.orgs.orgs')" :desc="$t('admin.settings.orgs.desc')">
<Settings :title="$t('admin.settings.orgs.orgs')" :description="$t('admin.settings.orgs.desc')">
<div class="space-y-4 text-wp-text-100">
<ListItem
v-for="org in orgs"

View file

@ -1,6 +1,6 @@
<template>
<Settings :title="$t('admin.settings.queue.queue')" :desc="$t('admin.settings.queue.desc')">
<template #titleActions>
<Settings :title="$t('admin.settings.queue.queue')" :description="$t('admin.settings.queue.desc')">
<template #headerActions>
<div v-if="queueInfo">
<div class="flex items-center gap-2">
<Button

View file

@ -1,11 +1,10 @@
<template>
<Settings
:title="$t('registries.registries')"
:desc="$t('admin.settings.registries.desc')"
:description="$t('admin.settings.registries.desc')"
docs-url="docs/usage/registries"
:warning="$t('admin.settings.registries.warning')"
>
<template #titleActions>
<template #headerActions>
<Button
v-if="selectedRegistry"
:text="$t('registries.show')"
@ -15,6 +14,10 @@
<Button v-else :text="$t('registries.add')" start-icon="plus" @click="showAddRegistry" />
</template>
<template #headerEnd>
<Warning class="text-sm mt-4" :text="$t('admin.settings.registries.warning')" />
</template>
<RegistryList
v-if="!selectedRegistry"
v-model="registries"
@ -39,6 +42,7 @@ import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
import Warning from '~/components/atomic/Warning.vue';
import Settings from '~/components/layout/Settings.vue';
import RegistryEdit from '~/components/registry/RegistryEdit.vue';
import RegistryList from '~/components/registry/RegistryList.vue';

View file

@ -1,6 +1,6 @@
<template>
<Settings :title="$t('admin.settings.repos.repos')" :desc="$t('admin.settings.repos.desc')">
<template #titleActions>
<Settings :title="$t('admin.settings.repos.repos')" :description="$t('admin.settings.repos.desc')">
<template #headerActions>
<Button
start-icon="heal"
:is-loading="isRepairingRepos"

View file

@ -1,15 +1,18 @@
<template>
<Settings
:title="$t('secrets.secrets')"
:desc="$t('admin.settings.secrets.desc')"
:description="$t('admin.settings.secrets.desc')"
docs-url="docs/usage/secrets"
:warning="$t('admin.settings.secrets.warning')"
>
<template #titleActions>
<template #headerActions>
<Button v-if="selectedSecret" :text="$t('secrets.show')" start-icon="back" @click="selectedSecret = undefined" />
<Button v-else :text="$t('secrets.add')" start-icon="plus" @click="showAddSecret" />
</template>
<template #headerEnd>
<Warning class="text-sm mt-4" :text="$t('admin.settings.secrets.warning')" />
</template>
<SecretList
v-if="!selectedSecret"
v-model="secrets"
@ -34,6 +37,7 @@ import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue';
import Warning from '~/components/atomic/Warning.vue';
import Settings from '~/components/layout/Settings.vue';
import SecretEdit from '~/components/secrets/SecretEdit.vue';
import SecretList from '~/components/secrets/SecretList.vue';

View file

@ -1,6 +1,6 @@
<template>
<Settings :title="$t('admin.settings.users.users')" :desc="$t('admin.settings.users.desc')">
<template #titleActions>
<Settings :title="$t('admin.settings.users.users')" :description="$t('admin.settings.users.desc')">
<template #headerActions>
<Button
v-if="selectedUser"
:text="$t('admin.settings.users.show')"

View file

@ -1,6 +1,6 @@
<template>
<Settings :title="$t('admin.settings.agents.agents')" :desc="desc">
<template #titleActions>
<Settings :title="$t('admin.settings.agents.agents')" :description>
<template #headerActions>
<Button
v-if="selectedAgent"
:text="$t('admin.settings.agents.show')"
@ -46,7 +46,7 @@ import AgentForm from './AgentForm.vue';
import AgentList from './AgentList.vue';
const props = defineProps<{
desc: string;
description: string;
loadAgents: (page: number) => Promise<Agent[] | null>;
createAgent: (agent: Partial<Agent>) => Promise<Agent>;
updateAgent: (agent: Agent) => Promise<Agent | void>;

View file

@ -5,7 +5,7 @@
type="radio"
class="radio relative flex-shrink-0 border bg-wp-control-neutral-100 border-wp-control-neutral-200 cursor-pointer rounded-full w-5 h-5 checked:bg-wp-control-ok-200 checked:border-wp-control-ok-200 focus-visible:border-wp-control-neutral-300 checked:focus-visible:border-wp-control-ok-300"
:value="option.value"
:checked="innerValue.includes(option.value)"
:checked="innerValue?.includes(option.value)"
@click="innerValue = option.value"
/>
<div class="flex flex-col ml-4">

View file

@ -1,18 +1,18 @@
<template>
<Panel>
<h1 class="text-xl text-wp-text-100">{{ title }}</h1>
<div class="flex flex-col gap-4 border-b mb-4 pb-4">
<div class="flex flex-col sm:flex-row gap-4 sm:gap-12 md:justify-between dark:border-wp-background-100">
<div v-if="desc" class="flex items-center gap-x-2 text-sm text-wp-text-alt-100">
<span class="flex flex-grow-0">{{ desc }}</span>
<DocsLink v-if="docsUrl" class="flex flex-grow-0" :topic="title" :url="docsUrl" />
</div>
<div class="flex flex-col border-b mb-4 pb-4 justify-center dark:border-wp-background-100">
<h1 class="text-xl text-wp-text-100 flex items-center gap-1">
{{ title }}
<DocsLink v-if="docsUrl" :topic="title" :url="docsUrl" />
</h1>
<div>
<slot v-if="$slots.titleActions" name="titleActions" />
<div class="flex flex-wrap gap-x-4 gap-y-2 items-center justify-between">
<p v-if="description" class="text-sm text-wp-text-alt-100">{{ description }}</p>
<div v-if="$slots.headerActions">
<slot name="headerActions" />
</div>
</div>
<Warning v-if="warning" class="text-sm mt-1" :text="warning" />
<slot name="headerEnd" />
</div>
<slot />
@ -21,13 +21,11 @@
<script setup lang="ts">
import DocsLink from '~/components/atomic/DocsLink.vue';
import Warning from '~/components/atomic/Warning.vue';
import Panel from '~/components/layout/Panel.vue';
defineProps<{
title: string;
desc?: string;
description?: string;
docsUrl?: string;
warning?: string;
}>();
</script>

View file

@ -6,7 +6,7 @@
>
<div v-if="pipelineCount > 0" class="spinner" />
<div
class="z-1 flex items-center justify-center h-full w-full font-bold bg-white bg-opacity-15 dark:bg-black dark:bg-opacity-10 rounded-md"
class="z-0 flex items-center justify-center h-full w-full font-bold bg-white bg-opacity-15 dark:bg-black dark:bg-opacity-10 rounded-md"
>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
{{ pipelineCount > 9 ? '9+' : pipelineCount }}
@ -31,17 +31,17 @@ onMounted(async () => {
</script>
<style scoped>
@keyframes spinner-rotate {
@keyframes rotate {
100% {
transform: rotate(1turn);
}
}
.spinner {
@apply absolute z-0 inset-1.5 rounded-md;
@apply absolute inset-1.5 rounded-md;
overflow: hidden;
}
.spinner::before {
@apply absolute -z-2 bg-wp-primary-200 dark:bg-wp-primary-300;
@apply absolute bg-wp-primary-200 dark:bg-wp-primary-300;
content: '';
left: -50%;
top: -50%;
@ -51,11 +51,16 @@ onMounted(async () => {
background-size:
50% 50%,
50% 50%;
background-image: linear-gradient(#fff, transparent);
animation: spinner-rotate 1.5s linear infinite;
background-image: linear-gradient(#fff, #fff);
animation: rotate 1.5s linear infinite;
}
.spinner::after {
@apply absolute inset-0.5 rounded-md bg-blend-darken bg-wp-primary-200 dark:bg-wp-primary-300;
@apply absolute inset-0.5 bg-wp-primary-200 dark:bg-wp-primary-300;
/*
The nested border radius needs to be calculated correctly to look right:
https://www.30secondsofcode.org/css/s/nested-border-radius/
*/
border-radius: calc(0.375rem - 0.125rem);
content: '';
}
</style>

View file

@ -24,26 +24,26 @@
</div>
<TextField
v-if="searchBoxPresent"
class="w-auto <md:w-full <md:order-3"
class="w-auto <md:w-full flex-grow <md:order-3"
:aria-label="$t('search')"
:placeholder="$t('search')"
:model-value="search"
@update:model-value="(value: string) => $emit('update:search', value)"
/>
<div
v-if="$slots.titleActions"
v-if="$slots.headerActions"
class="flex items-center md:justify-end gap-x-2 min-w-0"
:class="{
'md:flex-1': searchBoxPresent,
}"
>
<slot name="titleActions" />
<slot name="headerActions" />
</div>
</div>
<div v-if="enableTabs" class="flex md:items-center flex-col py-2 md:flex-row md:justify-between md:py-0">
<Tabs class="<md:order-2" />
<div v-if="$slots.titleActions" class="flex content-start md:justify-end">
<div v-if="$slots.headerActions" class="flex content-start md:justify-end">
<slot name="tabActions" />
</div>
</div>
@ -52,6 +52,8 @@
</template>
<script setup lang="ts">
import { computed } from 'vue';
import IconButton from '~/components/atomic/IconButton.vue';
import TextField from '~/components/form/TextField.vue';
import Container from '~/components/layout/Container.vue';
@ -69,5 +71,5 @@ defineEmits<{
(event: 'update:search', query: string): void;
}>();
const searchBoxPresent = props.search !== undefined;
const searchBoxPresent = computed(() => props.search !== undefined);
</script>

View file

@ -7,7 +7,7 @@
@update:search="(value) => $emit('update:search', value)"
>
<template #title><slot name="title" /></template>
<template v-if="$slots.titleActions" #titleActions><slot name="titleActions" /></template>
<template v-if="$slots.headerActions" #headerActions><slot name="headerActions" /></template>
<template v-if="$slots.tabActions" #tabActions><slot name="tabActions" /></template>
</Header>

View file

@ -1,6 +1,6 @@
<template>
<AgentManager
:desc="$t('org.settings.agents.desc')"
:description="$t('org.settings.agents.desc')"
:load-agents="loadAgents"
:create-agent="createAgent"
:update-agent="updateAgent"

View file

@ -1,10 +1,10 @@
<template>
<Settings
:title="$t('registries.registries')"
:desc="$t('org.settings.registries.desc')"
:description="$t('org.settings.registries.desc')"
docs-url="docs/usage/registries"
>
<template #titleActions>
<template #headerActions>
<Button
v-if="selectedRegistry"
:text="$t('registries.show')"

View file

@ -1,6 +1,6 @@
<template>
<Settings :title="$t('secrets.secrets')" :desc="$t('org.settings.secrets.desc')" docs-url="docs/usage/secrets">
<template #titleActions>
<Settings :title="$t('secrets.secrets')" :description="$t('org.settings.secrets.desc')" docs-url="docs/usage/secrets">
<template #headerActions>
<Button v-if="selectedSecret" :text="$t('secrets.show')" start-icon="back" @click="selectedSecret = undefined" />
<Button v-else :text="$t('secrets.add')" start-icon="plus" @click="showAddSecret" />
</template>

View file

@ -1,6 +1,6 @@
<template>
<Settings :title="$t('repo.settings.badge.badge')">
<template #titleActions>
<template #headerActions>
<a v-if="badgeUrl" :href="badgeUrl" target="_blank">
<img :src="badgeUrl" />
</a>

View file

@ -1,6 +1,10 @@
<template>
<Settings :title="$t('repo.settings.crons.crons')" :desc="$t('repo.settings.crons.desc')" docs-url="docs/usage/cron">
<template #titleActions>
<Settings
:title="$t('repo.settings.crons.crons')"
:description="$t('repo.settings.crons.desc')"
docs-url="docs/usage/cron"
>
<template #headerActions>
<Button
v-if="selectedCron"
start-icon="back"

View file

@ -1,26 +1,6 @@
<template>
<Settings :title="$t('repo.settings.general.general')">
<form v-if="repoSettings" class="flex flex-col" @submit.prevent="saveRepoSettings">
<InputField
docs-url="docs/usage/project-settings#pipeline-path"
:label="$t('repo.settings.general.pipeline_path.path')"
>
<template #default="{ id }">
<TextField
:id="id"
v-model="repoSettings.config_file"
:placeholder="$t('repo.settings.general.pipeline_path.default')"
/>
</template>
<template #description>
<i18n-t keypath="repo.settings.general.pipeline_path.desc" tag="p" class="text-sm text-wp-text-alt-100">
<span class="code-box-inline">{{ $t('repo.settings.general.pipeline_path.desc_path_example') }}</span>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="code-box-inline">/</span>
</i18n-t>
</template>
</InputField>
<InputField
docs-url="docs/usage/project-settings#project-settings-1"
:label="$t('repo.settings.general.project')"
@ -35,11 +15,6 @@
:label="$t('repo.settings.general.allow_deploy.allow')"
:description="$t('repo.settings.general.allow_deploy.desc')"
/>
<Checkbox
v-model="repoSettings.gated"
:label="$t('repo.settings.general.protected.protected')"
:description="$t('repo.settings.general.protected.desc')"
/>
<Checkbox
v-model="repoSettings.netrc_only_trusted"
:label="$t('repo.settings.general.netrc_only_trusted.netrc_only_trusted')"
@ -69,6 +44,36 @@
/>
</InputField>
<InputField :label="$t('require_approval.require_approval_for')">
<RadioField
v-model="repoSettings.require_approval"
:options="[
{
value: RepoRequireApproval.None,
text: $t('require_approval.none'),
description: $t('require_approval.none_desc'),
},
{
value: RepoRequireApproval.Forks,
text: $t('require_approval.forks'),
},
{
value: RepoRequireApproval.PullRequests,
text: $t('require_approval.pull_requests'),
},
{
value: RepoRequireApproval.AllEvents,
text: $t('require_approval.all_events'),
},
]"
/>
<template #description>
<p class="text-sm">
{{ $t('require_approval.desc') }}
</p>
</template>
</InputField>
<InputField
docs-url="docs/usage/project-settings#project-visibility"
:label="$t('repo.settings.general.visibility.visibility')"
@ -87,6 +92,26 @@
</div>
</InputField>
<InputField
docs-url="docs/usage/project-settings#pipeline-path"
:label="$t('repo.settings.general.pipeline_path.path')"
>
<template #default="{ id }">
<TextField
:id="id"
v-model="repoSettings.config_file"
:placeholder="$t('repo.settings.general.pipeline_path.default')"
/>
</template>
<template #description>
<i18n-t keypath="repo.settings.general.pipeline_path.desc" tag="p" class="text-sm text-wp-text-alt-100">
<span class="code-box-inline">{{ $t('repo.settings.general.pipeline_path.desc_path_example') }}</span>
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
<span class="code-box-inline">/</span>
</i18n-t>
</template>
</InputField>
<InputField
docs-url="docs/usage/project-settings#cancel-previous-pipelines"
:label="$t('repo.settings.general.cancel_prev.cancel')"
@ -130,7 +155,7 @@ import useApiClient from '~/compositions/useApiClient';
import { useAsyncAction } from '~/compositions/useAsyncAction';
import useAuthentication from '~/compositions/useAuthentication';
import useNotifications from '~/compositions/useNotifications';
import { RepoVisibility, WebhookEvents, type Repo, type RepoSettings } from '~/lib/api/types';
import { RepoRequireApproval, RepoVisibility, WebhookEvents, type Repo, type RepoSettings } from '~/lib/api/types';
import { useRepoStore } from '~/store/repos';
const apiClient = useApiClient();
@ -151,7 +176,7 @@ function loadRepoSettings() {
config_file: repo.value.config_file,
timeout: repo.value.timeout,
visibility: repo.value.visibility,
gated: repo.value.gated,
require_approval: repo.value.require_approval,
trusted: repo.value.trusted,
allow_pr: repo.value.allow_pr,
allow_deploy: repo.value.allow_deploy,

View file

@ -1,6 +1,6 @@
<template>
<Settings :title="$t('registries.credentials')" :desc="$t('registries.desc')" docs-url="docs/usage/registries">
<template #titleActions>
<Settings :title="$t('registries.credentials')" :description="$t('registries.desc')" docs-url="docs/usage/registries">
<template #headerActions>
<Button
v-if="selectedRegistry"
:text="$t('registries.show')"

View file

@ -1,6 +1,6 @@
<template>
<Settings :title="$t('secrets.secrets')" :desc="$t('secrets.desc')" docs-url="docs/usage/secrets">
<template #titleActions>
<Settings :title="$t('secrets.secrets')" :description="$t('secrets.desc')" docs-url="docs/usage/secrets">
<template #headerActions>
<Button v-if="selectedSecret" :text="$t('secrets.show')" start-icon="back" @click="selectedSecret = undefined" />
<Button v-else :text="$t('secrets.add')" start-icon="plus" @click="showAddSecret" />
</template>

View file

@ -1,6 +1,6 @@
<template>
<AgentManager
:desc="$t('user.settings.agents.desc')"
:description="$t('user.settings.agents.desc')"
:load-agents="loadAgents"
:create-agent="createAgent"
:update-agent="updateAgent"

View file

@ -1,7 +1,7 @@
<template>
<Settings :title="$t('user.settings.cli_and_api.cli_and_api')" :desc="$t('user.settings.cli_and_api.desc')">
<Settings :title="$t('user.settings.cli_and_api.cli_and_api')" :description="$t('user.settings.cli_and_api.desc')">
<InputField :label="$t('user.settings.cli_and_api.cli_usage')">
<template #titleActions>
<template #headerActions>
<a :href="cliDownload" target="_blank" class="ml-4 text-wp-link-100 hover:text-wp-link-200">{{
$t('user.settings.cli_and_api.download_cli')
}}</a>
@ -10,14 +10,14 @@
</InputField>
<InputField :label="$t('user.settings.cli_and_api.token')">
<template #titleActions>
<template #headerActions>
<Button class="ml-auto" :text="$t('user.settings.cli_and_api.reset_token')" @click="resetToken" />
</template>
<pre class="code-box">{{ token }}</pre>
</InputField>
<InputField :label="$t('user.settings.cli_and_api.api_usage')">
<template #titleActions>
<template #headerActions>
<a
v-if="enableSwagger"
:href="`${address}/swagger/index.html`"

View file

@ -1,10 +1,10 @@
<template>
<Settings
:title="$t('registries.registries')"
:desc="$t('user.settings.registries.desc')"
:description="$t('user.settings.registries.desc')"
docs-url="docs/usage/registries"
>
<template #titleActions>
<template #headerActions>
<Button
v-if="selectedRegistry"
:text="$t('registries.show')"

View file

@ -1,6 +1,10 @@
<template>
<Settings :title="$t('secrets.secrets')" :desc="$t('user.settings.secrets.desc')" docs-url="docs/usage/secrets">
<template #titleActions>
<Settings
:title="$t('secrets.secrets')"
:description="$t('user.settings.secrets.desc')"
docs-url="docs/usage/secrets"
>
<template #headerActions>
<Button v-if="selectedSecret" :text="$t('secrets.show')" start-icon="back" @click="selectedSecret = undefined" />
<Button v-else :text="$t('secrets.add')" start-icon="plus" @click="showAddSecret" />
</template>

View file

@ -67,7 +67,7 @@ export interface Repo {
last_pipeline: number;
gated: boolean;
require_approval: RepoRequireApproval;
// Events that will cancel running pipelines before starting a new one
cancel_previous_pipeline_events: string[];
@ -81,6 +81,13 @@ export enum RepoVisibility {
Private = 'private',
Internal = 'internal',
}
export enum RepoRequireApproval {
None = 'none',
Forks = 'forks',
PullRequests = 'pull_requests',
AllEvents = 'all_events',
}
/* eslint-enable */
export type RepoSettings = Pick<
@ -89,7 +96,7 @@ export type RepoSettings = Pick<
| 'timeout'
| 'visibility'
| 'trusted'
| 'gated'
| 'require_approval'
| 'allow_pr'
| 'allow_deploy'
| 'cancel_previous_pipeline_events'

View file

@ -4,7 +4,7 @@
{{ $t('repositories') }}
</template>
<template #titleActions>
<template #headerActions>
<Button :to="{ name: 'repo-add' }" start-icon="plus" :text="$t('repo.add')" />
</template>

View file

@ -1,7 +1,7 @@
<template>
<Scaffold enable-tabs>
<template #title>{{ $t('user.settings.settings') }}</template>
<template #titleActions><Button :text="$t('logout')" :to="`${address}/logout`" /></template>
<template #headerActions><Button :text="$t('logout')" :to="`${address}/logout`" /></template>
<Tab id="general" :title="$t('user.settings.general.general')">
<UserGeneralTab />
</Tab>

View file

@ -4,7 +4,7 @@
{{ org.name }}
</template>
<template #titleActions>
<template #headerActions>
<IconButton
v-if="orgPermissions.admin"
icon="settings"

View file

@ -4,7 +4,7 @@
{{ org.name }}
</template>
<template #titleActions>
<template #headerActions>
<IconButton
v-if="orgPermissions.admin"
:to="{ name: org.is_user ? 'user' : 'repo-settings' }"

View file

@ -4,7 +4,6 @@
v-model:active-tab="activeTab"
enable-tabs
disable-tab-url-hash-mode
:go-back="goBack"
>
<template #title>
<span class="flex">
@ -16,7 +15,7 @@
{{ repo.name }}
</span>
</template>
<template #titleActions>
<template #headerActions>
<a v-if="badgeUrl" :href="badgeUrl" target="_blank">
<img :src="badgeUrl" />
</a>
@ -68,7 +67,6 @@ import useAuthentication from '~/compositions/useAuthentication';
import useConfig from '~/compositions/useConfig';
import { useForgeStore } from '~/compositions/useForgeStore';
import useNotifications from '~/compositions/useNotifications';
import { useRouteBack } from '~/compositions/useRouteBack';
import type { Forge, RepoPermissions } from '~/lib/api/types';
import { usePipelineStore } from '~/store/pipelines';
import { useRepoStore } from '~/store/repos';
@ -155,6 +153,4 @@ const activeTab = computed({
}
},
});
const goBack = useRouteBack({ name: 'repos' });
</script>

View file

@ -19,7 +19,7 @@
</span>
</template>
<template #titleActions>
<template #headerActions>
<div class="flex md:items-center flex-col gap-2 md:flex-row md:justify-between min-w-0">
<div class="flex content-start gap-2 min-w-0">
<PipelineStatusIcon :status="pipeline.status" class="flex flex-shrink-0" />

View file

@ -14,6 +14,27 @@
package woodpecker
type ApprovalMode string
var (
RequireApprovalNone ApprovalMode = "none" // require approval for no events
RequireApprovalForks ApprovalMode = "forks" // require approval for PRs from forks
RequireApprovalPullRequests ApprovalMode = "pull_requests" // require approval for all PRs (default)
RequireApprovalAllEvents ApprovalMode = "all_events" // require approval for all events
)
func (mode ApprovalMode) Valid() bool {
switch mode {
case RequireApprovalNone,
RequireApprovalForks,
RequireApprovalPullRequests,
RequireApprovalAllEvents:
return true
default:
return false
}
}
type (
// User represents a user account.
User struct {
@ -27,37 +48,39 @@ type (
// Repo represents a repository.
Repo struct {
ID int64 `json:"id,omitempty"`
ForgeRemoteID string `json:"forge_remote_id"`
Owner string `json:"owner"`
Name string `json:"name"`
FullName string `json:"full_name"`
Avatar string `json:"avatar_url,omitempty"`
ForgeURL string `json:"forge_url,omitempty"`
Clone string `json:"clone_url,omitempty"`
DefaultBranch string `json:"default_branch,omitempty"`
SCMKind string `json:"scm,omitempty"`
Timeout int64 `json:"timeout,omitempty"`
Visibility string `json:"visibility"`
IsSCMPrivate bool `json:"private"`
IsTrusted bool `json:"trusted"`
IsGated bool `json:"gated"`
IsActive bool `json:"active"`
AllowPullRequests bool `json:"allow_pr"`
Config string `json:"config_file"`
CancelPreviousPipelineEvents []string `json:"cancel_previous_pipeline_events"`
NetrcOnlyTrusted bool `json:"netrc_only_trusted"`
ID int64 `json:"id,omitempty"`
ForgeRemoteID string `json:"forge_remote_id"`
Owner string `json:"owner"`
Name string `json:"name"`
FullName string `json:"full_name"`
Avatar string `json:"avatar_url,omitempty"`
ForgeURL string `json:"forge_url,omitempty"`
Clone string `json:"clone_url,omitempty"`
DefaultBranch string `json:"default_branch,omitempty"`
SCMKind string `json:"scm,omitempty"`
Timeout int64 `json:"timeout,omitempty"`
Visibility string `json:"visibility"`
IsSCMPrivate bool `json:"private"`
IsTrusted bool `json:"trusted"`
IsGated bool `json:"gated,omitempty"` // TODO: remove in next major release
RequireApproval ApprovalMode `json:"require_approval"`
IsActive bool `json:"active"`
AllowPullRequests bool `json:"allow_pr"`
Config string `json:"config_file"`
CancelPreviousPipelineEvents []string `json:"cancel_previous_pipeline_events"`
NetrcOnlyTrusted bool `json:"netrc_only_trusted"`
}
// RepoPatch defines a repository patch request.
RepoPatch struct {
Config *string `json:"config_file,omitempty"`
IsTrusted *bool `json:"trusted,omitempty"`
IsGated *bool `json:"gated,omitempty"`
Timeout *int64 `json:"timeout,omitempty"`
Visibility *string `json:"visibility"`
AllowPull *bool `json:"allow_pr,omitempty"`
PipelineCounter *int `json:"pipeline_counter,omitempty"`
Config *string `json:"config_file,omitempty"`
IsTrusted *bool `json:"trusted,omitempty"`
IsGated *bool `json:"gated,omitempty"` // TODO: remove in next major release
RequireApproval *ApprovalMode `json:"require_approval,omitempty"`
Timeout *int64 `json:"timeout,omitempty"`
Visibility *string `json:"visibility"`
AllowPull *bool `json:"allow_pr,omitempty"`
PipelineCounter *int `json:"pipeline_counter,omitempty"`
}
PipelineError struct {