woodpecker/pipeline/frontend/yaml/compiler/compiler.go

317 lines
8.2 KiB
Go
Raw Normal View History

// 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 07:56:08 +00:00
package compiler
import (
"fmt"
"path"
2017-03-05 07:56:08 +00:00
backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
yaml_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/utils"
"go.woodpecker-ci.org/woodpecker/v2/shared/constant"
2017-03-05 07:56:08 +00:00
)
const (
defaultCloneName = "clone"
)
// Registry represents registry credentials
2017-04-06 16:04:25 +00:00
type Registry struct {
Hostname string
Username string
Password string
Email string
Token string
}
2017-04-10 10:39:50 +00:00
type Secret struct {
Name string
Value string
AllowedPlugins []string
}
func (s *Secret) Available(container *yaml_types.Container) bool {
return (len(s.AllowedPlugins) == 0 || utils.MatchImage(container.Image, s.AllowedPlugins...)) && (len(s.AllowedPlugins) == 0 || container.IsPlugin())
2017-04-10 10:39:50 +00:00
}
type secretMap map[string]Secret
func (sm secretMap) toStringMap() map[string]string {
m := make(map[string]string, len(sm))
for k, v := range sm {
m[k] = v.Value
}
return m
}
2017-06-22 19:06:28 +00:00
type ResourceLimit struct {
MemSwapLimit int64
MemLimit int64
ShmSize int64
CPUQuota int64
CPUShares int64
CPUSet string
}
2017-03-05 07:56:08 +00:00
// Compiler compiles the yaml
type Compiler struct {
local bool
escalated []string
prefix string
volumes []string
networks []string
env map[string]string
cloneEnv map[string]string
base string
path string
metadata metadata.Metadata
registries []Registry
secrets secretMap
cacher Cacher
reslimit ResourceLimit
defaultCloneImage string
trustedPipeline bool
netrcOnlyTrusted bool
2017-03-05 07:56:08 +00:00
}
// New creates a new Compiler with options.
func New(opts ...Option) *Compiler {
2017-04-10 10:39:50 +00:00
compiler := &Compiler{
env: map[string]string{},
cloneEnv: map[string]string{},
secrets: map[string]Secret{},
2017-04-10 10:39:50 +00:00
}
2017-03-05 07:56:08 +00:00
for _, opt := range opts {
opt(compiler)
}
return compiler
}
// Compile compiles the YAML configuration to the pipeline intermediate
// representation configuration format.
func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, error) {
config := new(backend_types.Config)
2017-03-05 07:56:08 +00: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.
return config, nil
} else if err != nil {
return nil, err
}
2017-03-05 07:56:08 +00:00
// create a default volume
config.Volumes = append(config.Volumes, &backend_types.Volume{
Name: fmt.Sprintf("%s_default", c.prefix),
2017-03-05 07:56:08 +00:00
})
// create a default network
config.Networks = append(config.Networks, &backend_types.Network{
Name: fmt.Sprintf("%s_default", c.prefix),
})
2017-03-05 07:56:08 +00:00
2020-11-19 07:42:18 +00:00
// create secrets for mask
for _, sec := range c.secrets {
config.Secrets = append(config.Secrets, &backend_types.Secret{
2020-11-19 07:42:18 +00:00
Name: sec.Name,
Value: sec.Value,
Mask: true,
})
}
2017-03-05 07:56:08 +00:00
// overrides the default workspace paths when specified
// in the YAML file.
if len(conf.Workspace.Base) != 0 {
c.base = conf.Workspace.Base
}
if len(conf.Workspace.Path) != 0 {
c.path = conf.Workspace.Path
}
cloneImage := constant.DefaultCloneImage
if len(c.defaultCloneImage) > 0 {
cloneImage = c.defaultCloneImage
}
2017-03-05 07:56:08 +00:00
// add default clone step
if !c.local && len(conf.Clone.ContainerList) == 0 && !conf.SkipClone {
cloneSettings := map[string]any{"depth": "0"}
if c.metadata.Curr.Event == metadata.EventTag {
cloneSettings["tags"] = "true"
}
container := &yaml_types.Container{
Name: defaultCloneName,
Image: cloneImage,
Settings: cloneSettings,
Environment: c.cloneEnv,
}
step, err := c.createProcess(container, backend_types.StepTypeClone)
if err != nil {
return nil, err
}
2017-03-05 07:56:08 +00:00
stage := new(backend_types.Stage)
2017-03-05 07:56:08 +00:00
stage.Steps = append(stage.Steps, step)
config.Stages = append(config.Stages, stage)
} 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 07:56:08 +00:00
continue
} else if err != nil {
return nil, err
2017-03-05 07:56:08 +00:00
}
stage := new(backend_types.Stage)
2017-03-05 07:56:08 +00:00
step, err := c.createProcess(container, backend_types.StepTypeClone)
if err != nil {
return nil, err
}
// only inject netrc if it's a trusted repo or a trusted plugin
if !c.netrcOnlyTrusted || c.trustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage()) {
for k, v := range c.cloneEnv {
step.Environment[k] = v
}
}
2017-03-05 07:56:08 +00:00
stage.Steps = append(stage.Steps, step)
config.Stages = append(config.Stages, stage)
}
}
err := c.setupCache(conf, config)
if err != nil {
return nil, err
}
2017-06-22 19:06:28 +00:00
2017-03-05 07:56:08 +00:00
// add services steps
if len(conf.Services.ContainerList) != 0 {
stage := new(backend_types.Stage)
2017-03-05 07:56:08 +00:00
for _, container := range conf.Services.ContainerList {
if match, err := container.When.Match(c.metadata, false, c.env); !match && err == nil {
2017-05-14 17:28:17 +00:00
continue
} else if err != nil {
return nil, err
2017-05-14 17:28:17 +00:00
}
step, err := c.createProcess(container, backend_types.StepTypeService)
if err != nil {
return nil, err
}
2017-03-05 07:56:08 +00:00
stage.Steps = append(stage.Steps, step)
}
config.Stages = append(config.Stages, stage)
}
// add pipeline steps
steps := make([]*dagCompilerStep, 0, len(conf.Steps.ContainerList))
for pos, container := range conf.Steps.ContainerList {
2022-01-05 20:50:23 +00:00
// Skip if local and should not run local
if c.local && !container.When.IsLocal() {
2017-05-06 01:15:47 +00:00
continue
}
if match, err := container.When.Match(c.metadata, false, c.env); !match && err == nil {
2017-03-05 07:56:08 +00:00
continue
} else if err != nil {
return nil, err
2017-03-05 07:56:08 +00:00
}
stepType := backend_types.StepTypeCommands
if container.IsPlugin() {
stepType = backend_types.StepTypePlugin
}
step, err := c.createProcess(container, stepType)
if err != nil {
return nil, err
}
// inject netrc if it's a trusted repo or a trusted clone-plugin
if c.trustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage()) {
for k, v := range c.cloneEnv {
step.Environment[k] = v
}
}
steps = append(steps, &dagCompilerStep{
step: step,
position: pos,
name: container.Name,
group: container.Group,
dependsOn: container.DependsOn,
})
}
// generate stages out of steps
stepStages, err := newDAGCompiler(steps).compile()
if err != nil {
return nil, err
2017-03-05 07:56:08 +00:00
}
config.Stages = append(config.Stages, stepStages...)
err = c.setupCacheRebuild(conf, config)
if err != nil {
return nil, err
}
2017-06-22 19:06:28 +00:00
return config, nil
2017-03-05 07:56:08 +00:00
}
2017-06-22 19:06:28 +00:00
func (c *Compiler) setupCache(conf *yaml_types.Workflow, ir *backend_types.Config) error {
2017-06-22 19:06:28 +00:00
if c.local || len(conf.Cache) == 0 || c.cacher == nil {
return nil
2017-06-22 19:06:28 +00:00
}
container := c.cacher.Restore(path.Join(c.metadata.Repo.Owner, c.metadata.Repo.Name), c.metadata.Curr.Commit.Branch, conf.Cache)
step, err := c.createProcess(container, backend_types.StepTypeCache)
if err != nil {
return err
}
2017-06-22 19:06:28 +00:00
stage := new(backend_types.Stage)
2017-06-22 19:06:28 +00:00
stage.Steps = append(stage.Steps, step)
ir.Stages = append(ir.Stages, stage)
return nil
2017-06-22 19:06:28 +00:00
}
func (c *Compiler) setupCacheRebuild(conf *yaml_types.Workflow, ir *backend_types.Config) error {
if c.local || len(conf.Cache) == 0 || c.metadata.Curr.Event != metadata.EventPush || c.cacher == nil {
return nil
2017-06-22 19:06:28 +00:00
}
container := c.cacher.Rebuild(path.Join(c.metadata.Repo.Owner, c.metadata.Repo.Name), c.metadata.Curr.Commit.Branch, conf.Cache)
2017-06-22 19:06:28 +00:00
step, err := c.createProcess(container, backend_types.StepTypeCache)
if err != nil {
return err
}
2017-06-22 19:06:28 +00:00
stage := new(backend_types.Stage)
2017-06-22 19:06:28 +00:00
stage.Steps = append(stage.Steps, step)
ir.Stages = append(ir.Stages, stage)
return nil
2017-06-22 19:06:28 +00:00
}