Remove old pipeline options (#4016)

This commit is contained in:
qwerty287 2024-08-15 18:58:51 +02:00 committed by GitHub
parent 8251ec198b
commit aafd217cce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 100 additions and 574 deletions

View file

@ -6,15 +6,15 @@ Some versions need some changes to the server configuration or the pipeline conf
- Removed `WOODPECKER_DEV_OAUTH_HOST` and `WOODPECKER_DEV_GITEA_OAUTH_URL` use `WOODPECKER_EXPERT_FORGE_OAUTH_HOST` - Removed `WOODPECKER_DEV_OAUTH_HOST` and `WOODPECKER_DEV_GITEA_OAUTH_URL` use `WOODPECKER_EXPERT_FORGE_OAUTH_HOST`
- Compatibility mode of deprecated `pipeline:`, `platform:` and `branches:` pipeline config options are now removed and pipeline will now fail if still in use. - Compatibility mode of deprecated `pipeline:`, `platform:` and `branches:` pipeline config options are now removed and pipeline will now fail if still in use.
- Deprecated `steps.[name].group` in favor of `steps.[name].depends_on` (see [workflow syntax](./20-usage/20-workflow-syntax.md#depends_on) to learn how to set dependencies) - Removed `steps.[name].group` in favor of `steps.[name].depends_on` (see [workflow syntax](./20-usage/20-workflow-syntax.md#depends_on) to learn how to set dependencies)
- Removed `WOODPECKER_ROOT_PATH` and `WOODPECKER_ROOT_URL` config variables. Use `WOODPECKER_HOST` with a path instead - Removed `WOODPECKER_ROOT_PATH` and `WOODPECKER_ROOT_URL` config variables. Use `WOODPECKER_HOST` with a path instead
- Pipelines without a config file will now be skipped instead of failing - Pipelines without a config file will now be skipped instead of failing
- Removed implicitly defined `regcred` image pull secret name. Set it explicitly via `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES` - Removed implicitly defined `regcred` image pull secret name. Set it explicitly via `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES`
- Deprecated `includes` and `excludes` support from **event** filter - Removed `includes` and `excludes` support from **event** filter
- Deprecated uppercasing all secret env vars, instead, the value of the `secrets` property is used. [Read more](./20-usage/40-secrets.md#use-secrets-in-commands) - Removed uppercasing all secret env vars, instead, the value of the `secrets` property is used. [Read more](./20-usage/40-secrets.md#use-secrets-in-commands)
- Deprecated alternative names for secrets, use `environment` with `from_secret` - Removed alternative names for secrets, use `environment` with `from_secret`
- Deprecated slice definition for env vars - Removed slice definition for env vars
- Deprecated `environment` filter, use `when.evaluate` - Removed `environment` filter, use `when.evaluate`
- Removed `WOODPECKER_WEBHOOK_HOST` in favor of `WOODPECKER_EXPERT_WEBHOOK_HOST` - Removed `WOODPECKER_WEBHOOK_HOST` in favor of `WOODPECKER_EXPERT_WEBHOOK_HOST`
- Migrated to rfc9421 for webhook signatures - Migrated to rfc9421 for webhook signatures
- Renamed `start_time`, `end_time`, `created_at`, `started_at`, `finished_at` and `reviewed_at` JSON fields to `started`, `finished`, `created`, `started`, `finished`, `reviewed` - Renamed `start_time`, `end_time`, `created_at`, `started_at`, `finished_at` and `reviewed_at` JSON fields to `started`, `finished`, `created`, `started`, `finished`, `reviewed`

View file

@ -275,7 +275,6 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er
step: step, step: step,
position: pos, position: pos,
name: container.Name, name: container.Name,
group: container.Group,
dependsOn: container.DependsOn, dependsOn: container.DependsOn,
}) })
} }

View file

