mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-03 14:18:42 +00:00
parent
59d824ebf8
commit
10f2e209d6
7 changed files with 111 additions and 26 deletions
|
@ -18,8 +18,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -155,6 +157,17 @@ func (e *kube) Load(ctx context.Context) (*types.BackendInfo, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *kube) getConfig() *config {
|
||||||
|
if e.config == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
c := *e.config
|
||||||
|
c.PodLabels = maps.Clone(e.config.PodLabels)
|
||||||
|
c.PodAnnotations = maps.Clone(e.config.PodLabels)
|
||||||
|
c.ImagePullSecretNames = slices.Clone(e.config.ImagePullSecretNames)
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
// Setup the pipeline environment.
|
// Setup the pipeline environment.
|
||||||
func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID string) error {
|
func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID string) error {
|
||||||
log.Trace().Str("taskUUID", taskUUID).Msgf("Setting up Kubernetes primitives")
|
log.Trace().Str("taskUUID", taskUUID).Msgf("Setting up Kubernetes primitives")
|
||||||
|
|
53
pipeline/backend/kubernetes/kubernetes_test.go
Normal file
53
pipeline/backend/kubernetes/kubernetes_test.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2024 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGettingConfig(t *testing.T) {
|
||||||
|
engine := kube{
|
||||||
|
config: &config{
|
||||||
|
Namespace: "default",
|
||||||
|
StorageClass: "hdd",
|
||||||
|
VolumeSize: "1G",
|
||||||
|
StorageRwx: false,
|
||||||
|
PodLabels: map[string]string{"l1": "v1"},
|
||||||
|
PodAnnotations: map[string]string{"a1": "v1"},
|
||||||
|
ImagePullSecretNames: []string{"regcred"},
|
||||||
|
SecurityContext: SecurityContextConfig{RunAsNonRoot: false},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config := engine.getConfig()
|
||||||
|
config.Namespace = "wp"
|
||||||
|
config.StorageClass = "ssd"
|
||||||
|
config.StorageRwx = true
|
||||||
|
config.PodLabels = nil
|
||||||
|
config.PodAnnotations["a2"] = "v2"
|
||||||
|
config.ImagePullSecretNames = append(config.ImagePullSecretNames, "docker.io")
|
||||||
|
config.SecurityContext.RunAsNonRoot = true
|
||||||
|
|
||||||
|
assert.Equal(t, "default", engine.config.Namespace)
|
||||||
|
assert.Equal(t, "hdd", engine.config.StorageClass)
|
||||||
|
assert.Equal(t, "1G", engine.config.VolumeSize)
|
||||||
|
assert.Equal(t, false, engine.config.StorageRwx)
|
||||||
|
assert.Equal(t, 1, len(engine.config.PodLabels))
|
||||||
|
assert.Equal(t, 1, len(engine.config.PodAnnotations))
|
||||||
|
assert.Equal(t, 1, len(engine.config.ImagePullSecretNames))
|
||||||
|
assert.Equal(t, false, engine.config.SecurityContext.RunAsNonRoot)
|
||||||
|
}
|
|
@ -74,15 +74,16 @@ func podMeta(step *types.Step, config *config, podName string) metav1.ObjectMeta
|
||||||
Namespace: config.Namespace,
|
Namespace: config.Namespace,
|
||||||
}
|
}
|
||||||
|
|
||||||
labels := make(map[string]string, len(config.PodLabels)+1)
|
meta.Labels = config.PodLabels
|
||||||
// copy to not alter the engine config
|
if meta.Labels == nil {
|
||||||
maps.Copy(labels, config.PodLabels)
|
meta.Labels = make(map[string]string, 1)
|
||||||
labels[StepLabel] = step.Name
|
}
|
||||||
meta.Labels = labels
|
meta.Labels[StepLabel] = step.Name
|
||||||
|
|
||||||
// copy to not alter the engine config
|
meta.Annotations = config.PodAnnotations
|
||||||
meta.Annotations = make(map[string]string, len(config.PodAnnotations))
|
if meta.Annotations == nil {
|
||||||
maps.Copy(meta.Annotations, config.PodAnnotations)
|
meta.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
securityContext := step.BackendOptions.Kubernetes.SecurityContext
|
securityContext := step.BackendOptions.Kubernetes.SecurityContext
|
||||||
if securityContext != nil {
|
if securityContext != nil {
|
||||||
|
@ -442,13 +443,14 @@ func startPod(ctx context.Context, engine *kube, step *types.Step) (*v1.Pod, err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pod, err := mkPod(step, engine.config, podName, engine.goos)
|
engineConfig := engine.getConfig()
|
||||||
|
pod, err := mkPod(step, engineConfig, podName, engine.goos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace().Msgf("creating pod: %s", pod.Name)
|
log.Trace().Msgf("creating pod: %s", pod.Name)
|
||||||
return engine.client.CoreV1().Pods(engine.config.Namespace).Create(ctx, pod, metav1.CreateOptions{})
|
return engine.client.CoreV1().Pods(engineConfig.Namespace).Create(ctx, pod, metav1.CreateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopPod(ctx context.Context, engine *kube, step *types.Step, deleteOpts metav1.DeleteOptions) error {
|
func stopPod(ctx context.Context, engine *kube, step *types.Step, deleteOpts metav1.DeleteOptions) error {
|
||||||
|
|
|
@ -32,7 +32,7 @@ const (
|
||||||
ServiceLabel = "service"
|
ServiceLabel = "service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mkService(step *types.Step, namespace string) (*v1.Service, error) {
|
func mkService(step *types.Step, config *config) (*v1.Service, error) {
|
||||||
name, err := serviceName(step)
|
name, err := serviceName(step)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -51,7 +51,7 @@ func mkService(step *types.Step, namespace string) (*v1.Service, error) {
|
||||||
return &v1.Service{
|
return &v1.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: name,
|
Name: name,
|
||||||
Namespace: namespace,
|
Namespace: config.Namespace,
|
||||||
},
|
},
|
||||||
Spec: v1.ServiceSpec{
|
Spec: v1.ServiceSpec{
|
||||||
Type: v1.ServiceTypeClusterIP,
|
Type: v1.ServiceTypeClusterIP,
|
||||||
|
@ -77,13 +77,14 @@ func servicePort(port types.Port) v1.ServicePort {
|
||||||
}
|
}
|
||||||
|
|
||||||
func startService(ctx context.Context, engine *kube, step *types.Step) (*v1.Service, error) {
|
func startService(ctx context.Context, engine *kube, step *types.Step) (*v1.Service, error) {
|
||||||
svc, err := mkService(step, engine.config.Namespace)
|
engineConfig := engine.getConfig()
|
||||||
|
svc, err := mkService(step, engineConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace().Str("name", svc.Name).Interface("selector", svc.Spec.Selector).Interface("ports", svc.Spec.Ports).Msg("creating service")
|
log.Trace().Str("name", svc.Name).Interface("selector", svc.Spec.Selector).Interface("ports", svc.Spec.Ports).Msg("creating service")
|
||||||
return engine.client.CoreV1().Services(engine.config.Namespace).Create(ctx, svc, metav1.CreateOptions{})
|
return engine.client.CoreV1().Services(engineConfig.Namespace).Create(ctx, svc, metav1.CreateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopService(ctx context.Context, engine *kube, step *types.Step, deleteOpts metav1.DeleteOptions) error {
|
func stopService(ctx context.Context, engine *kube, step *types.Step, deleteOpts metav1.DeleteOptions) error {
|
||||||
|
|
|
@ -82,7 +82,7 @@ func TestService(t *testing.T) {
|
||||||
s, err := mkService(&types.Step{
|
s, err := mkService(&types.Step{
|
||||||
Name: "bar",
|
Name: "bar",
|
||||||
Ports: ports,
|
Ports: ports,
|
||||||
}, "foo")
|
}, &config{Namespace: "foo"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
j, err := json.Marshal(s)
|
j, err := json.Marshal(s)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -25,15 +25,15 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mkPersistentVolumeClaim(namespace, name, storageClass, size string, storageRwx bool) (*v1.PersistentVolumeClaim, error) {
|
func mkPersistentVolumeClaim(config *config, name string) (*v1.PersistentVolumeClaim, error) {
|
||||||
_storageClass := &storageClass
|
_storageClass := &config.StorageClass
|
||||||
if storageClass == "" {
|
if config.StorageClass == "" {
|
||||||
_storageClass = nil
|
_storageClass = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var accessMode v1.PersistentVolumeAccessMode
|
var accessMode v1.PersistentVolumeAccessMode
|
||||||
|
|
||||||
if storageRwx {
|
if config.StorageRwx {
|
||||||
accessMode = v1.ReadWriteMany
|
accessMode = v1.ReadWriteMany
|
||||||
} else {
|
} else {
|
||||||
accessMode = v1.ReadWriteOnce
|
accessMode = v1.ReadWriteOnce
|
||||||
|
@ -47,14 +47,14 @@ func mkPersistentVolumeClaim(namespace, name, storageClass, size string, storage
|
||||||
pvc := &v1.PersistentVolumeClaim{
|
pvc := &v1.PersistentVolumeClaim{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: volumeName,
|
Name: volumeName,
|
||||||
Namespace: namespace,
|
Namespace: config.Namespace,
|
||||||
},
|
},
|
||||||
Spec: v1.PersistentVolumeClaimSpec{
|
Spec: v1.PersistentVolumeClaimSpec{
|
||||||
AccessModes: []v1.PersistentVolumeAccessMode{accessMode},
|
AccessModes: []v1.PersistentVolumeAccessMode{accessMode},
|
||||||
StorageClassName: _storageClass,
|
StorageClassName: _storageClass,
|
||||||
Resources: v1.VolumeResourceRequirements{
|
Resources: v1.VolumeResourceRequirements{
|
||||||
Requests: v1.ResourceList{
|
Requests: v1.ResourceList{
|
||||||
v1.ResourceStorage: resource.MustParse(size),
|
v1.ResourceStorage: resource.MustParse(config.VolumeSize),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -76,13 +76,14 @@ func volumeMountPath(name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func startVolume(ctx context.Context, engine *kube, name string) (*v1.PersistentVolumeClaim, error) {
|
func startVolume(ctx context.Context, engine *kube, name string) (*v1.PersistentVolumeClaim, error) {
|
||||||
pvc, err := mkPersistentVolumeClaim(engine.config.Namespace, name, engine.config.StorageClass, engine.config.VolumeSize, engine.config.StorageRwx)
|
engineConfig := engine.getConfig()
|
||||||
|
pvc, err := mkPersistentVolumeClaim(engineConfig, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace().Msgf("creating volume: %s", pvc.Name)
|
log.Trace().Msgf("creating volume: %s", pvc.Name)
|
||||||
return engine.client.CoreV1().PersistentVolumeClaims(engine.config.Namespace).Create(ctx, pvc, metav1.CreateOptions{})
|
return engine.client.CoreV1().PersistentVolumeClaims(engineConfig.Namespace).Create(ctx, pvc, metav1.CreateOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopVolume(ctx context.Context, engine *kube, name string, deleteOpts metav1.DeleteOptions) error {
|
func stopVolume(ctx context.Context, engine *kube, name string, deleteOpts metav1.DeleteOptions) error {
|
||||||
|
|
|
@ -84,20 +84,35 @@ func TestPersistentVolumeClaim(t *testing.T) {
|
||||||
"status": {}
|
"status": {}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
pvc, err := mkPersistentVolumeClaim("someNamespace", "somename", "local-storage", "1Gi", true)
|
pvc, err := mkPersistentVolumeClaim(&config{
|
||||||
|
Namespace: "someNamespace",
|
||||||
|
StorageClass: "local-storage",
|
||||||
|
VolumeSize: "1Gi",
|
||||||
|
StorageRwx: true,
|
||||||
|
}, "somename")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
j, err := json.Marshal(pvc)
|
j, err := json.Marshal(pvc)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.JSONEq(t, expectedRwx, string(j))
|
assert.JSONEq(t, expectedRwx, string(j))
|
||||||
|
|
||||||
pvc, err = mkPersistentVolumeClaim("someNamespace", "somename", "local-storage", "1Gi", false)
|
pvc, err = mkPersistentVolumeClaim(&config{
|
||||||
|
Namespace: "someNamespace",
|
||||||
|
StorageClass: "local-storage",
|
||||||
|
VolumeSize: "1Gi",
|
||||||
|
StorageRwx: false,
|
||||||
|
}, "somename")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
j, err = json.Marshal(pvc)
|
j, err = json.Marshal(pvc)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.JSONEq(t, expectedRwo, string(j))
|
assert.JSONEq(t, expectedRwo, string(j))
|
||||||
|
|
||||||
_, err = mkPersistentVolumeClaim("someNamespace", "some0..INVALID3name", "local-storage", "1Gi", false)
|
_, err = mkPersistentVolumeClaim(&config{
|
||||||
|
Namespace: "someNamespace",
|
||||||
|
StorageClass: "local-storage",
|
||||||
|
VolumeSize: "1Gi",
|
||||||
|
StorageRwx: false,
|
||||||
|
}, "some0..INVALID3name")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue