Add Agent-level Tolerations setting (#5266)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
scottshotgg 2025-08-10 03:12:42 -05:00 committed by GitHub
parent ee2804d8a5
commit d7495357d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 209 additions and 0 deletions

View file

@ -380,6 +380,24 @@ Determines if Pod annotations can be defined from a step's backend options.
---
### BACKEND_K8S_POD_TOLERATIONS
- Name: `WOODPECKER_BACKEND_K8S_POD_TOLERATIONS`
- Default: none
Additional tolerations to apply to worker Pods. Must be a YAML object, e.g. `[{"effect":"NoSchedule","key":"jobs","operator":"Exists"}]`.
---
### BACKEND_K8S_POD_TOLERATIONS_ALLOW_FROM_STEP
- Name: `WOODPECKER_BACKEND_K8S_POD_TOLERATIONS_ALLOW_FROM_STEP`
- Default: `true`
Determines if Pod tolerations can be defined from a step's backend options.
---
### BACKEND_K8S_POD_NODE_SELECTOR
- Name: `WOODPECKER_BACKEND_K8S_POD_NODE_SELECTOR`

View file

@ -73,12 +73,24 @@ var Flags = []cli.Flag{
Usage: "backend k8s Agent-wide worker pod node selector",
Value: "",
},
&cli.StringFlag{
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_TOLERATIONS"),
Name: "backend-k8s-pod-tolerations",
Usage: "backend k8s Agent-wide worker pod tolerations",
Value: "",
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS_ALLOW_FROM_STEP"),
Name: "backend-k8s-pod-annotations-allow-from-step",
Usage: "whether to allow using annotations from step's backend options",
Value: false,
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_TOLERATIONS_ALLOW_FROM_STEP"),
Name: "backend-k8s-pod-tolerations-allow-from-step",
Usage: "whether to allow using tolerations from step's backend options",
Value: true,
},
&cli.BoolFlag{
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_SECCTX_NONROOT"), // cspell:words secctx nonroot
Name: "backend-k8s-secctx-nonroot",

View file

@ -67,6 +67,8 @@ type config struct {
PodAnnotations map[string]string
PodAnnotationsAllowFromStep bool
PodNodeSelector map[string]string
PodTolerationsAllowFromStep bool
PodTolerations []Toleration
ImagePullSecretNames []string
SecurityContext SecurityContextConfig
NativeSecretsAllowFromStep bool
@ -109,6 +111,7 @@ func configFromCliContext(ctx context.Context) (*config, error) {
PodLabelsAllowFromStep: c.Bool("backend-k8s-pod-labels-allow-from-step"),
PodAnnotations: make(map[string]string), // just init empty map to prevent nil panic
PodAnnotationsAllowFromStep: c.Bool("backend-k8s-pod-annotations-allow-from-step"),
PodTolerationsAllowFromStep: c.Bool("backend-k8s-pod-tolerations-allow-from-step"),
PodNodeSelector: make(map[string]string), // just init empty map to prevent nil panic
ImagePullSecretNames: c.StringSlice("backend-k8s-pod-image-pull-secret-names"),
SecurityContext: SecurityContextConfig{
@ -136,6 +139,13 @@ func configFromCliContext(ctx context.Context) (*config, error) {
return nil, err
}
}
if podTolerations := c.String("backend-k8s-pod-tolerations"); podTolerations != "" {
if err := yaml.Unmarshal([]byte(podTolerations), &config.PodTolerations); err != nil {
log.Error().Err(err).Msgf("could not unmarshal pod tolerations '%s'", podTolerations)
return nil, err
}
}
return &config, nil
}
}

View file

@ -179,6 +179,14 @@ func podSpec(step *types.Step, config *config, options BackendOptions, nsp nativ
Tolerations: tolerations(options.Tolerations),
SecurityContext: podSecurityContext(options.SecurityContext, config.SecurityContext, step.Privileged),
}
// If there are tolerations and they are allowed
if config.PodTolerationsAllowFromStep && len(options.Tolerations) != 0 {
spec.Tolerations = tolerations(options.Tolerations)
} else {
spec.Tolerations = tolerations(config.PodTolerations)
}
spec.Volumes, err = pvcVolumes(step.Volumes)
if err != nil {
return spec, err

View file

@ -387,6 +387,7 @@ func TestFullPod(t *testing.T) {
PodLabelsAllowFromStep: true,
PodAnnotations: map[string]string{"apps.kubernetes.io/pod-index": "0"},
PodAnnotationsAllowFromStep: true,
PodTolerationsAllowFromStep: true,
PodNodeSelector: map[string]string{"topology.kubernetes.io/region": "eu-central-1"},
SecurityContext: SecurityContextConfig{RunAsNonRoot: false},
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{
@ -662,6 +663,160 @@ func TestSecrets(t *testing.T) {
ja.Assertf(string(podJSON), expected)
}
func TestPodTolerations(t *testing.T) {
const expected = `
{
"metadata": {
"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
"namespace": "woodpecker",
"creationTimestamp": null,
"labels": {
"step": "toleration-test",
"woodpecker-ci.org/step": "toleration-test"
}
},
"spec": {
"containers": [
{
"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
"image": "alpine",
"resources": {}
}
],
"restartPolicy": "Never",
"tolerations": [
{
"key": "foo",
"value": "bar",
"effect": "NoSchedule"
},
{
"key": "baz",
"value": "qux",
"effect": "NoExecute"
}
]
},
"status": {}
}`
globalTolerations := []Toleration{
{Key: "foo", Value: "bar", Effect: TaintEffectNoSchedule},
{Key: "baz", Value: "qux", Effect: TaintEffectNoExecute},
}
pod, err := mkPod(&types.Step{
Name: "toleration-test",
Image: "alpine",
UUID: "01he8bebctabr3kgk0qj36d2me-0",
}, &config{
Namespace: "woodpecker",
PodTolerations: globalTolerations,
PodTolerationsAllowFromStep: false,
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{})
assert.NoError(t, err)
podJSON, err := json.Marshal(pod)
assert.NoError(t, err)
ja := jsonassert.New(t)
ja.Assertf(string(podJSON), expected)
}
func TestPodTolerationsAllowFromStep(t *testing.T) {
const expectedDisallow = `
{
"metadata": {
"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
"namespace": "woodpecker",
"creationTimestamp": null,
"labels": {
"step": "toleration-test",
"woodpecker-ci.org/step": "toleration-test"
}
},
"spec": {
"containers": [
{
"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
"image": "alpine",
"resources": {}
}
],
"restartPolicy": "Never"
},
"status": {}
}`
const expectedAllow = `
{
"metadata": {
"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
"namespace": "woodpecker",
"creationTimestamp": null,
"labels": {
"step": "toleration-test",
"woodpecker-ci.org/step": "toleration-test"
}
},
"spec": {
"containers": [
{
"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
"image": "alpine",
"resources": {}
}
],
"restartPolicy": "Never",
"tolerations": [
{
"key": "custom",
"value": "value",
"effect": "NoSchedule"
}
]
},
"status": {}
}`
stepTolerations := []Toleration{
{Key: "custom", Value: "value", Effect: TaintEffectNoSchedule},
}
step := &types.Step{
Name: "toleration-test",
Image: "alpine",
UUID: "01he8bebctabr3kgk0qj36d2me-0",
}
pod, err := mkPod(step, &config{
Namespace: "woodpecker",
PodTolerationsAllowFromStep: false,
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{
Tolerations: stepTolerations,
})
assert.NoError(t, err)
podJSON, err := json.Marshal(pod)
assert.NoError(t, err)
ja := jsonassert.New(t)
ja.Assertf(string(podJSON), expectedDisallow)
pod, err = mkPod(step, &config{
Namespace: "woodpecker",
PodTolerationsAllowFromStep: true,
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{
Tolerations: stepTolerations,
})
assert.NoError(t, err)
podJSON, err = json.Marshal(pod)
assert.NoError(t, err)
ja = jsonassert.New(t)
ja.Assertf(string(podJSON), expectedAllow)
}
func TestStepSecret(t *testing.T) {
const expected = `{
"metadata": {

View file

@ -646,6 +646,12 @@
"type": ["boolean", "string", "number"]
}
},
"tolerations": {
"type": "object",
"additionalProperties": {
"type": ["boolean", "string", "number"]
}
},
"securityContext": {
"$ref": "#/definitions/step_backend_kubernetes_security_context"
},