@ -150,63 +150,65 @@ func TestCompilerCompile(t *testing.T) {
}, },
}, },
{ {
name: "workflow with three steps and one group", name: "workflow with three steps",
fronConf: &yaml_types.Workflow{Steps: yaml_types.ContainerList{ContainerList: []*yaml_types.Container{{ fronConf: &yaml_types.Workflow{Steps: yaml_types.ContainerList{ContainerList: []*yaml_types.Container{{
Name: "echo env", Name: "echo env",
Image: "bash", Image: "bash",
Commands: []string{"env"}, Commands: []string{"env"},
}, { }, {
Name: "parallel echo 1", Name: "parallel echo 1",
Group: "parallel",
Image: "bash", Image: "bash",
Commands: []string{"echo 1"}, Commands: []string{"echo 1"},
}, { }, {
Name: "parallel echo 2", Name: "parallel echo 2",
Group: "parallel",
Image: "bash", Image: "bash",
Commands: []string{"echo 2"}, Commands: []string{"echo 2"},
}}}}, }}}},
backConf: &backend_types.Config{ backConf: &backend_types.Config{
Networks: defaultNetworks, Networks: defaultNetworks,
Volumes: defaultVolumes, Volumes: defaultVolumes,
Stages: []*backend_types.Stage{defaultCloneStage, { Stages: []*backend_types.Stage{
Steps: []*backend_types.Step{{ defaultCloneStage, {
Name: "echo env", Steps: []*backend_types.Step{{
Type: backend_types.StepTypeCommands, Name: "echo env",
Image: "bash", Type: backend_types.StepTypeCommands,
Commands: []string{"env"}, Image: "bash",
OnSuccess: true, Commands: []string{"env"},
Failure: "fail", OnSuccess: true,
Volumes: []string{defaultVolumes[0].Name + ":/test"}, Failure: "fail",
WorkingDir: "/test/src/github.com/octocat/hello-world", Volumes: []string{defaultVolumes[0].Name + ":/test"},
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}}, WorkingDir: "/test/src/github.com/octocat/hello-world",
ExtraHosts: []backend_types.HostAlias{}, Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}},
}}, ExtraHosts: []backend_types.HostAlias{},
}, { }},
Steps: []*backend_types.Step{{
Name: "parallel echo 1",
Type: backend_types.StepTypeCommands,
Image: "bash",
Commands: []string{"echo 1"},
OnSuccess: true,
Failure: "fail",
Volumes: []string{defaultVolumes[0].Name + ":/test"},
WorkingDir: "/test/src/github.com/octocat/hello-world",
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}},
ExtraHosts: []backend_types.HostAlias{},
}, { }, {
Name: "parallel echo 2", Steps: []*backend_types.Step{{
Type: backend_types.StepTypeCommands, Name: "parallel echo 1",
Image: "bash", Type: backend_types.StepTypeCommands,
Commands: []string{"echo 2"}, Image: "bash",
OnSuccess: true, Commands: []string{"echo 1"},
Failure: "fail", OnSuccess: true,
Volumes: []string{defaultVolumes[0].Name + ":/test"}, Failure: "fail",
WorkingDir: "/test/src/github.com/octocat/hello-world", Volumes: []string{defaultVolumes[0].Name + ":/test"},
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}}, WorkingDir: "/test/src/github.com/octocat/hello-world",
ExtraHosts: []backend_types.HostAlias{}, Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}},
}}, ExtraHosts: []backend_types.HostAlias{},
}}, }},
}, {
Steps: []*backend_types.Step{{
Name: "parallel echo 2",
Type: backend_types.StepTypeCommands,
Image: "bash",
Commands: []string{"echo 2"},
OnSuccess: true,
Failure: "fail",
Volumes: []string{defaultVolumes[0].Name + ":/test"},
WorkingDir: "/test/src/github.com/octocat/hello-world",
Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}},
ExtraHosts: []backend_types.HostAlias{},
}},
},
},
}, },
}, },
{ {
@ -274,7 +276,7 @@ func TestCompilerCompile(t *testing.T) {
Name: "step", Name: "step",
Image: "bash", Image: "bash",
Commands: []string{"env"}, Commands: []string{"env"},
Secrets: yaml_types.Secrets{Secrets: []*yaml_types.Secret{{Source: "missing", Target: "missing"}}}, Secrets: []string{"missing"},
}}}}, }}}},
backConf: nil, backConf: nil,
expectedErr: "secret \"missing\" not found", expectedErr: "secret \"missing\" not found",

View file

@ -125,20 +125,17 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe
return nil, err return nil, err
} }
for _, requested := range container.Secrets.Secrets { for _, requested := range container.Secrets {
secretValue, err := getSecretValue(requested.Source) secretValue, err := getSecretValue(requested)
if err != nil { if err != nil {
return nil, err return nil, err
} }
toUpperTarget := strings.ToUpper(requested.Target) if !environmentAllowed(requested, stepType) {
if !environmentAllowed(toUpperTarget, stepType) {
continue continue
} }
environment[requested.Target] = secretValue environment[requested] = secretValue
// TODO: deprecated, remove in 3.x
environment[toUpperTarget] = secretValue
} }
if utils.MatchImage(container.Image, c.escalated...) && container.IsPlugin() { if utils.MatchImage(container.Image, c.escalated...) && container.IsPlugin() {

View file

@ -24,7 +24,6 @@ type dagCompilerStep struct {
step *backend_types.Step step *backend_types.Step
position int position int
name string name string
group string
dependsOn []string dependsOn []string
} }
@ -51,25 +50,16 @@ func (c dagCompiler) compile() ([]*backend_types.Stage, error) {
if c.isDAG() { if c.isDAG() {
return c.compileByDependsOn() return c.compileByDependsOn()
} }
return c.compileByGroup() return c.compileSequence()
} }
func (c dagCompiler) compileByGroup() ([]*backend_types.Stage, error) { func (c dagCompiler) compileSequence() ([]*backend_types.Stage, error) {
stages := make([]*backend_types.Stage, 0, len(c.steps)) stages := make([]*backend_types.Stage, 0, len(c.steps))
var currentStage *backend_types.Stage
var currentGroup string
for _, s := range c.steps { for _, s := range c.steps {
// create a new stage if current step is in a new group compared to last one stages = append(stages, &backend_types.Stage{
if currentStage == nil || currentGroup != s.group || s.group == "" { Steps: []*backend_types.Step{s.step},
currentGroup = s.group })
currentStage = new(backend_types.Stage)
stages = append(stages, currentStage)
}
// add step to current stage
currentStage.Steps = append(currentStage.Steps, s.step)
} }
return stages, nil return stages, nil

