mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-09-02 04:03:48 +00:00
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:
parent
ee2804d8a5
commit
d7495357d5
6 changed files with 209 additions and 0 deletions
|
@ -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
|
### BACKEND_K8S_POD_NODE_SELECTOR
|
||||||
|
|
||||||
- Name: `WOODPECKER_BACKEND_K8S_POD_NODE_SELECTOR`
|
- Name: `WOODPECKER_BACKEND_K8S_POD_NODE_SELECTOR`
|
||||||
|
|
|
@ -73,12 +73,24 @@ var Flags = []cli.Flag{
|
||||||
Usage: "backend k8s Agent-wide worker pod node selector",
|
Usage: "backend k8s Agent-wide worker pod node selector",
|
||||||
Value: "",
|
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{
|
&cli.BoolFlag{
|
||||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS_ALLOW_FROM_STEP"),
|
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS_ALLOW_FROM_STEP"),
|
||||||
Name: "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",
|
Usage: "whether to allow using annotations from step's backend options",
|
||||||
Value: false,
|
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{
|
&cli.BoolFlag{
|
||||||
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_SECCTX_NONROOT"), // cspell:words secctx nonroot
|
Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_SECCTX_NONROOT"), // cspell:words secctx nonroot
|
||||||
Name: "backend-k8s-secctx-nonroot",
|
Name: "backend-k8s-secctx-nonroot",
|
||||||
|
|
|
@ -67,6 +67,8 @@ type config struct {
|
||||||
PodAnnotations map[string]string
|
PodAnnotations map[string]string
|
||||||
PodAnnotationsAllowFromStep bool
|
PodAnnotationsAllowFromStep bool
|
||||||
PodNodeSelector map[string]string
|
PodNodeSelector map[string]string
|
||||||
|
PodTolerationsAllowFromStep bool
|
||||||
|
PodTolerations []Toleration
|
||||||
ImagePullSecretNames []string
|
ImagePullSecretNames []string
|
||||||
SecurityContext SecurityContextConfig
|
SecurityContext SecurityContextConfig
|
||||||
NativeSecretsAllowFromStep bool
|
NativeSecretsAllowFromStep bool
|
||||||
|
@ -109,6 +111,7 @@ func configFromCliContext(ctx context.Context) (*config, error) {
|
||||||
PodLabelsAllowFromStep: c.Bool("backend-k8s-pod-labels-allow-from-step"),
|
PodLabelsAllowFromStep: c.Bool("backend-k8s-pod-labels-allow-from-step"),
|
||||||
PodAnnotations: make(map[string]string), // just init empty map to prevent nil panic
|
PodAnnotations: make(map[string]string), // just init empty map to prevent nil panic
|
||||||
PodAnnotationsAllowFromStep: c.Bool("backend-k8s-pod-annotations-allow-from-step"),
|
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
|
PodNodeSelector: make(map[string]string), // just init empty map to prevent nil panic
|
||||||
ImagePullSecretNames: c.StringSlice("backend-k8s-pod-image-pull-secret-names"),
|
ImagePullSecretNames: c.StringSlice("backend-k8s-pod-image-pull-secret-names"),
|
||||||
SecurityContext: SecurityContextConfig{
|
SecurityContext: SecurityContextConfig{
|
||||||
|
@ -136,6 +139,13 @@ func configFromCliContext(ctx context.Context) (*config, error) {
|
||||||
return nil, err
|
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
|
return &config, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,6 +179,14 @@ func podSpec(step *types.Step, config *config, options BackendOptions, nsp nativ
|
||||||
Tolerations: tolerations(options.Tolerations),
|
Tolerations: tolerations(options.Tolerations),
|
||||||
SecurityContext: podSecurityContext(options.SecurityContext, config.SecurityContext, step.Privileged),
|
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)
|
spec.Volumes, err = pvcVolumes(step.Volumes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return spec, err
|
return spec, err
|
||||||
|
|
|
@ -387,6 +387,7 @@ func TestFullPod(t *testing.T) {
|
||||||
PodLabelsAllowFromStep: true,
|
PodLabelsAllowFromStep: true,
|
||||||
PodAnnotations: map[string]string{"apps.kubernetes.io/pod-index": "0"},
|
PodAnnotations: map[string]string{"apps.kubernetes.io/pod-index": "0"},
|
||||||
PodAnnotationsAllowFromStep: true,
|
PodAnnotationsAllowFromStep: true,
|
||||||
|
PodTolerationsAllowFromStep: true,
|
||||||
PodNodeSelector: map[string]string{"topology.kubernetes.io/region": "eu-central-1"},
|
PodNodeSelector: map[string]string{"topology.kubernetes.io/region": "eu-central-1"},
|
||||||
SecurityContext: SecurityContextConfig{RunAsNonRoot: false},
|
SecurityContext: SecurityContextConfig{RunAsNonRoot: false},
|
||||||
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{
|
}, "wp-01he8bebctabr3kgk0qj36d2me-0", "linux/amd64", BackendOptions{
|
||||||
|
@ -662,6 +663,160 @@ func TestSecrets(t *testing.T) {
|
||||||
ja.Assertf(string(podJSON), expected)
|
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) {
|
func TestStepSecret(t *testing.T) {
|
||||||
const expected = `{
|
const expected = `{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|
|
@ -646,6 +646,12 @@
|
||||||
"type": ["boolean", "string", "number"]
|
"type": ["boolean", "string", "number"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"tolerations": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": ["boolean", "string", "number"]
|
||||||
|
}
|
||||||
|
},
|
||||||
"securityContext": {
|
"securityContext": {
|
||||||
"$ref": "#/definitions/step_backend_kubernetes_security_context"
|
"$ref": "#/definitions/step_backend_kubernetes_security_context"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue