1
0
mirror of https://github.com/woodpecker-ci/woodpecker.git synced 2026-06-03 16:35:37 +02:00
Files
woodpecker/pipeline/frontend/yaml/compiler/compiler.go
T

284 lines
8.4 KiB
Go
Raw Normal View History

2023-08-10 11:06:00 +02:00
// 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.
2017-03-05 18:56:08 +11:00
package compiler
import (
"errors"
2017-03-05 18:56:08 +11:00
"fmt"
2025-07-05 12:59:17 +03:00
"maps"
"path"
2025-07-05 12:59:17 +03:00
"slices"
2017-03-05 18:56:08 +11:00
2024-12-22 11:44:34 +02:00
backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata"
yaml_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types"
"go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/utils"
"go.woodpecker-ci.org/woodpecker/v3/shared/constant"
2017-03-05 18:56:08 +11:00
)
2022-01-17 14:43:30 +01:00
const (
defaultCloneName = "clone"
2022-01-17 14:43:30 +01:00
)
// Registry represents registry credentials.
2017-04-07 01:04:25 +09:00
type Registry struct {
Hostname string
Username string
Password string
}
2017-04-10 12:39:50 +02:00
type Secret struct {
Name string
Value string
AllowedPlugins []string
Events []metadata.Event
2022-10-27 04:21:07 +02:00
}
func (s *Secret) Available(event metadata.Event, container *yaml_types.Container) error {
2024-01-27 20:59:44 +01:00
onlyAllowSecretForPlugins := len(s.AllowedPlugins) > 0
if onlyAllowSecretForPlugins && !container.IsPlugin() {
2024-09-26 14:04:07 +02:00
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)
2024-01-27 20:59:44 +01:00
}
2017-04-10 12:39:50 +02:00
if onlyAllowSecretForPlugins && !utils.MatchImageDynamic(container.Image, s.AllowedPlugins...) {
2024-01-27 20:59:44 +01:00
return fmt.Errorf("secret %q is not allowed to be used with image %q by step %q", s.Name, container.Image, container.Name)
}
if !s.Match(event) {
return fmt.Errorf("secret %q is not allowed to be used with pipeline event %q", s.Name, event)
}
2024-01-27 20:59:44 +01:00
return nil
}
// Match returns true if an image and event match the restricted list.
// Note that EventPullClosed are treated as EventPull.
func (s *Secret) Match(event metadata.Event) bool {
2024-01-27 20:59:44 +01:00
// if there is no filter set secret matches all webhook events
if len(s.Events) == 0 {
return true
}
2024-03-29 09:48:28 +01:00
// treat all pull events the same way
if event.IsPull() {
2025-09-25 00:07:45 +02:00
event = metadata.EventPull
}
2024-01-27 20:59:44 +01:00
// one match is enough
2025-07-05 12:59:17 +03:00
return slices.Contains(s.Events, event)
}
// Compiler compiles the yaml.
2017-03-05 18:56:08 +11:00
type Compiler struct {
2024-11-01 22:37:31 +02:00
local bool
escalated []string
prefix string
volumes []string
networks []string
env map[string]string
cloneEnv map[string]string
workspaceBase string
workspacePath string
metadata metadata.Metadata
registries []Registry
secrets map[string]Secret
defaultClonePlugin string
trustedClonePlugins []string
securityTrustedPipeline bool
// TODO: remove with version 4.x
forceIgnoreServiceFailure bool
2017-03-05 18:56:08 +11:00
}
// New creates a new Compiler with options.
func New(opts ...Option) *Compiler {
2017-04-10 12:39:50 +02:00
compiler := &Compiler{
env: map[string]string{},
cloneEnv: map[string]string{},
secrets: map[string]Secret{},
defaultClonePlugin: constant.DefaultClonePlugin,
trustedClonePlugins: constant.TrustedClonePlugins,
2017-04-10 12:39:50 +02:00
}
2017-03-05 18:56:08 +11:00
for _, opt := range opts {
opt(compiler)
}
return compiler
}
// Compile compiles the YAML configuration to the pipeline intermediate
// representation configuration format.
2023-06-06 09:14:21 +02:00
func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, error) {
config := new(backend_types.Config)
2017-03-05 18:56:08 +11:00
if match, err := conf.When.Match(c.metadata, true, c.env); !match && err == nil {
// This pipeline does not match the configured filter so return an empty config and stop further compilation.
// An empty pipeline will just be skipped completely.
2022-10-06 01:49:23 +02:00
return config, nil
} else if err != nil {
return nil, err
}
2017-03-05 18:56:08 +11:00
// create a default volume
2025-07-05 12:59:17 +03:00
config.Volume = fmt.Sprintf("%s_default", c.prefix)
2017-03-05 18:56:08 +11:00
// create a default network
2025-07-05 12:59:17 +03:00
config.Network = fmt.Sprintf("%s_default", c.prefix)
2017-03-05 18:56:08 +11:00
2020-11-19 15:42:18 +08:00
// create secrets for mask
for _, sec := range c.secrets {
2023-06-06 09:14:21 +02:00
config.Secrets = append(config.Secrets, &backend_types.Secret{
2020-11-19 15:42:18 +08:00
Name: sec.Name,
Value: sec.Value,
})
}
2017-03-05 18:56:08 +11:00
// overrides the default workspace paths when specified
// in the YAML file.
if len(conf.Workspace.Base) != 0 {
c.workspaceBase = path.Clean(conf.Workspace.Base)
2017-03-05 18:56:08 +11:00
}
if len(conf.Workspace.Path) != 0 {
c.workspacePath = path.Clean(conf.Workspace.Path)
2017-03-05 18:56:08 +11:00
}
// add default clone step
if !c.local && len(conf.Clone.ContainerList) == 0 && !conf.SkipClone && len(c.defaultClonePlugin) != 0 {
2023-11-12 18:23:48 +01:00
cloneSettings := map[string]any{"depth": "0"}
if c.metadata.Curr.Event == metadata.EventTag {
cloneSettings["tags"] = "true"
}
2023-06-06 09:14:21 +02:00
container := &yaml_types.Container{
2024-02-23 16:32:06 +01:00
Name: defaultCloneName,
Image: c.defaultClonePlugin,
2024-02-23 16:32:06 +01:00
Settings: cloneSettings,
Environment: make(map[string]any),
}
for k, v := range c.cloneEnv {
container.Environment[k] = v
}
step, err := c.createProcess(container, conf, backend_types.StepTypeClone)
2023-11-05 12:47:42 +01:00
if err != nil {
return nil, err
}
2017-03-05 18:56:08 +11:00
2023-06-06 09:14:21 +02:00
stage := new(backend_types.Stage)
2017-03-05 18:56:08 +11:00
stage.Steps = append(stage.Steps, step)
config.Stages = append(config.Stages, stage)
2021-10-16 02:54:28 +02:00
} else if !c.local && !conf.SkipClone {
for _, container := range conf.Clone.ContainerList {
if match, err := container.When.Match(c.metadata, false, c.env); !match && err == nil {
2017-03-05 18:56:08 +11:00
continue
2022-10-06 01:49:23 +02:00
} else if err != nil {
return nil, err
2017-03-05 18:56:08 +11:00
}
2022-10-06 01:49:23 +02:00
2023-06-06 09:14:21 +02:00
stage := new(backend_types.Stage)
2017-03-05 18:56:08 +11:00
step, err := c.createProcess(container, conf, backend_types.StepTypeClone)
2023-11-05 12:47:42 +01:00
if err != nil {
return nil, err
}
// only inject netrc if it's a trusted repo or a trusted plugin
if c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
2025-07-05 12:59:17 +03:00
maps.Copy(step.Environment, c.cloneEnv)
}
2017-03-05 18:56:08 +11:00
stage.Steps = append(stage.Steps, step)
config.Stages = append(config.Stages, stage)
}
}
// add services steps
2023-06-06 09:14:21 +02:00
if len(conf.Services.ContainerList) != 0 {
stage := new(backend_types.Stage)
2017-03-05 18:56:08 +11:00
for _, container := range conf.Services.ContainerList {
if match, err := container.When.Match(c.metadata, false, c.env); !match && err == nil {
2017-05-14 19:28:17 +02:00
continue
2022-10-06 01:49:23 +02:00
} else if err != nil {
return nil, err
2017-05-14 19:28:17 +02:00
}
step, err := c.createProcess(container, conf, backend_types.StepTypeService)
2023-11-05 12:47:42 +01:00
if err != nil {
return nil, err
}
2017-03-05 18:56:08 +11:00
stage.Steps = append(stage.Steps, step)
}
config.Stages = append(config.Stages, stage)
}
2023-12-24 12:14:30 +01:00
// add pipeline steps
stepNames := make(map[string]struct{}, len(conf.Steps.ContainerList))
for _, container := range conf.Steps.ContainerList {
stepNames[container.Name] = struct{}{}
}
2023-12-24 12:14:30 +01:00
steps := make([]*dagCompilerStep, 0, len(conf.Steps.ContainerList))
for pos, container := range conf.Steps.ContainerList {
2022-01-05 21:50:23 +01:00
// Skip if local and should not run local
2022-08-14 19:32:49 +02:00
if c.local && !container.When.IsLocal() {
2017-05-06 03:15:47 +02:00
continue
}
if match, err := container.When.Match(c.metadata, false, c.env); !match && err == nil {
2017-03-05 18:56:08 +11:00
continue
2022-10-06 01:49:23 +02:00
} else if err != nil {
return nil, err
2017-03-05 18:56:08 +11:00
}
stepType := backend_types.StepTypeCommands
if container.IsPlugin() {
stepType = backend_types.StepTypePlugin
}
step, err := c.createProcess(container, conf, stepType)
2023-11-05 12:47:42 +01:00
if err != nil {
return nil, err
}
2023-08-08 12:49:29 +02:00
// only inject netrc if it's a trusted repo or a trusted plugin
2024-11-01 22:37:31 +02:00
if c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) {
2025-07-05 12:59:17 +03:00
maps.Copy(step.Environment, c.cloneEnv)
2023-08-08 12:49:29 +02:00
}
2023-12-24 12:14:30 +01:00
steps = append(steps, &dagCompilerStep{
step: step,
position: pos,
name: container.Name,
dependsOn: container.DependsOn,
})
}
// generate stages out of steps
stepStages, err := newDAGCompiler(steps).compile()
2023-12-24 12:14:30 +01:00
if err != nil {
// If the missing dep exists in the config but isn't in the surviving
// step list, it was filtered out by its 'when' conditions. Surface a
// more actionable error.
var missingDepErr *ErrStepMissingDependency
if errors.As(err, &missingDepErr) {
if _, inConfig := stepNames[missingDepErr.dep]; inConfig {
return nil, &ErrStepFilteredDependency{name: missingDepErr.name, dep: missingDepErr.dep}
}
}
2023-12-24 12:14:30 +01:00
return nil, err
2017-03-05 18:56:08 +11:00
}
2023-12-24 12:14:30 +01:00
config.Stages = append(config.Stages, stepStages...)
2022-10-06 01:49:23 +02:00
return config, nil
2017-03-05 18:56:08 +11:00
}