From 66e221b4b1e47fa3fd407fd45a40f3774ac6ee0b Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Wed, 15 Apr 2026 09:50:03 +0200 Subject: [PATCH] Move skip pipeline by commit message into pipeline/frontend package (#6437) and add some custom types for events and failure in metadata package. no logic change at all. --- cli/exec/metadata.go | 4 +-- cmd/server/openapi/docs.go | 27 ++++++++++++++- pipeline/frontend/metadata/const.go | 32 +++++++++--------- pipeline/frontend/metadata/environment.go | 8 ++--- pipeline/frontend/metadata/types.go | 2 +- pipeline/frontend/yaml/compiler/compiler.go | 8 ++--- .../frontend/yaml/compiler/compiler_test.go | 14 ++++---- pipeline/frontend/yaml/compiler/convert.go | 2 +- .../frontend/yaml/constraint/constraint.go | 4 +-- pipeline/frontend/yaml/constraint/skip.go | 33 +++++++++++++++++++ pipeline/runtime/runtime_test.go | 2 +- pipeline/runtime/step.go | 2 +- pipeline/runtime/step_test.go | 2 +- server/model/pipeline.go | 3 +- server/pipeline/create.go | 20 +++++------ server/pipeline/items.go | 4 +-- server/pipeline/step_builder/metadata.go | 2 +- 17 files changed, 113 insertions(+), 56 deletions(-) create mode 100644 pipeline/frontend/yaml/constraint/skip.go diff --git a/cli/exec/metadata.go b/cli/exec/metadata.go index ac2152379..a529c3617 100644 --- a/cli/exec/metadata.go +++ b/cli/exec/metadata.go @@ -97,7 +97,7 @@ func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis, w metadataFileAndOverrideOrDefault(c, "pipeline-started", func(i int64) { m.Curr.Started = i }, c.Int64) metadataFileAndOverrideOrDefault(c, "pipeline-finished", func(i int64) { m.Curr.Finished = i }, c.Int64) metadataFileAndOverrideOrDefault(c, "pipeline-status", func(s string) { m.Curr.Status = s }, c.String) - metadataFileAndOverrideOrDefault(c, "pipeline-event", func(s string) { m.Curr.Event = s }, c.String) + metadataFileAndOverrideOrDefault(c, "pipeline-event", func(s string) { m.Curr.Event = metadata.Event(s) }, c.String) metadataFileAndOverrideOrDefault(c, "pipeline-url", func(s string) { m.Curr.ForgeURL = s }, c.String) metadataFileAndOverrideOrDefault(c, "pipeline-deploy-to", func(s string) { m.Curr.DeployTo = s }, c.String) metadataFileAndOverrideOrDefault(c, "pipeline-deploy-task", func(s string) { m.Curr.DeployTask = s }, c.String) @@ -123,7 +123,7 @@ func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis, w metadataFileAndOverrideOrDefault(c, "prev-pipeline-started", func(i int64) { m.Prev.Started = i }, c.Int64) metadataFileAndOverrideOrDefault(c, "prev-pipeline-finished", func(i int64) { m.Prev.Finished = i }, c.Int64) metadataFileAndOverrideOrDefault(c, "prev-pipeline-status", func(s string) { m.Prev.Status = s }, c.String) - metadataFileAndOverrideOrDefault(c, "prev-pipeline-event", func(s string) { m.Prev.Event = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-pipeline-event", func(s string) { m.Prev.Event = metadata.Event(s) }, c.String) metadataFileAndOverrideOrDefault(c, "prev-pipeline-url", func(s string) { m.Prev.ForgeURL = s }, c.String) // Previous Pipeline Commit diff --git a/cmd/server/openapi/docs.go b/cmd/server/openapi/docs.go index c69723471..977ae65b5 100644 --- a/cmd/server/openapi/docs.go +++ b/cmd/server/openapi/docs.go @@ -5891,6 +5891,31 @@ const docTemplate = `{ } } }, + "metadata.Event": { + "type": "string", + "enum": [ + "push", + "pull_request", + "pull_request_closed", + "pull_request_metadata", + "tag", + "release", + "deployment", + "cron", + "manual" + ], + "x-enum-varnames": [ + "EventPush", + "EventPull", + "EventPullClosed", + "EventPullMetadata", + "EventTag", + "EventRelease", + "EventDeploy", + "EventCron", + "EventManual" + ] + }, "metadata.Forge": { "type": "object", "properties": { @@ -5950,7 +5975,7 @@ const docTemplate = `{ "type": "string" }, "event": { - "type": "string" + "$ref": "#/definitions/metadata.Event" }, "event_reason": { "type": "array", diff --git a/pipeline/frontend/metadata/const.go b/pipeline/frontend/metadata/const.go index 69b971d93..7ca126127 100644 --- a/pipeline/frontend/metadata/const.go +++ b/pipeline/frontend/metadata/const.go @@ -14,20 +14,22 @@ package metadata +type Event string + // Event types corresponding to forge hooks. const ( - EventPush = "push" - EventPull = "pull_request" - EventPullClosed = "pull_request_closed" - EventPullMetadata = "pull_request_metadata" - EventTag = "tag" - EventRelease = "release" - EventDeploy = "deployment" - EventCron = "cron" - EventManual = "manual" + EventPush Event = "push" + EventPull Event = "pull_request" + EventPullClosed Event = "pull_request_closed" + EventPullMetadata Event = "pull_request_metadata" + EventTag Event = "tag" + EventRelease Event = "release" + EventDeploy Event = "deployment" + EventCron Event = "cron" + EventManual Event = "manual" ) -func EventIsPull(event string) bool { +func (event Event) IsPull() bool { switch event { case EventPull, EventPullClosed, @@ -37,11 +39,11 @@ func EventIsPull(event string) bool { return false } +type Failure string + // Different ways to handle failure states. const ( - FailureIgnore = "ignore" - FailureFail = "fail" - //nolint:godot - // TODO: Not implemented yet. - // FailureCancel = "cancel" + FailureIgnore Failure = "ignore" + FailureFail Failure = "fail" + FailureCancel Failure = "cancel" ) diff --git a/pipeline/frontend/metadata/environment.go b/pipeline/frontend/metadata/environment.go index 912e2c09f..daf0078c2 100644 --- a/pipeline/frontend/metadata/environment.go +++ b/pipeline/frontend/metadata/environment.go @@ -67,7 +67,7 @@ func (m *Metadata) Environ() map[string]string { pipeline := m.Curr setNonEmptyEnvVar(params, "CI_PIPELINE_NUMBER", strconv.FormatInt(pipeline.Number, 10)) setNonEmptyEnvVar(params, "CI_PIPELINE_PARENT", strconv.FormatInt(pipeline.Parent, 10)) - setNonEmptyEnvVar(params, "CI_PIPELINE_EVENT", pipeline.Event) + setNonEmptyEnvVar(params, "CI_PIPELINE_EVENT", string(pipeline.Event)) setNonEmptyEnvVar(params, "CI_PIPELINE_EVENT_REASON", strings.Join(pipeline.EventReason, ",")) setNonEmptyEnvVar(params, "CI_PIPELINE_URL", m.getPipelineWebURL(pipeline, 0)) setNonEmptyEnvVar(params, "CI_PIPELINE_FORGE_URL", pipeline.ForgeURL) @@ -102,7 +102,7 @@ func (m *Metadata) Environ() map[string]string { if pipeline.Event == EventRelease { setNonEmptyEnvVar(params, "CI_COMMIT_PRERELEASE", strconv.FormatBool(pipeline.Commit.IsPrerelease)) } - if EventIsPull(pipeline.Event) { + if pipeline.Event.IsPull() { sourceBranch, targetBranch := getSourceTargetBranches(commit.Refspec) setNonEmptyEnvVar(params, "CI_COMMIT_SOURCE_BRANCH", sourceBranch) setNonEmptyEnvVar(params, "CI_COMMIT_TARGET_BRANCH", targetBranch) @@ -127,7 +127,7 @@ func (m *Metadata) Environ() map[string]string { prevPipeline := m.Prev setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_NUMBER", strconv.FormatInt(prevPipeline.Number, 10)) setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_PARENT", strconv.FormatInt(prevPipeline.Parent, 10)) - setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_EVENT", prevPipeline.Event) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_EVENT", string(prevPipeline.Event)) setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_EVENT_REASON", strings.Join(prevPipeline.EventReason, ",")) setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_URL", m.getPipelineWebURL(prevPipeline, 0)) setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_FORGE_URL", prevPipeline.ForgeURL) @@ -149,7 +149,7 @@ func (m *Metadata) Environ() map[string]string { setNonEmptyEnvVar(params, "CI_PREV_COMMIT_BRANCH", prevCommit.Branch) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR", prevCommit.Author.Name) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR_EMAIL", prevCommit.Author.Email) - if EventIsPull(prevPipeline.Event) { + if prevPipeline.Event.IsPull() { prevSourceBranch, prevTargetBranch := getSourceTargetBranches(prevCommit.Refspec) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_SOURCE_BRANCH", prevSourceBranch) setNonEmptyEnvVar(params, "CI_PREV_COMMIT_TARGET_BRANCH", prevTargetBranch) diff --git a/pipeline/frontend/metadata/types.go b/pipeline/frontend/metadata/types.go index 542395b6c..f39602dde 100644 --- a/pipeline/frontend/metadata/types.go +++ b/pipeline/frontend/metadata/types.go @@ -48,7 +48,7 @@ type ( Started int64 `json:"started,omitempty"` Finished int64 `json:"finished,omitempty"` Status string `json:"status,omitempty"` - Event string `json:"event,omitempty"` + Event Event `json:"event,omitempty"` EventReason []string `json:"event_reason,omitempty"` ForgeURL string `json:"forge_url,omitempty"` DeployTo string `json:"target,omitempty"` diff --git a/pipeline/frontend/yaml/compiler/compiler.go b/pipeline/frontend/yaml/compiler/compiler.go index 612ac5409..3251b4f3a 100644 --- a/pipeline/frontend/yaml/compiler/compiler.go +++ b/pipeline/frontend/yaml/compiler/compiler.go @@ -42,10 +42,10 @@ type Secret struct { Name string Value string AllowedPlugins []string - Events []string + Events []metadata.Event } -func (s *Secret) Available(event string, container *yaml_types.Container) error { +func (s *Secret) Available(event metadata.Event, container *yaml_types.Container) error { onlyAllowSecretForPlugins := len(s.AllowedPlugins) > 0 if onlyAllowSecretForPlugins && !container.IsPlugin() { return fmt.Errorf("secret %q is only allowed to be used by plugins (a filter has been set on the secret). Note: Image filters do not work for normal steps", s.Name) @@ -64,13 +64,13 @@ func (s *Secret) Available(event string, container *yaml_types.Container) error // Match returns true if an image and event match the restricted list. // Note that EventPullClosed are treated as EventPull. -func (s *Secret) Match(event string) bool { +func (s *Secret) Match(event metadata.Event) bool { // if there is no filter set secret matches all webhook events if len(s.Events) == 0 { return true } // treat all pull events the same way - if metadata.EventIsPull(event) { + if event.IsPull() { event = metadata.EventPull } // one match is enough diff --git a/pipeline/frontend/yaml/compiler/compiler_test.go b/pipeline/frontend/yaml/compiler/compiler_test.go index 80023df66..4d732fa50 100644 --- a/pipeline/frontend/yaml/compiler/compiler_test.go +++ b/pipeline/frontend/yaml/compiler/compiler_test.go @@ -29,7 +29,7 @@ import ( func TestSecretAvailable(t *testing.T) { secret := Secret{ AllowedPlugins: []string{}, - Events: []string{"push"}, + Events: []metadata.Event{"push"}, } assert.NoError(t, secret.Available("push", &yaml_types.Container{ Image: "golang", @@ -40,7 +40,7 @@ func TestSecretAvailable(t *testing.T) { secret = Secret{ Name: "foo", AllowedPlugins: []string{"golang"}, - Events: []string{"push"}, + Events: []metadata.Event{"push"}, } assert.NoError(t, secret.Available("push", &yaml_types.Container{ Name: "step", @@ -440,18 +440,18 @@ func TestSecretMatch(t *testing.T) { tcl := []*struct { name string secret Secret - event string + event metadata.Event match bool }{ { name: "should match event", - secret: Secret{Events: []string{"pull_request"}}, + secret: Secret{Events: []metadata.Event{"pull_request"}}, event: "pull_request", match: true, }, { name: "should not match event", - secret: Secret{Events: []string{"pull_request"}}, + secret: Secret{Events: []metadata.Event{"pull_request"}}, event: "push", match: false, }, @@ -463,13 +463,13 @@ func TestSecretMatch(t *testing.T) { }, { name: "pull close should match pull", - secret: Secret{Events: []string{"pull_request"}}, + secret: Secret{Events: []metadata.Event{"pull_request"}}, event: "pull_request_closed", match: true, }, { name: "pull metadata change should match pull", - secret: Secret{Events: []string{"pull_request"}}, + secret: Secret{Events: []metadata.Event{"pull_request"}}, event: "pull_request_metadata", match: true, }, diff --git a/pipeline/frontend/yaml/compiler/convert.go b/pipeline/frontend/yaml/compiler/convert.go index 3a0331860..3464703c2 100644 --- a/pipeline/frontend/yaml/compiler/convert.go +++ b/pipeline/frontend/yaml/compiler/convert.go @@ -153,7 +153,7 @@ func (c *Compiler) createProcess(container *yaml_types.Container, workflow *yaml failure := container.Failure if container.Failure == "" { - failure = metadata.FailureFail + failure = string(metadata.FailureFail) } return &backend_types.Step{ diff --git a/pipeline/frontend/yaml/constraint/constraint.go b/pipeline/frontend/yaml/constraint/constraint.go index 69e70c14e..0dfed2713 100644 --- a/pipeline/frontend/yaml/constraint/constraint.go +++ b/pipeline/frontend/yaml/constraint/constraint.go @@ -172,13 +172,13 @@ func (c *Constraint) Match(m metadata.Metadata, global bool, env map[string]stri } match = match && c.Platform.Match(m.Sys.Platform) && - (len(c.Event) == 0 || slices.Contains(c.Event, m.Curr.Event)) && + (len(c.Event) == 0 || slices.Contains(c.Event, string(m.Curr.Event))) && c.Repo.Match(path.Join(m.Repo.Owner, m.Repo.Name)) && c.Ref.Match(m.Curr.Commit.Ref) && c.Instance.Match(m.Sys.Host) // changed files filter apply only for pull-request and push events - if metadata.EventIsPull(m.Curr.Event) || m.Curr.Event == metadata.EventPush { + if m.Curr.Event.IsPull() || m.Curr.Event == metadata.EventPush { match = match && c.Path.Match(m.Curr.Commit.ChangedFiles, m.Curr.Commit.Message) } diff --git a/pipeline/frontend/yaml/constraint/skip.go b/pipeline/frontend/yaml/constraint/skip.go new file mode 100644 index 000000000..a7150b166 --- /dev/null +++ b/pipeline/frontend/yaml/constraint/skip.go @@ -0,0 +1,33 @@ +// Copyright 2026 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 constraint + +import ( + "regexp" + + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" +) + +var skipPipelineRegex = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`) + +func IsSkipCommitMessage(event metadata.Event, commitMessage string) bool { + if event == metadata.EventPush || event.IsPull() { + skipMatch := skipPipelineRegex.FindString(commitMessage) + if len(skipMatch) > 0 { + return true + } + } + return false +} diff --git a/pipeline/runtime/runtime_test.go b/pipeline/runtime/runtime_test.go index 0e9549541..e497564a2 100644 --- a/pipeline/runtime/runtime_test.go +++ b/pipeline/runtime/runtime_test.go @@ -62,7 +62,7 @@ func withExitCode(code int) func(*backend_types.Step) { } func withIgnoreFailure() func(*backend_types.Step) { - return func(s *backend_types.Step) { s.Failure = metadata.FailureIgnore } + return func(s *backend_types.Step) { s.Failure = string(metadata.FailureIgnore) } } func withOnFailure() func(*backend_types.Step) { diff --git a/pipeline/runtime/step.go b/pipeline/runtime/step.go index 6df81648c..abef6ec6c 100644 --- a/pipeline/runtime/step.go +++ b/pipeline/runtime/step.go @@ -186,7 +186,7 @@ func (r *Runtime) runBlockingStep(runnerCtx context.Context, step *backend_types } err = r.traceStep(processState, err, step) - if err != nil && step.Failure == metadata.FailureIgnore { + if err != nil && metadata.Failure(step.Failure) == metadata.FailureIgnore { return nil } return err diff --git a/pipeline/runtime/step_test.go b/pipeline/runtime/step_test.go index 45e4444ed..7f3dd1b0b 100644 --- a/pipeline/runtime/step_test.go +++ b/pipeline/runtime/step_test.go @@ -553,7 +553,7 @@ func TestRunBlockingStep(t *testing.T) { t.Parallel() r := newDummyRuntime(t, newTestTracer(t)) step := dummyStep("s1") - step.Failure = metadata.FailureIgnore + step.Failure = string(metadata.FailureIgnore) step.Environment[dummy.EnvKeyStepExitCode] = "1" err := r.runBlockingStep(t.Context(), step) diff --git a/server/model/pipeline.go b/server/model/pipeline.go index 4c9c97e75..ebe1ce54b 100644 --- a/server/model/pipeline.go +++ b/server/model/pipeline.go @@ -17,6 +17,7 @@ package model import ( "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" ) type Pipeline struct { @@ -100,7 +101,7 @@ func (p Pipeline) IsMultiPipeline() bool { // IsPullRequest checks if it's a PR event. func (p Pipeline) IsPullRequest() bool { - return p.Event == EventPull || p.Event == EventPullClosed || p.Event == EventPullMetadata + return metadata.Event(p.Event).IsPull() } type PipelineOptions struct { diff --git a/server/pipeline/create.go b/server/pipeline/create.go index a55b83c45..09093551e 100644 --- a/server/pipeline/create.go +++ b/server/pipeline/create.go @@ -18,11 +18,12 @@ import ( "context" "errors" "fmt" - "regexp" "github.com/rs/zerolog/log" pipeline_errors "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/constraint" "go.woodpecker-ci.org/woodpecker/v3/server" "go.woodpecker-ci.org/woodpecker/v3/server/forge" forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" @@ -31,8 +32,6 @@ import ( "go.woodpecker-ci.org/woodpecker/v3/version" ) -var skipPipelineRegex = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`) - // Create a new pipeline and start it. func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline *model.Pipeline) (*model.Pipeline, error) { repoUser, err := _store.GetUser(repo.UserID) @@ -42,16 +41,13 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline return nil, errors.New(msg) } - if pipeline.Event == model.EventPush || pipeline.IsPullRequest() { - skipMatch := skipPipelineRegex.FindString(pipeline.Message) - if len(skipMatch) > 0 { - ref := pipeline.Commit - if len(ref) == 0 { - ref = pipeline.Ref - } - log.Debug().Str("repo", repo.FullName).Msgf("ignoring pipeline as skip-ci was found in the commit (%s) message '%s'", ref, pipeline.Message) - return nil, ErrFiltered + if constraint.IsSkipCommitMessage(metadata.Event(pipeline.Event), pipeline.Message) { + ref := pipeline.Commit + if len(ref) == 0 { + ref = pipeline.Ref } + log.Debug().Str("repo", repo.FullName).Msgf("ignoring pipeline as skip-ci was found in the commit (%s) message '%s'", ref, pipeline.Message) + return nil, ErrFiltered } _forge, err := server.Config.Services.Manager.ForgeFromRepo(repo) diff --git a/server/pipeline/items.go b/server/pipeline/items.go index bb33c6723..89d622473 100644 --- a/server/pipeline/items.go +++ b/server/pipeline/items.go @@ -53,9 +53,9 @@ func parsePipeline(ctx context.Context, forge forge.Forge, store store.Store, cu var secrets []compiler.Secret for _, sec := range secs { - var events []string + var events []pipeline_metadata.Event for _, event := range sec.Events { - events = append(events, string(event)) + events = append(events, pipeline_metadata.Event(event)) } secrets = append(secrets, compiler.Secret{ diff --git a/server/pipeline/step_builder/metadata.go b/server/pipeline/step_builder/metadata.go index f99928c21..42067e473 100644 --- a/server/pipeline/step_builder/metadata.go +++ b/server/pipeline/step_builder/metadata.go @@ -112,7 +112,7 @@ func metadataPipelineFromModelPipeline(pipeline *model.Pipeline, includeParent b Started: pipeline.Started, Finished: pipeline.Finished, Status: string(pipeline.Status), - Event: string(pipeline.Event), + Event: metadata.Event(pipeline.Event), EventReason: pipeline.EventReason, ForgeURL: pipeline.ForgeURL, DeployTo: pipeline.DeployTo,