woodpecker/pipeline/backend/kubernetes/pod.go
runephilosof-karnovgroup adb2c82790
Update go module path for major version 2 (#2905)
https://go.dev/doc/modules/release-workflow#breaking

Fixes https://github.com/woodpecker-ci/woodpecker/issues/2913 fixes
#2654
```
runephilosof@fedora:~/code/platform-woodpecker/woodpecker-repo-configurator (master)$ go get go.woodpecker-ci.org/woodpecker@v2.0.0
go: go.woodpecker-ci.org/woodpecker@v2.0.0: invalid version: module contains a go.mod file, so module path must match major version ("go.woodpecker-ci.org/woodpecker/v2")
```

---------

Co-authored-by: qwerty287 <80460567+qwerty287@users.noreply.github.com>
2023-12-08 08:15:08 +01:00

253 lines
6.9 KiB
Go

// Copyright 2022 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 kubernetes
import (
"fmt"
"maps"
"strings"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/common"
"go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
)
func Pod(namespace string, step *types.Step, labels, annotations map[string]string, goos string, secCtxConf SecurityContextConfig) (*v1.Pod, error) {
var (
vols []v1.Volume
volMounts []v1.VolumeMount
entrypoint []string
args []string
)
if step.WorkingDir != "" {
for _, vol := range step.Volumes {
volumeName, err := dnsName(strings.Split(vol, ":")[0])
if err != nil {
return nil, err
}
vols = append(vols, v1.Volume{
Name: volumeName,
VolumeSource: v1.VolumeSource{
PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
ClaimName: volumeName,
ReadOnly: false,
},
},
})
volMounts = append(volMounts, v1.VolumeMount{
Name: volumeName,
MountPath: volumeMountPath(vol),
})
}
}
var pullPolicy v1.PullPolicy
if step.Pull {
pullPolicy = v1.PullAlways
}
if len(step.Commands) != 0 {
scriptEnv, entry, cmds := common.GenerateContainerConf(step.Commands, goos)
for k, v := range scriptEnv {
step.Environment[k] = v
}
entrypoint = entry
args = cmds
}
hostAliases := []v1.HostAlias{}
for _, extraHost := range step.ExtraHosts {
host := strings.Split(extraHost, ":")
hostAliases = append(hostAliases, v1.HostAlias{IP: host[1], Hostnames: []string{host[0]}})
}
resourceRequirements := v1.ResourceRequirements{Requests: v1.ResourceList{}, Limits: v1.ResourceList{}}
var err error
for key, val := range step.BackendOptions.Kubernetes.Resources.Requests {
resourceKey := v1.ResourceName(key)
resourceRequirements.Requests[resourceKey], err = resource.ParseQuantity(val)
if err != nil {
return nil, fmt.Errorf("resource request '%v' quantity '%v': %w", key, val, err)
}
}
for key, val := range step.BackendOptions.Kubernetes.Resources.Limits {
resourceKey := v1.ResourceName(key)
resourceRequirements.Limits[resourceKey], err = resource.ParseQuantity(val)
if err != nil {
return nil, fmt.Errorf("resource limit '%v' quantity '%v': %w", key, val, err)
}
}
var serviceAccountName string
if step.BackendOptions.Kubernetes.ServiceAccountName != "" {
serviceAccountName = step.BackendOptions.Kubernetes.ServiceAccountName
}
podName, err := dnsName(step.Name)
if err != nil {
return nil, err
}
labels["step"] = podName
var nodeSelector map[string]string
platform, exist := step.Environment["CI_SYSTEM_PLATFORM"]
if exist && platform != "" {
arch := strings.Split(platform, "/")[1]
nodeSelector = map[string]string{v1.LabelArchStable: arch}
log.Trace().Msgf("Using the node selector from the Agent's platform: %v", nodeSelector)
}
beOptNodeSelector := step.BackendOptions.Kubernetes.NodeSelector
if len(beOptNodeSelector) > 0 {
if len(nodeSelector) == 0 {
nodeSelector = beOptNodeSelector
} else {
log.Trace().Msgf("Appending labels to the node selector from the backend options: %v", beOptNodeSelector)
maps.Copy(nodeSelector, beOptNodeSelector)
}
}
var tolerations []v1.Toleration
beTolerations := step.BackendOptions.Kubernetes.Tolerations
if len(beTolerations) > 0 {
for _, t := range step.BackendOptions.Kubernetes.Tolerations {
toleration := v1.Toleration{
Key: t.Key,
Operator: v1.TolerationOperator(t.Operator),
Value: t.Value,
Effect: v1.TaintEffect(t.Effect),
TolerationSeconds: t.TolerationSeconds,
}
tolerations = append(tolerations, toleration)
}
log.Trace().Msgf("Tolerations that will be used in the backend options: %v", beTolerations)
}
beSecurityContext := step.BackendOptions.Kubernetes.SecurityContext
log.Trace().Interface("Security context", beSecurityContext).Msg("Security context that will be used for pods/containers")
podSecCtx := podSecurityContext(beSecurityContext, secCtxConf)
containerSecCtx := containerSecurityContext(beSecurityContext, step.Privileged)
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: namespace,
Labels: labels,
Annotations: annotations,
},
Spec: v1.PodSpec{
RestartPolicy: v1.RestartPolicyNever,
HostAliases: hostAliases,
NodeSelector: nodeSelector,
Tolerations: tolerations,
ServiceAccountName: serviceAccountName,
SecurityContext: podSecCtx,
Containers: []v1.Container{{
Name: podName,
Image: step.Image,
ImagePullPolicy: pullPolicy,
Command: entrypoint,
Args: args,
WorkingDir: step.WorkingDir,
Env: mapToEnvVars(step.Environment),
VolumeMounts: volMounts,
Resources: resourceRequirements,
SecurityContext: containerSecCtx,
}},
ImagePullSecrets: []v1.LocalObjectReference{{Name: "regcred"}},
Volumes: vols,
},
}
return pod, nil
}
func mapToEnvVars(m map[string]string) []v1.EnvVar {
var ev []v1.EnvVar
for k, v := range m {
ev = append(ev, v1.EnvVar{
Name: k,
Value: v,
})
}
return ev
}
func volumeMountPath(i string) string {
s := strings.Split(i, ":")
if len(s) > 1 {
return s[1]
}
return s[0]
}
func podSecurityContext(sc *types.SecurityContext, secCtxConf SecurityContextConfig) *v1.PodSecurityContext {
var (
nonRoot *bool
user *int64
group *int64
fsGroup *int64
)
if sc != nil && sc.RunAsNonRoot != nil {
if *sc.RunAsNonRoot {
nonRoot = sc.RunAsNonRoot // true
}
} else if secCtxConf.RunAsNonRoot {
nonRoot = &secCtxConf.RunAsNonRoot // true
}
if sc != nil {
user = sc.RunAsUser
group = sc.RunAsGroup
fsGroup = sc.FSGroup
}
if nonRoot == nil && user == nil && group == nil && fsGroup == nil {
return nil
}
return &v1.PodSecurityContext{
RunAsNonRoot: nonRoot,
RunAsUser: user,
RunAsGroup: group,
FSGroup: fsGroup,
}
}
func containerSecurityContext(sc *types.SecurityContext, stepPrivileged bool) *v1.SecurityContext {
var privileged *bool
if sc != nil && sc.Privileged != nil && *sc.Privileged {
privileged = sc.Privileged // true
} else if stepPrivileged {
privileged = &stepPrivileged // true
}
if privileged == nil {
return nil
}
return &v1.SecurityContext{
Privileged: privileged,
}
}