2023-08-10 09:06:00 +00: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 07:56:08 +00:00
package compiler
import (
"fmt"
2024-07-18 20:52:22 +00:00
"path"
2017-03-05 07:56:08 +00:00
2023-12-08 07:15: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
)
2022-01-17 13:43:30 +00:00
const (
2022-04-06 13:30:49 +00:00
defaultCloneName = "clone"
2022-01-17 13:43:30 +00:00
)
2024-05-13 20:58:21 +00:00
// Registry represents registry credentials.
2017-04-06 16:04:25 +00:00
type Registry struct {
Hostname string
Username string
Password string
}
2017-04-10 10:39:50 +00:00
type Secret struct {
2023-10-24 18:38:47 +00:00
Name string
Value string
AllowedPlugins [ ] string
2024-01-27 19:59:44 +00:00
Events [ ] string
2022-10-27 02:21:07 +00:00
}
2024-01-27 19:59:44 +00:00
func ( s * Secret ) Available ( event string , container * yaml_types . Container ) error {
onlyAllowSecretForPlugins := len ( s . AllowedPlugins ) > 0
if onlyAllowSecretForPlugins && ! container . IsPlugin ( ) {
2024-09-26 12:04:07 +00: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 19:59:44 +00:00
}
2017-04-10 10:39:50 +00:00
2024-08-31 11:46:50 +00:00
if onlyAllowSecretForPlugins && ! utils . MatchImageDynamic ( container . Image , s . AllowedPlugins ... ) {
2024-01-27 19:59:44 +00: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 )
}
2022-10-03 17:25:43 +00:00
2024-01-27 19:59:44 +00: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 string ) bool {
// if there is no filter set secret matches all webhook events
if len ( s . Events ) == 0 {
return true
}
2024-03-29 08:48:28 +00:00
// treat all pull events the same way
2024-01-27 19:59:44 +00:00
if event == "pull_request_closed" {
event = "pull_request"
2022-10-03 17:25:43 +00:00
}
2024-01-27 19:59:44 +00:00
// one match is enough
for _ , e := range s . Events {
if e == event {
return true
}
}
// a filter is set but the webhook did not match it
return false
2022-10-03 17:25:43 +00:00
}
2024-05-13 20:58:21 +00:00
// Compiler compiles the yaml.
2017-03-05 07:56:08 +00:00
type Compiler struct {
2024-11-01 20:37:31 +00: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
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 {
2024-09-01 18:41:10 +00:00
env : map [ string ] string { } ,
cloneEnv : map [ string ] string { } ,
secrets : map [ string ] Secret { } ,
defaultClonePlugin : constant . DefaultClonePlugin ,
trustedClonePlugins : constant . TrustedClonePlugins ,
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.
2023-06-06 07:14:21 +00:00
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
2023-07-02 21:45:22 +00:00
if match , err := conf . When . Match ( c . metadata , true , c . env ) ; ! match && err == nil {
2022-09-26 07:27:20 +00:00
// 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-05 23:49:23 +00:00
return config , nil
} else if err != nil {
return nil , err
2022-09-26 07:27:20 +00:00
}
2017-03-05 07:56:08 +00:00
// create a default volume
2023-06-06 07:14:21 +00:00
config . Volumes = append ( config . Volumes , & backend_types . Volume {
2023-07-09 19:03:19 +00:00
Name : fmt . Sprintf ( "%s_default" , c . prefix ) ,
2017-03-05 07:56:08 +00:00
} )
// create a default network
2023-07-09 19:03:19 +00:00
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 {
2023-06-06 07:14:21 +00:00
config . Secrets = append ( config . Secrets , & backend_types . Secret {
2020-11-19 07:42:18 +00:00
Name : sec . Name ,
Value : sec . Value ,
} )
}
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 {
2024-07-18 20:52:22 +00:00
c . workspaceBase = path . Clean ( conf . Workspace . Base )
2017-03-05 07:56:08 +00:00
}
if len ( conf . Workspace . Path ) != 0 {
2024-07-18 20:52:22 +00:00
c . workspacePath = path . Clean ( conf . Workspace . Path )
2017-03-05 07:56:08 +00:00
}
// add default clone step
2024-09-01 18:41:10 +00:00
if ! c . local && len ( conf . Clone . ContainerList ) == 0 && ! conf . SkipClone && len ( c . defaultClonePlugin ) != 0 {
2023-11-12 17:23:48 +00:00
cloneSettings := map [ string ] any { "depth" : "0" }
2023-06-04 22:15:07 +00:00
if c . metadata . Curr . Event == metadata . EventTag {
2022-08-15 09:52:30 +00:00
cloneSettings [ "tags" ] = "true"
}
2023-06-06 07:14:21 +00:00
container := & yaml_types . Container {
2024-02-23 15:32:06 +00:00
Name : defaultCloneName ,
2024-09-01 18:41:10 +00:00
Image : c . defaultClonePlugin ,
2024-02-23 15:32:06 +00:00
Settings : cloneSettings ,
2024-02-23 16:40:52 +00:00
Environment : make ( map [ string ] any ) ,
2024-02-22 17:25:57 +00:00
}
for k , v := range c . cloneEnv {
container . Environment [ k ] = v
2017-07-19 00:53:10 +00:00
}
2024-01-09 14:22:59 +00:00
step , err := c . createProcess ( container , backend_types . StepTypeClone )
2023-11-05 11:47:42 +00:00
if err != nil {
return nil , err
}
2017-03-05 07:56:08 +00:00
2023-06-06 07:14:21 +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 )
2021-10-16 00:54:28 +00:00
} else if ! c . local && ! conf . SkipClone {
2024-01-09 14:22:59 +00:00
for _ , container := range conf . Clone . ContainerList {
2023-07-02 21:45:22 +00:00
if match , err := container . When . Match ( c . metadata , false , c . env ) ; ! match && err == nil {
2017-03-05 07:56:08 +00:00
continue
2022-10-05 23:49:23 +00:00
} else if err != nil {
return nil , err
2017-03-05 07:56:08 +00:00
}
2022-10-05 23:49:23 +00:00
2023-06-06 07:14:21 +00:00
stage := new ( backend_types . Stage )
2017-03-05 07:56:08 +00:00
2024-01-09 14:22:59 +00:00
step , err := c . createProcess ( container , backend_types . StepTypeClone )
2023-11-05 11:47:42 +00:00
if err != nil {
return nil , err
}
2023-03-20 20:17:49 +00:00
// only inject netrc if it's a trusted repo or a trusted plugin
2024-11-01 20:37:31 +00:00
if ! c . netrcOnlyTrusted || c . securityTrustedPipeline || ( container . IsPlugin ( ) && container . IsTrustedCloneImage ( c . trustedClonePlugins ) ) {
2023-03-20 20:17:49 +00:00
for k , v := range c . cloneEnv {
step . Environment [ k ] = v
}
2021-11-25 20:30:03 +00:00
}
2023-03-20 20:17:49 +00:00
2017-03-05 07:56:08 +00:00
stage . Steps = append ( stage . Steps , step )
config . Stages = append ( config . Stages , stage )
}
}
// add services steps
2023-06-06 07:14:21 +00:00
if len ( conf . Services . ContainerList ) != 0 {
stage := new ( backend_types . Stage )
2017-03-05 07:56:08 +00:00
2024-01-09 14:22:59 +00:00
for _ , container := range conf . Services . ContainerList {
2023-07-02 21:45:22 +00:00
if match , err := container . When . Match ( c . metadata , false , c . env ) ; ! match && err == nil {
2017-05-14 17:28:17 +00:00
continue
2022-10-05 23:49:23 +00:00
} else if err != nil {
return nil , err
2017-05-14 17:28:17 +00:00
}
2024-01-09 14:22:59 +00:00
step , err := c . createProcess ( container , backend_types . StepTypeService )
2023-11-05 11:47:42 +00:00
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 )
}
2023-12-24 11:14:30 +00:00
// 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
2022-08-14 17:32:49 +00:00
if c . local && ! container . When . IsLocal ( ) {
2017-05-06 01:15:47 +00:00
continue
}
2023-07-02 21:45:22 +00:00
if match , err := container . When . Match ( c . metadata , false , c . env ) ; ! match && err == nil {
2017-03-05 07:56:08 +00:00
continue
2022-10-05 23:49:23 +00:00
} else if err != nil {
return nil , err
2017-03-05 07:56:08 +00:00
}
2023-07-11 13:53:05 +00:00
stepType := backend_types . StepTypeCommands
if container . IsPlugin ( ) {
stepType = backend_types . StepTypePlugin
}
2024-01-09 14:22:59 +00:00
step , err := c . createProcess ( container , stepType )
2023-11-05 11:47:42 +00:00
if err != nil {
return nil , err
}
2023-08-08 10:49:29 +00:00
// inject netrc if it's a trusted repo or a trusted clone-plugin
2024-11-01 20:37:31 +00:00
if c . securityTrustedPipeline || ( container . IsPlugin ( ) && container . IsTrustedCloneImage ( c . trustedClonePlugins ) ) {
2023-08-08 10:49:29 +00:00
for k , v := range c . cloneEnv {
step . Environment [ k ] = v
}
}
2023-12-24 11:14:30 +00:00
steps = append ( steps , & dagCompilerStep {
step : step ,
position : pos ,
name : container . Name ,
dependsOn : container . DependsOn ,
} )
}
// generate stages out of steps
2024-01-09 14:22:59 +00:00
stepStages , err := newDAGCompiler ( steps ) . compile ( )
2023-12-24 11:14:30 +00:00
if err != nil {
return nil , err
2017-03-05 07:56:08 +00:00
}
2023-12-24 11:14:30 +00:00
config . Stages = append ( config . Stages , stepStages ... )
2022-10-05 23:49:23 +00:00
return config , nil
2017-03-05 07:56:08 +00:00
}