You've already forked woodpecker
							
							
				mirror of
				https://github.com/woodpecker-ci/woodpecker.git
				synced 2025-10-30 23:27:39 +02:00 
			
		
		
		
	| @@ -18,8 +18,10 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"maps" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"slices" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/rs/zerolog/log" | ||||
| @@ -155,6 +157,17 @@ func (e *kube) Load(ctx context.Context) (*types.BackendInfo, error) { | ||||
| 	}, 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. | ||||
| func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID string) error { | ||||
| 	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, | ||||
| 	} | ||||
|  | ||||
| 	labels := make(map[string]string, len(config.PodLabels)+1) | ||||
| 	// copy to not alter the engine config | ||||
| 	maps.Copy(labels, config.PodLabels) | ||||
| 	labels[StepLabel] = step.Name | ||||
| 	meta.Labels = labels | ||||
| 	meta.Labels = config.PodLabels | ||||
| 	if meta.Labels == nil { | ||||
| 		meta.Labels = make(map[string]string, 1) | ||||
| 	} | ||||
| 	meta.Labels[StepLabel] = step.Name | ||||
|  | ||||
| 	// copy to not alter the engine config | ||||
| 	meta.Annotations = make(map[string]string, len(config.PodAnnotations)) | ||||
| 	maps.Copy(meta.Annotations, config.PodAnnotations) | ||||
| 	meta.Annotations = config.PodAnnotations | ||||
| 	if meta.Annotations == nil { | ||||
| 		meta.Annotations = make(map[string]string) | ||||
| 	} | ||||
|  | ||||
| 	securityContext := step.BackendOptions.Kubernetes.SecurityContext | ||||
| 	if securityContext != nil { | ||||
| @@ -442,13 +443,14 @@ func startPod(ctx context.Context, engine *kube, step *types.Step) (*v1.Pod, err | ||||
| 	if err != nil { | ||||
| 		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 { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
|   | ||||
| @@ -32,7 +32,7 @@ const ( | ||||
| 	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) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| @@ -51,7 +51,7 @@ func mkService(step *types.Step, namespace string) (*v1.Service, error) { | ||||
| 	return &v1.Service{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      name, | ||||
| 			Namespace: namespace, | ||||
| 			Namespace: config.Namespace, | ||||
| 		}, | ||||
| 		Spec: v1.ServiceSpec{ | ||||
| 			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) { | ||||
| 	svc, err := mkService(step, engine.config.Namespace) | ||||
| 	engineConfig := engine.getConfig() | ||||
| 	svc, err := mkService(step, engineConfig) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
|   | ||||
| @@ -82,7 +82,7 @@ func TestService(t *testing.T) { | ||||
| 	s, err := mkService(&types.Step{ | ||||
| 		Name:  "bar", | ||||
| 		Ports: ports, | ||||
| 	}, "foo") | ||||
| 	}, &config{Namespace: "foo"}) | ||||
| 	assert.NoError(t, err) | ||||
| 	j, err := json.Marshal(s) | ||||
| 	assert.NoError(t, err) | ||||
|   | ||||
| @@ -25,15 +25,15 @@ import ( | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| ) | ||||
|  | ||||
| func mkPersistentVolumeClaim(namespace, name, storageClass, size string, storageRwx bool) (*v1.PersistentVolumeClaim, error) { | ||||
| 	_storageClass := &storageClass | ||||
| 	if storageClass == "" { | ||||
| func mkPersistentVolumeClaim(config *config, name string) (*v1.PersistentVolumeClaim, error) { | ||||
| 	_storageClass := &config.StorageClass | ||||
| 	if config.StorageClass == "" { | ||||
| 		_storageClass = nil | ||||
| 	} | ||||
|  | ||||
| 	var accessMode v1.PersistentVolumeAccessMode | ||||
|  | ||||
| 	if storageRwx { | ||||
| 	if config.StorageRwx { | ||||
| 		accessMode = v1.ReadWriteMany | ||||
| 	} else { | ||||
| 		accessMode = v1.ReadWriteOnce | ||||
| @@ -47,14 +47,14 @@ func mkPersistentVolumeClaim(namespace, name, storageClass, size string, storage | ||||
| 	pvc := &v1.PersistentVolumeClaim{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      volumeName, | ||||
| 			Namespace: namespace, | ||||
| 			Namespace: config.Namespace, | ||||
| 		}, | ||||
| 		Spec: v1.PersistentVolumeClaimSpec{ | ||||
| 			AccessModes:      []v1.PersistentVolumeAccessMode{accessMode}, | ||||
| 			StorageClassName: _storageClass, | ||||
| 			Resources: v1.VolumeResourceRequirements{ | ||||
| 				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) { | ||||
| 	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 { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
|   | ||||
| @@ -84,20 +84,35 @@ func TestPersistentVolumeClaim(t *testing.T) { | ||||
| 	  "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) | ||||
|  | ||||
| 	j, err := json.Marshal(pvc) | ||||
| 	assert.NoError(t, err) | ||||
| 	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) | ||||
|  | ||||
| 	j, err = json.Marshal(pvc) | ||||
| 	assert.NoError(t, err) | ||||
| 	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) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user