View file

@ -85,7 +85,6 @@ func TestConvertDAGToStages(t *testing.T) {
"echo env": { "echo env": {
position: 0, position: 0,
name: "echo env", name: "echo env",
group: "",
step: &backend_types.Step{ step: &backend_types.Step{
UUID: "01HJDPEW6R7J0JBE3F1T7Q0TYX", UUID: "01HJDPEW6R7J0JBE3F1T7Q0TYX",
Type: "commands", Type: "commands",
@ -96,7 +95,6 @@ func TestConvertDAGToStages(t *testing.T) {
"echo 1": { "echo 1": {
position: 1, position: 1,
name: "echo 1", name: "echo 1",
group: "",
dependsOn: []string{"echo env", "echo 2"}, dependsOn: []string{"echo env", "echo 2"},
step: &backend_types.Step{ step: &backend_types.Step{
UUID: "01HJDPF770QGRZER8RF79XVS4M", UUID: "01HJDPF770QGRZER8RF79XVS4M",
@ -108,7 +106,6 @@ func TestConvertDAGToStages(t *testing.T) {
"echo 2": { "echo 2": {
position: 2, position: 2,
name: "echo 2", name: "echo 2",
group: "",
step: &backend_types.Step{ step: &backend_types.Step{
UUID: "01HJDPFF5RMEYZW0YTGR1Y1ZR0", UUID: "01HJDPFF5RMEYZW0YTGR1Y1ZR0",
Type: "commands", Type: "commands",

View file

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"maps" "maps"
"path" "path"
"slices"
"strings" "strings"
"github.com/bmatcuk/doublestar/v4" "github.com/bmatcuk/doublestar/v4"
@ -37,20 +38,18 @@ type (
} }
Constraint struct { Constraint struct {
Ref List Ref List
Repo List Repo List
Instance List Instance List
Platform List Platform List
Environment List Branch List
Branch List Cron List
Cron List Status List
Status List Matrix Map
Matrix Map Local yamlBaseTypes.BoolTrue
Local yamlBaseTypes.BoolTrue Path Path
Path Path Evaluate string `yaml:"evaluate,omitempty"`
Evaluate string `yaml:"evaluate,omitempty"` Event yamlBaseTypes.StringOrSlice
// TODO: change to StringOrSlice in 3.x
Event List
} }
// List defines a runtime constraint for exclude & include string slices. // List defines a runtime constraint for exclude & include string slices.
@ -164,8 +163,7 @@ func (c *Constraint) Match(m metadata.Metadata, global bool, env map[string]stri
} }
match = match && c.Platform.Match(m.Sys.Platform) && match = match && c.Platform.Match(m.Sys.Platform) &&
c.Environment.Match(m.Curr.DeployTo) && (len(c.Event) == 0 || slices.Contains(c.Event, m.Curr.Event)) &&
c.Event.Match(m.Curr.Event) &&
c.Repo.Match(path.Join(m.Repo.Owner, m.Repo.Name)) && c.Repo.Match(path.Join(m.Repo.Owner, m.Repo.Name)) &&
c.Ref.Match(m.Curr.Commit.Ref) && c.Ref.Match(m.Curr.Commit.Ref) &&
c.Instance.Match(m.Sys.Host) c.Instance.Match(m.Sys.Host)

View file

@ -145,7 +145,7 @@ func (l *Linter) lintSettings(config *WorkflowConfig, c *types.Container, field
if len(c.Environment) != 0 { if len(c.Environment) != 0 {
return newLinterError("Should not configure both environment and settings", config.File, fmt.Sprintf("%s.%s", field, c.Name), true) return newLinterError("Should not configure both environment and settings", config.File, fmt.Sprintf("%s.%s", field, c.Name), true)
} }
if len(c.Secrets.Secrets) != 0 { if len(c.Secrets) != 0 {
return newLinterError("Should not configure both secrets and settings", config.File, fmt.Sprintf("%s.%s", field, c.Name), true) return newLinterError("Should not configure both secrets and settings", config.File, fmt.Sprintf("%s.%s", field, c.Name), true)
} }
return nil return nil
@ -218,102 +218,6 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) {
return err return err
} }
for _, step := range parsed.Steps.ContainerList {
if step.Group != "" {
err = multierr.Append(err, &errorTypes.PipelineError{
Type: errorTypes.PipelineErrorTypeDeprecation,
Message: "Please use depends_on instead of deprecated 'group' setting",
Data: errors.DeprecationErrorData{
File: config.File,
Field: "steps." + step.Name + ".group",
Docs: "https://woodpecker-ci.org/docs/next/usage/workflow-syntax#depends_on",
},
IsWarning: true,
})
}
}
for i, c := range parsed.When.Constraints {
if len(c.Event.Exclude) != 0 {
err = multierr.Append(err, &errorTypes.PipelineError{
Type: errorTypes.PipelineErrorTypeDeprecation,
Message: "Please only use allow lists for events",
Data: errors.DeprecationErrorData{
File: config.File,
Field: fmt.Sprintf("when[%d].event", i),
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#event-1",
},
IsWarning: true,
})
}
}
for _, step := range parsed.Steps.ContainerList {
for i, c := range step.When.Constraints {
if len(c.Event.Exclude) != 0 {
err = multierr.Append(err, &errorTypes.PipelineError{
Type: errorTypes.PipelineErrorTypeDeprecation,
Message: "Please only use allow lists for events",
Data: errors.DeprecationErrorData{
File: config.File,
Field: fmt.Sprintf("steps.%s.when[%d].event", step.Name, i),
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#event",
},
IsWarning: true,
})
}
}
}
for _, step := range parsed.Steps.ContainerList {
for i, c := range step.Secrets.Secrets {
if c.Source != c.Target {
err = multierr.Append(err, &errorTypes.PipelineError{
Type: errorTypes.PipelineErrorTypeDeprecation,
Message: "Secrets alternative names are deprecated, use environment with from_secret",
Data: errors.DeprecationErrorData{
File: config.File,
Field: fmt.Sprintf("steps.%s.secrets[%d]", step.Name, i),
Docs: "https://woodpecker-ci.org/docs/usage/secrets#use-secrets-in-settings-and-environment",
},
IsWarning: true,
})
}
}
}
for i, c := range parsed.When.Constraints {
if !c.Environment.IsEmpty() {
err = multierr.Append(err, &errorTypes.PipelineError{
Type: errorTypes.PipelineErrorTypeDeprecation,
Message: "environment filters are deprecated, use evaluate with CI_PIPELINE_DEPLOY_TARGET",
Data: errors.DeprecationErrorData{
File: config.File,
Field: fmt.Sprintf("when[%d].environment", i),
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate",
},
IsWarning: true,
})
}
}
for _, step := range parsed.Steps.ContainerList {
for i, c := range step.When.Constraints {
if !c.Environment.IsEmpty() {
err = multierr.Append(err, &errorTypes.PipelineError{
Type: errorTypes.PipelineErrorTypeDeprecation,
Message: "environment filters are deprecated, use evaluate with CI_PIPELINE_DEPLOY_TARGET",
Data: errors.DeprecationErrorData{
File: config.File,
Field: fmt.Sprintf("steps.%s.when[%d].environment", step.Name, i),
Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate",
},
IsWarning: true,
})
}
}
}
return err return err
} }
@ -326,7 +230,7 @@ func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) {
rootEventFilters := len(parsed.When.Constraints) > 0 rootEventFilters := len(parsed.When.Constraints) > 0
for _, c := range parsed.When.Constraints { for _, c := range parsed.When.Constraints {
if len(c.Event.Include) == 0 { if len(c.Event) == 0 {
rootEventFilters = false rootEventFilters = false
break break
} }
@ -340,7 +244,7 @@ func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) {
} else { } else {
stepEventIndex := -1 stepEventIndex := -1
for i, c := range step.When.Constraints { for i, c := range step.When.Constraints {
if len(c.Event.Include) == 0 { if len(c.Event) == 0 {
stepEventIndex = i stepEventIndex = i
break break
} }

View file

@ -162,7 +162,7 @@ func TestLintErrors(t *testing.T) {
want: "Cannot configure both entrypoint and settings", want: "Cannot configure both entrypoint and settings",
}, },
{ {
from: "steps: { build: { image: golang, settings: { test: 'true' }, environment: [ 'TEST=true' ] } }", from: "steps: { build: { image: golang, settings: { test: 'true' }, environment: { 'TEST': 'true' } } }",
want: "Should not configure both environment and settings", want: "Should not configure both environment and settings",
}, },
{ {

View file

@ -38,15 +38,6 @@ steps:
commands: commands:
- go test - go test
environment-array:
image: golang
environment:
- CGO=0
- GOOS=linux
- GOARCH=amd64
commands:
- go test
secrets: secrets:
image: docker image: docker
commands: commands:
@ -54,14 +45,7 @@ steps:
- echo $DOCKER_PASSWORD - echo $DOCKER_PASSWORD
secrets: secrets:
- docker_username - docker_username
- source: docker_prod_password - docker_prod_password
target: docker_password
group:
group: test
image: golang
commands:
- go test
detached: detached:
image: redis image: redis

View file

@ -37,14 +37,6 @@ steps:
- deployment - deployment
- release - release
when-event-exclude-pull_request_closed:
image: alpine
commands:
- echo "test"
when:
event:
exclude: pull_request_closed
when-ref: when-ref:
image: alpine image: alpine
commands: commands:
@ -78,7 +70,6 @@ steps:
commands: commands:
- echo "test" - echo "test"
when: when:
environment: production
event: deployment event: deployment
when-matrix: when-matrix:

View file

@ -163,18 +163,7 @@
"event": { "event": {
"description": "Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#event", "description": "Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#event",
"default": [], "default": [],
"oneOf": [ "$ref": "#/definitions/event_constraint_list"
{
"type": "array",
"minLength": 1,
"items": {
"$ref": "#/definitions/event_enum"
}
},
{
"$ref": "#/definitions/event_enum"
}
]
}, },
"ref": { "ref": {
"description": "Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#ref", "description": "Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#ref",
@ -188,10 +177,6 @@
"description": "Execute a step only on a specific platform. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#platform", "description": "Execute a step only on a specific platform. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#platform",
"$ref": "#/definitions/constraint_list" "$ref": "#/definitions/constraint_list"
}, },
"environment": {
"description": "Execute a step only for a specific environment. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#environment",
"$ref": "#/definitions/constraint_list"
},
"instance": { "instance": {
"description": "Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#instance", "description": "Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#instance",
"$ref": "#/definitions/constraint_list" "$ref": "#/definitions/constraint_list"
@ -291,10 +276,6 @@
"volumes": { "volumes": {
"$ref": "#/definitions/step_volumes" "$ref": "#/definitions/step_volumes"
}, },
"group": {
"description": "deprecated, use depends_on",
"type": "string"
},
"depends_on": { "depends_on": {
"description": "Execute a step after another step has finished.", "description": "Execute a step after another step has finished.",
"oneOf": [ "oneOf": [
@ -377,10 +358,6 @@
"volumes": { "volumes": {
"$ref": "#/definitions/step_volumes" "$ref": "#/definitions/step_volumes"
}, },
"group": {
"description": "deprecated, use depends_on",
"type": "string"
},
"depends_on": { "depends_on": {
"description": "Execute a step after another step has finished.", "description": "Execute a step after another step has finished.",
"oneOf": [ "oneOf": [
@ -457,10 +434,6 @@
"volumes": { "volumes": {
"$ref": "#/definitions/step_volumes" "$ref": "#/definitions/step_volumes"
}, },
"group": {
"description": "deprecated, use depends_on",
"type": "string"
},
"depends_on": { "depends_on": {
"description": "Execute a step after another step has finished.", "description": "Execute a step after another step has finished.",
"oneOf": [ "oneOf": [
@ -551,10 +524,6 @@
"description": "Execute a step only on a specific platform. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#platform", "description": "Execute a step only on a specific platform. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#platform",
"$ref": "#/definitions/constraint_list" "$ref": "#/definitions/constraint_list"
}, },
"environment": {
"description": "Execute a step only for a specific environment. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#environment",
"$ref": "#/definitions/constraint_list"
},
"matrix": { "matrix": {
"description": "Read more: https://woodpecker-ci.org/docs/usage/matrix-workflows", "description": "Read more: https://woodpecker-ci.org/docs/usage/matrix-workflows",
"type": "object", "type": "object",
@ -624,40 +593,6 @@
"items": { "items": {
"$ref": "#/definitions/event_enum" "$ref": "#/definitions/event_enum"
} }
},
{
"type": "object",
"additionalProperties": false,
"properties": {
"include": {
"oneOf": [
{
"$ref": "#/definitions/event_enum"
},
{
"type": "array",
"minLength": 1,
"items": {
"$ref": "#/definitions/event_enum"
}
}
]
},
"exclude": {
"oneOf": [
{
"$ref": "#/definitions/event_enum"
},
{
"type": "array",
"minLength": 1,
"items": {
"$ref": "#/definitions/event_enum"
}
}
]
}
}
} }
] ]
}, },
@ -739,43 +674,16 @@
}, },
"step_environment": { "step_environment": {
"description": "Pass environment variables to a pipeline step. Read more: https://woodpecker-ci.org/docs/usage/environment", "description": "Pass environment variables to a pipeline step. Read more: https://woodpecker-ci.org/docs/usage/environment",
"oneOf": [ "type": "object",
{ "additionalProperties": {
"type": "array", "type": ["boolean", "string", "number", "array", "object"]
"items": { }
"type": "string"
},
"minLength": 1
},
{
"type": "object",
"additionalProperties": {
"type": ["boolean", "string", "number", "array", "object"]
}
}
]
}, },
"step_secrets": { "step_secrets": {
"description": "Pass secrets to a pipeline step at runtime. Read more: https://woodpecker-ci.org/docs/usage/secrets", "description": "Pass secrets to a pipeline step at runtime. Read more: https://woodpecker-ci.org/docs/usage/secrets",
"type": "array", "type": "array",
"items": { "items": {
"oneOf": [ "type": "string"
{
"type": "string"
},
{
"type": "object",
"required": ["source", "target"],
"properties": {
"source": {
"type": "string"
},
"target": {
"type": "string"
}
}
}
]
}, },
"minLength": 1 "minLength": 1
}, },

View file

@ -15,6 +15,7 @@
package yaml package yaml
import ( import (
"slices"
"testing" "testing"
"github.com/franela/goblin" "github.com/franela/goblin"
@ -35,7 +36,7 @@ func TestParse(t *testing.T) {
g.Fail(err) g.Fail(err)
} }
g.Assert(out.When.Constraints[0].Event.Match("tester")).Equal(true) g.Assert(slices.Contains(out.When.Constraints[0].Event, "tester")).Equal(true)
g.Assert(out.Workspace.Base).Equal("/go") g.Assert(out.Workspace.Base).Equal("/go")
g.Assert(out.Workspace.Path).Equal("src/github.com/octocat/hello-world") g.Assert(out.Workspace.Path).Equal("src/github.com/octocat/hello-world")
@ -85,7 +86,7 @@ func TestParse(t *testing.T) {
g.Assert(len(out.Steps.ContainerList[0].When.Constraints)).Equal(0) g.Assert(len(out.Steps.ContainerList[0].When.Constraints)).Equal(0)
g.Assert(out.Steps.ContainerList[1].Name).Equal("notify_success") g.Assert(out.Steps.ContainerList[1].Name).Equal("notify_success")
g.Assert(out.Steps.ContainerList[1].Image).Equal("plugins/slack") g.Assert(out.Steps.ContainerList[1].Image).Equal("plugins/slack")
g.Assert(out.Steps.ContainerList[1].When.Constraints[0].Event.Include).Equal([]string{"success"}) g.Assert(out.Steps.ContainerList[1].When.Constraints[0].Event).Equal(yaml_base_types.StringOrSlice{"success"})
}) })
matchConfig, err := ParseString(sampleYaml) matchConfig, err := ParseString(sampleYaml)

View file

@ -1,51 +0,0 @@
// Copyright 2023 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 base
import (
"errors"
"fmt"
"strings"
)
// SliceOrMap represents a map of strings, string slice are converted into a map.
type SliceOrMap map[string]any
// UnmarshalYAML implements the Unmarshaler interface.
func (s *SliceOrMap) UnmarshalYAML(unmarshal func(any) error) error {
var sliceType []any
if err := unmarshal(&sliceType); err == nil {
parts := map[string]any{}
for _, s := range sliceType {
if str, ok := s.(string); ok {
str := strings.TrimSpace(str)
key, val, _ := strings.Cut(str, "=")
parts[key] = val
} else {
return fmt.Errorf("cannot unmarshal '%v' of type %T into a string value", s, s)
}
}
*s = parts
return nil
}
var mapType map[string]any
if err := unmarshal(&mapType); err == nil {
*s = mapType
return nil
}
return errors.New("failed to unmarshal SliceOrMap")
}

View file

@ -1,58 +0,0 @@
// 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 base
import (
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
type StructSliceOrMap struct {
Foos SliceOrMap `yaml:"foos,omitempty"`
Bars []string `yaml:"bars"`
}
func TestSliceOrMapYaml(t *testing.T) {
str := `{foos: [bar=baz, far=faz]}`
s := StructSliceOrMap{}
assert.NoError(t, yaml.Unmarshal([]byte(str), &s))
assert.Equal(t, SliceOrMap{"bar": "baz", "far": "faz"}, s.Foos)
d, err := yaml.Marshal(&s)
assert.NoError(t, err)
s2 := StructSliceOrMap{}
assert.NoError(t, yaml.Unmarshal(d, &s2))
assert.Equal(t, SliceOrMap{"bar": "baz", "far": "faz"}, s2.Foos)
}
func TestStr2SliceOrMapPtrMap(t *testing.T) {
s := map[string]*StructSliceOrMap{"udav": {
Foos: SliceOrMap{"io.rancher.os.bar": "baz", "io.rancher.os.far": "true"},
Bars: []string{},
}}
d, err := yaml.Marshal(&s)
assert.NoError(t, err)
s2 := map[string]*StructSliceOrMap{}
assert.NoError(t, yaml.Unmarshal(d, &s2))
assert.Equal(t, s, s2)
}

View file

@ -39,7 +39,6 @@ type (
Detached bool `yaml:"detach,omitempty"` Detached bool `yaml:"detach,omitempty"`
Directory string `yaml:"directory,omitempty"` Directory string `yaml:"directory,omitempty"`
Failure string `yaml:"failure,omitempty"` Failure string `yaml:"failure,omitempty"`
Group string `yaml:"group,omitempty"`
Image string `yaml:"image,omitempty"` Image string `yaml:"image,omitempty"`
Name string `yaml:"name,omitempty"` Name string `yaml:"name,omitempty"`
Pull bool `yaml:"pull,omitempty"` Pull bool `yaml:"pull,omitempty"`
@ -49,10 +48,8 @@ type (
Ports []string `yaml:"ports,omitempty"` Ports []string `yaml:"ports,omitempty"`
DependsOn base.StringOrSlice `yaml:"depends_on,omitempty"` DependsOn base.StringOrSlice `yaml:"depends_on,omitempty"`
// TODO: make []string in 3.x Secrets []string `yaml:"secrets,omitempty"`
Secrets Secrets `yaml:"secrets,omitempty"` Environment map[string]any `yaml:"environment,omitempty"`
// TODO: make map[string]any in 3.x
Environment base.SliceOrMap `yaml:"environment,omitempty"`
// Docker and Kubernetes Specific // Docker and Kubernetes Specific
Privileged bool `yaml:"privileged,omitempty"` Privileged bool `yaml:"privileged,omitempty"`
@ -125,7 +122,7 @@ func (c *Container) IsPlugin() bool {
return len(c.Commands) == 0 && return len(c.Commands) == 0 &&
len(c.Entrypoint) == 0 && len(c.Entrypoint) == 0 &&
len(c.Environment) == 0 && len(c.Environment) == 0 &&
len(c.Secrets.Secrets) == 0 len(c.Secrets) == 0
} }
func (c *Container) IsTrustedCloneImage() bool { func (c *Container) IsTrustedCloneImage() bool {

View file

@ -41,8 +41,8 @@ dns: 8.8.8.8
dns_search: example.com dns_search: example.com
entrypoint: [/bin/sh, -c] entrypoint: [/bin/sh, -c]
environment: environment:
- RACK_ENV=development RACK_ENV: development
- SHOW=true SHOW: true
extra_hosts: extra_hosts:
- somehost:162.242.195.82 - somehost:162.242.195.82
- otherhost:50.31.209.229 - otherhost:50.31.209.229
@ -88,7 +88,7 @@ func TestUnmarshalContainer(t *testing.T) {
DNS: base.StringOrSlice{"8.8.8.8"}, DNS: base.StringOrSlice{"8.8.8.8"},
DNSSearch: base.StringOrSlice{"example.com"}, DNSSearch: base.StringOrSlice{"example.com"},
Entrypoint: []string{"/bin/sh", "-c"}, Entrypoint: []string{"/bin/sh", "-c"},
Environment: base.SliceOrMap{"RACK_ENV": "development", "SHOW": "true"}, Environment: map[string]any{"RACK_ENV": "development", "SHOW": true},
ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229", "ipv6:2001:db8::10"}, ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229", "ipv6:2001:db8::10"},
Image: "golang:latest", Image: "golang:latest",
MemLimit: base.MemStringOrInt(1024), MemLimit: base.MemStringOrInt(1024),
@ -114,9 +114,7 @@ func TestUnmarshalContainer(t *testing.T) {
}, },
}, },
{ {
Event: constraint.List{ Event: base.StringOrSlice{"cron"},
Include: []string{"cron"},
},
Cron: constraint.List{ Cron: constraint.List{
Include: []string{"job1"}, Include: []string{"job1"},
}, },
@ -166,7 +164,6 @@ func TestUnmarshalContainers(t *testing.T) {
}, },
{ {
from: `publish-agent: from: `publish-agent:
group: bundle
image: print/env image: print/env
settings: settings:
repo: woodpeckerci/woodpecker-agent repo: woodpeckerci/woodpecker-agent
@ -179,16 +176,9 @@ func TestUnmarshalContainers(t *testing.T) {
event: push`, event: push`,
want: []*Container{ want: []*Container{
{ {
Name: "publish-agent", Name: "publish-agent",
Image: "print/env", Image: "print/env",
Group: "bundle", Secrets: []string{"docker_username", "docker_password"},
Secrets: Secrets{Secrets: []*Secret{{
Source: "docker_username",
Target: "docker_username",
}, {
Source: "docker_password",
Target: "docker_password",
}}},
Settings: map[string]any{ Settings: map[string]any{
"repo": "woodpeckerci/woodpecker-agent", "repo": "woodpeckerci/woodpecker-agent",
"dockerfile": "docker/Dockerfile.agent", "dockerfile": "docker/Dockerfile.agent",
@ -198,7 +188,7 @@ func TestUnmarshalContainers(t *testing.T) {
When: constraint.When{ When: constraint.When{
Constraints: []constraint.Constraint{ Constraints: []constraint.Constraint{
{ {
Event: constraint.List{Include: []string{"push"}}, Event: base.StringOrSlice{"push"},
Branch: constraint.List{Include: []string{"${CI_REPO_DEFAULT_BRANCH}"}}, Branch: constraint.List{Include: []string{"${CI_REPO_DEFAULT_BRANCH}"}},
}, },
}, },
@ -208,7 +198,6 @@ func TestUnmarshalContainers(t *testing.T) {
}, },
{ {
from: `publish-cli: from: `publish-cli:
group: docker
image: print/env image: print/env
settings: settings:
repo: woodpeckerci/woodpecker-cli repo: woodpeckerci/woodpecker-cli
@ -221,7 +210,6 @@ func TestUnmarshalContainers(t *testing.T) {
{ {
Name: "publish-cli", Name: "publish-cli",
Image: "print/env", Image: "print/env",
Group: "docker",
Settings: map[string]any{ Settings: map[string]any{
"repo": "woodpeckerci/woodpecker-cli", "repo": "woodpeckerci/woodpecker-cli",
"dockerfile": "docker/Dockerfile.cli", "dockerfile": "docker/Dockerfile.cli",
@ -230,7 +218,7 @@ func TestUnmarshalContainers(t *testing.T) {
When: constraint.When{ When: constraint.When{
Constraints: []constraint.Constraint{ Constraints: []constraint.Constraint{
{ {
Event: constraint.List{Include: []string{"push"}}, Event: base.StringOrSlice{"push"},
Branch: constraint.List{Include: []string{"${CI_REPO_DEFAULT_BRANCH}"}}, Branch: constraint.List{Include: []string{"${CI_REPO_DEFAULT_BRANCH}"}},
}, },
}, },
@ -252,11 +240,11 @@ func TestUnmarshalContainers(t *testing.T) {
When: constraint.When{ When: constraint.When{
Constraints: []constraint.Constraint{ Constraints: []constraint.Constraint{
{ {
Event: constraint.List{Include: []string{"push"}}, Event: base.StringOrSlice{"push"},
Branch: constraint.List{Include: []string{"${CI_REPO_DEFAULT_BRANCH}"}}, Branch: constraint.List{Include: []string{"${CI_REPO_DEFAULT_BRANCH}"}},
}, },
{ {
Event: constraint.List{Include: []string{"pull_request"}}, Event: base.StringOrSlice{"pull_request"},
}, },
}, },
}, },

View file

@ -1,48 +0,0 @@
// Copyright 2023 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 types
import "gopkg.in/yaml.v3"
type (
// Secrets defines a collection of secrets.
Secrets struct {
Secrets []*Secret
}
// Secret defines a container secret.
Secret struct {
Source string `yaml:"source"`
Target string `yaml:"target"`
}
)
// UnmarshalYAML implements the Unmarshaler interface.
func (s *Secrets) UnmarshalYAML(value *yaml.Node) error {
y, _ := yaml.Marshal(value)
var secrets []string
err := yaml.Unmarshal(y, &secrets)
if err == nil {
for _, str := range secrets {
s.Secrets = append(s.Secrets, &Secret{
Source: str,
Target: str,
})
}
return nil
}
return yaml.Unmarshal(y, &s.Secrets)
}

View file

@ -1,73 +0,0 @@
// Copyright 2023 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 types
import (
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
)
func TestUnmarshalSecrets(t *testing.T) {
testdata := []struct {
from string
want []*Secret
}{
{
from: "[ mysql_username, mysql_password]",
want: []*Secret{
{
Source: "mysql_username",
Target: "mysql_username",
},
{
Source: "mysql_password",
Target: "mysql_password",
},
},
},
{
from: "[ { source: mysql_prod_username, target: mysql_username } ]",
want: []*Secret{
{
Source: "mysql_prod_username",
Target: "mysql_username",
},
},
},
{
from: "[ { source: mysql_prod_username, target: mysql_username }, { source: redis_username, target: redis_username } ]",
want: []*Secret{
{
Source: "mysql_prod_username",
Target: "mysql_username",
},
{
Source: "redis_username",
Target: "redis_username",
},
},
},
}
for _, test := range testdata {
in := []byte(test.from)
got := Secrets{}
err := yaml.Unmarshal(in, &got)
assert.NoError(t, err)
assert.EqualValues(t, test.want, got.Secrets, "problem parsing secrets %q", test.from)
}
}