diff --git a/go.mod b/go.mod index aa99569ca..0055b9cbe 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/kinbiko/jsonassert v1.1.1 github.com/lib/pq v1.10.9 github.com/mattn/go-sqlite3 v1.14.20 + github.com/mitchellh/mapstructure v1.4.2 github.com/moby/moby v24.0.9+incompatible github.com/moby/term v0.5.0 github.com/muesli/termenv v0.15.2 diff --git a/go.sum b/go.sum index 233b4031f..a442311fc 100644 --- a/go.sum +++ b/go.sum @@ -67,14 +67,10 @@ github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDror github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/cli v24.0.7+incompatible h1:wa/nIwYFW7BVTGa7SWPVyyXU9lgORqUb1xfI36MSkFg= -github.com/docker/cli v24.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v24.0.8+incompatible h1:nZJsIKYXLeYFpsskShljFQcImMXw7zt+3DN/Ay/A6SI= github.com/docker/cli v24.0.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v24.0.8+incompatible h1:lbGlhLzolo0tpp+paD0JzOYId072MQmQxZLPevQCFPU= github.com/docker/docker v24.0.8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= @@ -89,8 +85,6 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/expr-lang/expr v1.15.8 h1:FL8+d3rSSP4tmK9o+vKfSMqqpGL8n15pEPiHcnBpxoI= -github.com/expr-lang/expr v1.15.8/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= github.com/expr-lang/expr v1.16.0 h1:BQabx+PbjsL2PEQwkJ4GIn3CcuUh8flduHhJ0lHjWwE= github.com/expr-lang/expr v1.16.0/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -149,8 +143,9 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -306,8 +301,6 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= -github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.20 h1:BAZ50Ns0OFBNxdAqFhbZqdPcht1Xlb16pDCqkq1spr0= github.com/mattn/go-sqlite3 v1.14.20/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= @@ -316,10 +309,8 @@ github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= -github.com/moby/moby v24.0.7+incompatible h1:RrVT5IXBn85mRtFKP+gFwVLCcnNPZIgN3NVRJG9Le+4= -github.com/moby/moby v24.0.7+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= -github.com/moby/moby v24.0.8+incompatible h1:lTOrmnT/ZwYrhTbcmkWMTd2Pk65vV+4YuEdIG04shac= -github.com/moby/moby v24.0.8+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/moby v24.0.9+incompatible h1:Z/hFbZJqC5Fmuf6jesMLdHU71CMAgdiSJ1ZYey+bFmg= github.com/moby/moby v24.0.9+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -429,8 +420,6 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= -github.com/xanzy/go-gitlab v0.95.2 h1:4p0IirHqEp5f0baK/aQqr4TR57IsD+8e4fuyAA1yi88= -github.com/xanzy/go-gitlab v0.95.2/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xanzy/go-gitlab v0.96.0 h1:LGkZ+wSNMRtHIBaYE4Hq3dZVjprwHv3Y1+rhKU3WETs= github.com/xanzy/go-gitlab v0.96.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -604,8 +593,6 @@ google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAs google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= -google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= -google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= @@ -636,16 +623,10 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.29.0 h1:NiCdQMY1QOp1H8lfRyeEf8eOwV6+0xA6XEE44ohDX2A= -k8s.io/api v0.29.0/go.mod h1:sdVmXoz2Bo/cb77Pxi71IPTSErEW32xa4aXwKH7gfBA= k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= -k8s.io/apimachinery v0.29.0 h1:+ACVktwyicPz0oc6MTMLwa2Pw3ouLAfAon1wPLtG48o= -k8s.io/apimachinery v0.29.0/go.mod h1:eVBxQ/cwiJxH58eK/jd/vAk4mrxmVlnpBH5J2GbMeis= k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/client-go v0.29.0 h1:KmlDtFcrdUzOYrBhXHgKw5ycWzc3ryPX5mQe0SkG3y8= -k8s.io/client-go v0.29.0/go.mod h1:yLkXH4HKMAywcrD82KMSmfYg2DlE8mepPR4JGSo5n38= k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0= @@ -705,7 +686,5 @@ xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohF xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/xorm v1.3.3/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo= -xorm.io/xorm v1.3.6 h1:hfpWHkDIWWqUi8FRF2H2M9O8lO3Ov47rwFcS9gPzPkU= -xorm.io/xorm v1.3.6/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo= xorm.io/xorm v1.3.7 h1:mLceAGu0b87r9pD4qXyxGHxifOXIIrAdVcA6k95/osw= xorm.io/xorm v1.3.7/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw= diff --git a/pipeline/backend/kubernetes/backend_options.go b/pipeline/backend/kubernetes/backend_options.go new file mode 100644 index 000000000..689f835fe --- /dev/null +++ b/pipeline/backend/kubernetes/backend_options.go @@ -0,0 +1,77 @@ +package kubernetes + +import ( + "github.com/mitchellh/mapstructure" + + backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" +) + +// BackendOptions defines all the advanced options for the kubernetes backend +type BackendOptions struct { + Resources Resources `mapstructure:"resources"` + ServiceAccountName string `mapstructure:"serviceAccountName"` + NodeSelector map[string]string `mapstructure:"nodeSelector"` + Tolerations []Toleration `mapstructure:"tolerations"` + SecurityContext *SecurityContext `mapstructure:"securityContext"` +} + +// Resources defines two maps for kubernetes resource definitions +type Resources struct { + Requests map[string]string `mapstructure:"requests"` + Limits map[string]string `mapstructure:"limits"` +} + +// Toleration defines Kubernetes toleration +type Toleration struct { + Key string `mapstructure:"key"` + Operator TolerationOperator `mapstructure:"operator"` + Value string `mapstructure:"value"` + Effect TaintEffect `mapstructure:"effect"` + TolerationSeconds *int64 `mapstructure:"tolerationSeconds"` +} + +type TaintEffect string + +const ( + TaintEffectNoSchedule TaintEffect = "NoSchedule" + TaintEffectPreferNoSchedule TaintEffect = "PreferNoSchedule" + TaintEffectNoExecute TaintEffect = "NoExecute" +) + +type TolerationOperator string + +const ( + TolerationOpExists TolerationOperator = "Exists" + TolerationOpEqual TolerationOperator = "Equal" +) + +type SecurityContext struct { + Privileged *bool `mapstructure:"privileged"` + RunAsNonRoot *bool `mapstructure:"runAsNonRoot"` + RunAsUser *int64 `mapstructure:"runAsUser"` + RunAsGroup *int64 `mapstructure:"runAsGroup"` + FSGroup *int64 `mapstructure:"fsGroup"` + SeccompProfile *SecProfile `mapstructure:"seccompProfile"` + ApparmorProfile *SecProfile `mapstructure:"apparmorProfile"` +} + +type SecProfile struct { + Type SecProfileType `mapstructure:"type"` + LocalhostProfile string `mapstructure:"localhostProfile"` +} + +type SecProfileType string + +const ( + SecProfileTypeRuntimeDefault SecProfileType = "RuntimeDefault" + SecProfileTypeLocalhost SecProfileType = "Localhost" +) + +func parseBackendOptions(step *backend.Step) (BackendOptions, error) { + var result BackendOptions + if step.BackendOptions == nil { + return result, nil + } + err := mapstructure.Decode(step.BackendOptions[EngineName], &result) + return result, err +} diff --git a/pipeline/backend/kubernetes/backend_options_test.go b/pipeline/backend/kubernetes/backend_options_test.go new file mode 100644 index 000000000..f9c21a5fe --- /dev/null +++ b/pipeline/backend/kubernetes/backend_options_test.go @@ -0,0 +1,73 @@ +package kubernetes + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" +) + +func Test_parseBackendOptions(t *testing.T) { + got, err := parseBackendOptions(&backend.Step{BackendOptions: nil}) + assert.NoError(t, err) + assert.Equal(t, BackendOptions{}, got) + got, err = parseBackendOptions(&backend.Step{BackendOptions: map[string]any{}}) + assert.NoError(t, err) + assert.Equal(t, BackendOptions{}, got) + got, err = parseBackendOptions(&backend.Step{ + BackendOptions: map[string]any{ + "kubernetes": map[string]any{ + "nodeSelector": map[string]string{"storage": "ssd"}, + "serviceAccountName": "wp-svc-acc", + "tolerations": []map[string]any{ + {"key": "net-port", "value": "100Mbit", "effect": TaintEffectNoSchedule}, + }, + "resources": map[string]any{ + "requests": map[string]string{"memory": "128Mi", "cpu": "1000m"}, + "limits": map[string]string{"memory": "256Mi", "cpu": "2"}, + }, + "securityContext": map[string]any{ + "privileged": newBool(true), + "runAsNonRoot": newBool(true), + "runAsUser": newInt64(101), + "runAsGroup": newInt64(101), + "fsGroup": newInt64(101), + "seccompProfile": map[string]any{ + "type": "Localhost", + "localhostProfile": "profiles/audit.json", + }, + "apparmorProfile": map[string]any{ + "type": "Localhost", + "localhostProfile": "k8s-apparmor-example-deny-write", + }, + }, + }, + }, + }) + assert.NoError(t, err) + assert.Equal(t, BackendOptions{ + NodeSelector: map[string]string{"storage": "ssd"}, + ServiceAccountName: "wp-svc-acc", + Tolerations: []Toleration{{Key: "net-port", Value: "100Mbit", Effect: TaintEffectNoSchedule}}, + Resources: Resources{ + Requests: map[string]string{"memory": "128Mi", "cpu": "1000m"}, + Limits: map[string]string{"memory": "256Mi", "cpu": "2"}, + }, + SecurityContext: &SecurityContext{ + Privileged: newBool(true), + RunAsNonRoot: newBool(true), + RunAsUser: newInt64(101), + RunAsGroup: newInt64(101), + FSGroup: newInt64(101), + SeccompProfile: &SecProfile{ + Type: "Localhost", + LocalhostProfile: "profiles/audit.json", + }, + ApparmorProfile: &SecProfile{ + Type: "Localhost", + LocalhostProfile: "k8s-apparmor-example-deny-write", + }, + }, + }, got) +} diff --git a/pipeline/backend/kubernetes/kubernetes.go b/pipeline/backend/kubernetes/kubernetes.go index 805c2643e..299dc2595 100644 --- a/pipeline/backend/kubernetes/kubernetes.go +++ b/pipeline/backend/kubernetes/kubernetes.go @@ -172,7 +172,7 @@ func (e *kube) getConfig() *config { return &c } -// Setup the pipeline environment. +// SetupWorkflow sets up the pipeline environment. func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msgf("Setting up Kubernetes primitives") @@ -183,7 +183,7 @@ func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID s } } - extraHosts := []types.HostAlias{} + var extraHosts []types.HostAlias for _, stage := range conf.Stages { for _, step := range stage.Steps { if step.Type == types.StepTypeService { @@ -206,14 +206,19 @@ func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID s return nil } -// Start the pipeline step. +// StartStep starts the pipeline step. func (e *kube) StartStep(ctx context.Context, step *types.Step, taskUUID string) error { + options, err := parseBackendOptions(step) + if err != nil { + log.Error().Err(err).Msg("could not parse backend options") + } + log.Trace().Str("taskUUID", taskUUID).Msgf("starting step: %s", step.Name) - _, err := startPod(ctx, e, step) + _, err = startPod(ctx, e, step, options) return err } -// Wait for the pipeline step to complete and returns +// WaitStep waits for the pipeline step to complete and returns // the completion results. func (e *kube) WaitStep(ctx context.Context, step *types.Step, taskUUID string) (*types.State, error) { podName, err := stepToPodName(step) @@ -291,7 +296,7 @@ func (e *kube) WaitStep(ctx context.Context, step *types.Step, taskUUID string) return bs, nil } -// Tail the pipeline step logs. +// TailStep tails the pipeline step logs. func (e *kube) TailStep(ctx context.Context, step *types.Step, taskUUID string) (io.ReadCloser, error) { podName, err := stepToPodName(step) if err != nil { @@ -369,7 +374,7 @@ func (e *kube) DestroyStep(ctx context.Context, step *types.Step, taskUUID strin return err } -// Destroy the pipeline environment. +// DestroyWorkflow destroys the pipeline environment. func (e *kube) DestroyWorkflow(ctx context.Context, conf *types.Config, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msg("deleting Kubernetes primitives") diff --git a/pipeline/backend/kubernetes/pod.go b/pipeline/backend/kubernetes/pod.go index 85f3dcf0c..698cdba57 100644 --- a/pipeline/backend/kubernetes/pod.go +++ b/pipeline/backend/kubernetes/pod.go @@ -35,20 +35,20 @@ const ( podPrefix = "wp-" ) -func mkPod(step *types.Step, config *config, podName, goos string) (*v1.Pod, error) { +func mkPod(step *types.Step, config *config, podName, goos string, options BackendOptions) (*v1.Pod, error) { var err error - meta, err := podMeta(step, config, podName) + meta, err := podMeta(step, config, options, podName) if err != nil { return nil, err } - spec, err := podSpec(step, config) + spec, err := podSpec(step, config, options) if err != nil { return nil, err } - container, err := podContainer(step, podName, goos) + container, err := podContainer(step, podName, goos, options) if err != nil { return nil, err } @@ -73,7 +73,7 @@ func podName(step *types.Step) (string, error) { return dnsName(podPrefix + step.UUID) } -func podMeta(step *types.Step, config *config, podName string) (metav1.ObjectMeta, error) { +func podMeta(step *types.Step, config *config, options BackendOptions, podName string) (metav1.ObjectMeta, error) { var err error meta := metav1.ObjectMeta{ Name: podName, @@ -98,7 +98,7 @@ func podMeta(step *types.Step, config *config, podName string) (metav1.ObjectMet meta.Annotations = make(map[string]string) } - securityContext := step.BackendOptions.Kubernetes.SecurityContext + securityContext := options.SecurityContext if securityContext != nil { key, value := apparmorAnnotation(podName, securityContext.ApparmorProfile) if key != nil && value != nil { @@ -113,16 +113,16 @@ func stepLabel(step *types.Step) (string, error) { return toDNSName(step.Name) } -func podSpec(step *types.Step, config *config) (v1.PodSpec, error) { +func podSpec(step *types.Step, config *config, options BackendOptions) (v1.PodSpec, error) { var err error spec := v1.PodSpec{ RestartPolicy: v1.RestartPolicyNever, - ServiceAccountName: step.BackendOptions.Kubernetes.ServiceAccountName, + ServiceAccountName: options.ServiceAccountName, ImagePullSecrets: imagePullSecretsReferences(config.ImagePullSecretNames), HostAliases: hostAliases(step.ExtraHosts), - NodeSelector: nodeSelector(step.BackendOptions.Kubernetes.NodeSelector, step.Environment["CI_SYSTEM_PLATFORM"]), - Tolerations: tolerations(step.BackendOptions.Kubernetes.Tolerations), - SecurityContext: podSecurityContext(step.BackendOptions.Kubernetes.SecurityContext, config.SecurityContext), + NodeSelector: nodeSelector(options.NodeSelector, step.Environment["CI_SYSTEM_PLATFORM"]), + Tolerations: tolerations(options.Tolerations), + SecurityContext: podSecurityContext(options.SecurityContext, config.SecurityContext), } spec.Volumes, err = volumes(step.Volumes) if err != nil { @@ -132,7 +132,7 @@ func podSpec(step *types.Step, config *config) (v1.PodSpec, error) { return spec, nil } -func podContainer(step *types.Step, podName, goos string) (v1.Container, error) { +func podContainer(step *types.Step, podName, goos string, options BackendOptions) (v1.Container, error) { var err error container := v1.Container{ Name: podName, @@ -156,9 +156,9 @@ func podContainer(step *types.Step, podName, goos string) (v1.Container, error) container.Env = mapToEnvVars(step.Environment) container.Ports = containerPorts(step.Ports) - container.SecurityContext = containerSecurityContext(step.BackendOptions.Kubernetes.SecurityContext, step.Privileged) + container.SecurityContext = containerSecurityContext(options.SecurityContext, step.Privileged) - container.Resources, err = resourceRequirements(step.BackendOptions.Kubernetes.Resources) + container.Resources, err = resourceRequirements(options.Resources) if err != nil { return container, err } @@ -268,7 +268,7 @@ func imagePullSecretsReference(imagePullSecretName string) v1.LocalObjectReferen } } -func resourceRequirements(resources types.Resources) (v1.ResourceRequirements, error) { +func resourceRequirements(resources Resources) (v1.ResourceRequirements, error) { var err error requirements := v1.ResourceRequirements{} @@ -315,7 +315,7 @@ func nodeSelector(backendNodeSelector map[string]string, platform string) map[st return nodeSelector } -func tolerations(backendTolerations []types.Toleration) []v1.Toleration { +func tolerations(backendTolerations []Toleration) []v1.Toleration { var tolerations []v1.Toleration if len(backendTolerations) > 0 { @@ -329,7 +329,7 @@ func tolerations(backendTolerations []types.Toleration) []v1.Toleration { return tolerations } -func toleration(backendToleration types.Toleration) v1.Toleration { +func toleration(backendToleration Toleration) v1.Toleration { return v1.Toleration{ Key: backendToleration.Key, Operator: v1.TolerationOperator(backendToleration.Operator), @@ -339,7 +339,7 @@ func toleration(backendToleration types.Toleration) v1.Toleration { } } -func podSecurityContext(sc *types.SecurityContext, secCtxConf SecurityContextConfig) *v1.PodSecurityContext { +func podSecurityContext(sc *SecurityContext, secCtxConf SecurityContextConfig) *v1.PodSecurityContext { var ( nonRoot *bool user *int64 @@ -381,7 +381,7 @@ func podSecurityContext(sc *types.SecurityContext, secCtxConf SecurityContextCon return securityContext } -func seccompProfile(scp *types.SecProfile) *v1.SeccompProfile { +func seccompProfile(scp *SecProfile) *v1.SeccompProfile { if scp == nil || len(scp.Type) == 0 { return nil } @@ -397,7 +397,7 @@ func seccompProfile(scp *types.SecProfile) *v1.SeccompProfile { return seccompProfile } -func containerSecurityContext(sc *types.SecurityContext, stepPrivileged bool) *v1.SecurityContext { +func containerSecurityContext(sc *SecurityContext, stepPrivileged bool) *v1.SecurityContext { var privileged *bool if sc != nil && sc.Privileged != nil && *sc.Privileged { @@ -417,7 +417,7 @@ func containerSecurityContext(sc *types.SecurityContext, stepPrivileged bool) *v return securityContext } -func apparmorAnnotation(containerName string, scp *types.SecProfile) (*string, *string) { +func apparmorAnnotation(containerName string, scp *SecProfile) (*string, *string) { if scp == nil { return nil, nil } @@ -428,12 +428,12 @@ func apparmorAnnotation(containerName string, scp *types.SecProfile) (*string, * profilePath string ) - if scp.Type == types.SecProfileTypeRuntimeDefault { + if scp.Type == SecProfileTypeRuntimeDefault { profileType = "runtime" profilePath = "default" } - if scp.Type == types.SecProfileTypeLocalhost { + if scp.Type == SecProfileTypeLocalhost { profileType = "localhost" profilePath = scp.LocalhostProfile } @@ -458,13 +458,13 @@ func mapToEnvVars(m map[string]string) []v1.EnvVar { return ev } -func startPod(ctx context.Context, engine *kube, step *types.Step) (*v1.Pod, error) { +func startPod(ctx context.Context, engine *kube, step *types.Step, options BackendOptions) (*v1.Pod, error) { podName, err := stepToPodName(step) if err != nil { return nil, err } engineConfig := engine.getConfig() - pod, err := mkPod(step, engineConfig, podName, engine.goos) + pod, err := mkPod(step, engineConfig, podName, engine.goos, options) if err != nil { return nil, err } diff --git a/pipeline/backend/kubernetes/pod_test.go b/pipeline/backend/kubernetes/pod_test.go index 8b9778c98..207b050a0 100644 --- a/pipeline/backend/kubernetes/pod_test.go +++ b/pipeline/backend/kubernetes/pod_test.go @@ -139,7 +139,7 @@ func TestTinyPod(t *testing.T) { Environment: map[string]string{"CI": "woodpecker"}, }, &config{ Namespace: "woodpecker", - }, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64") + }, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{}) assert.NoError(t, err) podJSON, err := json.Marshal(pod) @@ -297,17 +297,17 @@ func TestFullPod(t *testing.T) { {Number: 2345, Protocol: "tcp"}, {Number: 3456, Protocol: "udp"}, } - secCtx := types.SecurityContext{ + secCtx := SecurityContext{ Privileged: newBool(true), RunAsNonRoot: newBool(true), RunAsUser: newInt64(101), RunAsGroup: newInt64(101), FSGroup: newInt64(101), - SeccompProfile: &types.SecProfile{ + SeccompProfile: &SecProfile{ Type: "Localhost", LocalhostProfile: "profiles/audit.json", }, - ApparmorProfile: &types.SecProfile{ + ApparmorProfile: &SecProfile{ Type: "Localhost", LocalhostProfile: "k8s-apparmor-example-deny-write", }, @@ -324,25 +324,22 @@ func TestFullPod(t *testing.T) { Environment: map[string]string{"CGO": "0"}, ExtraHosts: hostAliases, Ports: ports, - BackendOptions: types.BackendOptions{ - Kubernetes: types.KubernetesBackendOptions{ - NodeSelector: map[string]string{"storage": "ssd"}, - ServiceAccountName: "wp-svc-acc", - Tolerations: []types.Toleration{{Key: "net-port", Value: "100Mbit", Effect: types.TaintEffectNoSchedule}}, - Resources: types.Resources{ - Requests: map[string]string{"memory": "128Mi", "cpu": "1000m"}, - Limits: map[string]string{"memory": "256Mi", "cpu": "2"}, - }, - SecurityContext: &secCtx, - }, - }, }, &config{ Namespace: "woodpecker", ImagePullSecretNames: []string{"regcred", "another-pull-secret"}, PodLabels: map[string]string{"app": "test"}, PodAnnotations: map[string]string{"apps.kubernetes.io/pod-index": "0"}, SecurityContext: SecurityContextConfig{RunAsNonRoot: false}, - }, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64") + }, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{ + NodeSelector: map[string]string{"storage": "ssd"}, + ServiceAccountName: "wp-svc-acc", + Tolerations: []Toleration{{Key: "net-port", Value: "100Mbit", Effect: TaintEffectNoSchedule}}, + Resources: Resources{ + Requests: map[string]string{"memory": "128Mi", "cpu": "1000m"}, + Limits: map[string]string{"memory": "256Mi", "cpu": "2"}, + }, + SecurityContext: &secCtx, + }) assert.NoError(t, err) podJSON, err := json.Marshal(pod) diff --git a/pipeline/backend/types/backend.go b/pipeline/backend/types/backend.go index 5bb765f5a..b3ae38705 100644 --- a/pipeline/backend/types/backend.go +++ b/pipeline/backend/types/backend.go @@ -60,8 +60,3 @@ type Backend interface { type BackendInfo struct { Platform string } - -// BackendOptions defines advanced options for specific backends -type BackendOptions struct { - Kubernetes KubernetesBackendOptions `json:"kubernetes,omitempty"` -} diff --git a/pipeline/backend/types/backend_kubernetes.go b/pipeline/backend/types/backend_kubernetes.go deleted file mode 100644 index 0c1a85ec1..000000000 --- a/pipeline/backend/types/backend_kubernetes.go +++ /dev/null @@ -1,76 +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 - -// KubernetesBackendOptions defines all the advanced options for the kubernetes backend -type KubernetesBackendOptions struct { - Resources Resources `json:"resouces,omitempty"` - ServiceAccountName string `json:"serviceAccountName,omitempty"` - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - Tolerations []Toleration `json:"tolerations,omitempty"` - SecurityContext *SecurityContext `json:"securityContext,omitempty"` -} - -// Resources defines two maps for kubernetes resource definitions -type Resources struct { - Requests map[string]string `json:"requests,omitempty"` - Limits map[string]string `json:"limits,omitempty"` -} - -// Defines Kubernetes toleration -type Toleration struct { - Key string `json:"key,omitempty"` - Operator TolerationOperator `json:"operator,omitempty"` - Value string `json:"value,omitempty"` - Effect TaintEffect `json:"effect,omitempty"` - TolerationSeconds *int64 `json:"tolerationSeconds,omitempty"` -} - -type TaintEffect string - -const ( - TaintEffectNoSchedule TaintEffect = "NoSchedule" - TaintEffectPreferNoSchedule TaintEffect = "PreferNoSchedule" - TaintEffectNoExecute TaintEffect = "NoExecute" -) - -type TolerationOperator string - -const ( - TolerationOpExists TolerationOperator = "Exists" - TolerationOpEqual TolerationOperator = "Equal" -) - -type SecurityContext struct { - Privileged *bool `json:"privileged,omitempty"` - RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"` - RunAsUser *int64 `json:"runAsUser,omitempty"` - RunAsGroup *int64 `json:"runAsGroup,omitempty"` - FSGroup *int64 `json:"fsGroup,omitempty"` - SeccompProfile *SecProfile `json:"seccompProfile,omitempty"` - ApparmorProfile *SecProfile `json:"apparmorProfile,omitempty"` -} - -type SecProfile struct { - Type SecProfileType `json:"type,omitempty"` - LocalhostProfile string `json:"localhostProfile,omitempty"` -} - -type SecProfileType string - -const ( - SecProfileTypeRuntimeDefault SecProfileType = "RuntimeDefault" - SecProfileTypeLocalhost SecProfileType = "Localhost" -) diff --git a/pipeline/backend/types/step.go b/pipeline/backend/types/step.go index 3fbfeeab7..9eb61fc06 100644 --- a/pipeline/backend/types/step.go +++ b/pipeline/backend/types/step.go @@ -46,7 +46,7 @@ type Step struct { AuthConfig Auth `json:"auth_config,omitempty"` NetworkMode string `json:"network_mode,omitempty"` Ports []Port `json:"ports,omitempty"` - BackendOptions BackendOptions `json:"backend_options,omitempty"` + BackendOptions map[string]any `json:"backend_options,omitempty"` } // StepType identifies the type of step diff --git a/pipeline/frontend/yaml/compiler/compiler.go b/pipeline/frontend/yaml/compiler/compiler.go index 6eab7c471..2ba24bc24 100644 --- a/pipeline/frontend/yaml/compiler/compiler.go +++ b/pipeline/frontend/yaml/compiler/compiler.go @@ -34,7 +34,6 @@ type Registry struct { Hostname string Username string Password string - Email string Token string } diff --git a/pipeline/frontend/yaml/compiler/convert.go b/pipeline/frontend/yaml/compiler/convert.go index b68e80fe4..84acd332e 100644 --- a/pipeline/frontend/yaml/compiler/convert.go +++ b/pipeline/frontend/yaml/compiler/convert.go @@ -134,11 +134,6 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe } } - // Advanced backend settings - backendOptions := backend_types.BackendOptions{ - Kubernetes: convertKubernetesBackendOptions(&container.BackendOptions.Kubernetes), - } - memSwapLimit := int64(container.MemSwapLimit) if c.reslimit.MemSwapLimit != 0 { memSwapLimit = c.reslimit.MemSwapLimit @@ -214,7 +209,7 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe Failure: failure, NetworkMode: networkMode, Ports: ports, - BackendOptions: backendOptions, + BackendOptions: container.BackendOptions, }, nil } @@ -240,52 +235,3 @@ func convertPort(portDef string) (backend_types.Port, error) { return port, nil } - -func convertKubernetesBackendOptions(kubeOpt *yaml_types.KubernetesBackendOptions) backend_types.KubernetesBackendOptions { - resources := backend_types.Resources{ - Limits: kubeOpt.Resources.Limits, - Requests: kubeOpt.Resources.Requests, - } - - var tolerations []backend_types.Toleration - for _, t := range kubeOpt.Tolerations { - tolerations = append(tolerations, backend_types.Toleration{ - Key: t.Key, - Operator: backend_types.TolerationOperator(t.Operator), - Value: t.Value, - Effect: backend_types.TaintEffect(t.Effect), - TolerationSeconds: t.TolerationSeconds, - }) - } - - var securityContext *backend_types.SecurityContext - if kubeOpt.SecurityContext != nil { - securityContext = &backend_types.SecurityContext{ - Privileged: kubeOpt.SecurityContext.Privileged, - RunAsNonRoot: kubeOpt.SecurityContext.RunAsNonRoot, - RunAsUser: kubeOpt.SecurityContext.RunAsUser, - RunAsGroup: kubeOpt.SecurityContext.RunAsGroup, - FSGroup: kubeOpt.SecurityContext.FSGroup, - } - if kubeOpt.SecurityContext.SeccompProfile != nil { - securityContext.SeccompProfile = &backend_types.SecProfile{ - Type: backend_types.SecProfileType(kubeOpt.SecurityContext.SeccompProfile.Type), - LocalhostProfile: kubeOpt.SecurityContext.SeccompProfile.LocalhostProfile, - } - } - if kubeOpt.SecurityContext.ApparmorProfile != nil { - securityContext.ApparmorProfile = &backend_types.SecProfile{ - Type: backend_types.SecProfileType(kubeOpt.SecurityContext.ApparmorProfile.Type), - LocalhostProfile: kubeOpt.SecurityContext.ApparmorProfile.LocalhostProfile, - } - } - } - - return backend_types.KubernetesBackendOptions{ - Resources: resources, - ServiceAccountName: kubeOpt.ServiceAccountName, - NodeSelector: kubeOpt.NodeSelector, - Tolerations: tolerations, - SecurityContext: securityContext, - } -} diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-custom-backend.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-custom-backend.yaml new file mode 100644 index 000000000..de6b09b9f --- /dev/null +++ b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-custom-backend.yaml @@ -0,0 +1,10 @@ +steps: + build: + image: golang + commands: + - go build + - go test + backend_options: + custom_backend: + option1: xyz + option2: [1, 2, 3] diff --git a/pipeline/frontend/yaml/linter/schema/schema_test.go b/pipeline/frontend/yaml/linter/schema/schema_test.go index 29bc5a4c4..d423a9e3d 100644 --- a/pipeline/frontend/yaml/linter/schema/schema_test.go +++ b/pipeline/frontend/yaml/linter/schema/schema_test.go @@ -111,6 +111,11 @@ func TestSchema(t *testing.T) { testFile: ".woodpecker/test-dag.yaml", fail: false, }, + { + name: "Custom backend", + testFile: ".woodpecker/test-custom-backend.yaml", + fail: false, + }, } for _, tt := range testTable { diff --git a/pipeline/frontend/yaml/types/backend_options.go b/pipeline/frontend/yaml/types/backend_options.go deleted file mode 100644 index 5c1a1b7a9..000000000 --- a/pipeline/frontend/yaml/types/backend_options.go +++ /dev/null @@ -1,71 +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 - -// BackendOptions are advanced options for specific backends -type BackendOptions struct { - Kubernetes KubernetesBackendOptions `yaml:"kubernetes,omitempty"` -} - -type KubernetesBackendOptions struct { - Resources Resources `yaml:"resources,omitempty"` - ServiceAccountName string `yaml:"serviceAccountName,omitempty"` - NodeSelector map[string]string `yaml:"nodeSelector,omitempty"` - Tolerations []Toleration `yaml:"tolerations,omitempty"` - SecurityContext *SecurityContext `yaml:"securityContext,omitempty"` -} - -type Resources struct { - Requests map[string]string `yaml:"requests,omitempty"` - Limits map[string]string `yaml:"limits,omitempty"` -} - -type Toleration struct { - Key string `yaml:"key,omitempty"` - Operator TolerationOperator `yaml:"operator,omitempty"` - Value string `yaml:"value,omitempty"` - Effect TaintEffect `yaml:"effect,omitempty"` - TolerationSeconds *int64 `yaml:"tolerationSeconds,omitempty"` -} - -type TaintEffect string - -const ( - TaintEffectNoSchedule TaintEffect = "NoSchedule" - TaintEffectPreferNoSchedule TaintEffect = "PreferNoSchedule" - TaintEffectNoExecute TaintEffect = "NoExecute" -) - -type TolerationOperator string - -const ( - TolerationOpExists TolerationOperator = "Exists" - TolerationOpEqual TolerationOperator = "Equal" -) - -type SecurityContext struct { - Privileged *bool `yaml:"privileged,omitempty"` - RunAsNonRoot *bool `yaml:"runAsNonRoot,omitempty"` - RunAsUser *int64 `yaml:"runAsUser,omitempty"` - RunAsGroup *int64 `yaml:"runAsGroup,omitempty"` - FSGroup *int64 `yaml:"fsGroup,omitempty"` - SeccompProfile *SecProfile `yaml:"seccompProfile,omitempty"` - ApparmorProfile *SecProfile `yaml:"apparmorProfile,omitempty"` -} - -type SecProfile struct { - Type string `yaml:"type,omitempty"` - LocalhostProfile string `yaml:"localhostProfile,omitempty"` -} diff --git a/pipeline/frontend/yaml/types/container.go b/pipeline/frontend/yaml/types/container.go index 810cd88c8..eabeee422 100644 --- a/pipeline/frontend/yaml/types/container.go +++ b/pipeline/frontend/yaml/types/container.go @@ -33,7 +33,7 @@ type ( // Container defines a container. Container struct { - BackendOptions BackendOptions `yaml:"backend_options,omitempty"` + BackendOptions map[string]any `yaml:"backend_options,omitempty"` Commands base.StringOrSlice `yaml:"commands,omitempty"` Entrypoint base.StringOrSlice `yaml:"entrypoint,omitempty"` Detached bool `yaml:"detach,omitempty"` diff --git a/server/store/datastore/migration/testfiles/tmp_4282431288 b/server/store/datastore/migration/testfiles/tmp_4282431288 new file mode 100644 index 000000000..d81b2c85d Binary files /dev/null and b/server/store/datastore/migration/testfiles/tmp_4282431288 differ