mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 20:01:02 +00:00
adb2c82790
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>
253 lines
6.9 KiB
Go
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,
|
|
}
|
|
}
|