You've already forked woodpecker
							
							
				mirror of
				https://github.com/woodpecker-ci/woodpecker.git
				synced 2025-10-30 23:27:39 +02:00 
			
		
		
		
	fix(backend/kubernetes): Ensure valid naming of name field (#1661)
- Kubernetes v1.26 on VKE causes error when creating persistent volume claim because of uppercase characters in name field This patch is trivial just in order to get it working - happy to implement differently. The error in question: ``` The PersistentVolumeClaim "wp-01G1131R63FWBSPMA4ZAZTKLE-0-clone-0" is invalid: metadata.name: Invalid value: "wp-01G1131R63FWBSPMA4ZAZTKLE-0-clone-0": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*') ```
This commit is contained in:
		| @@ -115,6 +115,7 @@ pipeline: | ||||
|         from_secret: codecov_token | ||||
|     when: | ||||
|       path: *when_path | ||||
|     failure: ignore | ||||
|  | ||||
| services: | ||||
|   service-postgres: | ||||
|   | ||||
| @@ -119,8 +119,12 @@ func (e *kube) Setup(ctx context.Context, conf *types.Config) error { | ||||
| 	log.Trace().Msgf("Setting up Kubernetes primitives") | ||||
|  | ||||
| 	for _, vol := range conf.Volumes { | ||||
| 		pvc := PersistentVolumeClaim(e.config.Namespace, vol.Name, e.config.StorageClass, e.config.VolumeSize, e.config.StorageRwx) | ||||
| 		_, err := e.client.CoreV1().PersistentVolumeClaims(e.config.Namespace).Create(ctx, pvc, metav1.CreateOptions{}) | ||||
| 		pvc, err := PersistentVolumeClaim(e.config.Namespace, vol.Name, e.config.StorageClass, e.config.VolumeSize, e.config.StorageRwx) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		_, err = e.client.CoreV1().PersistentVolumeClaims(e.config.Namespace).Create(ctx, pvc, metav1.CreateOptions{}) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @@ -131,10 +135,14 @@ func (e *kube) Setup(ctx context.Context, conf *types.Config) error { | ||||
| 	for _, stage := range conf.Stages { | ||||
| 		if stage.Alias == "services" { | ||||
| 			for _, step := range stage.Steps { | ||||
| 				log.Trace().Str("pod-name", podName(step)).Msgf("Creating service: %s", step.Name) | ||||
| 				stepName, err := dnsName(step.Name) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				log.Trace().Str("pod-name", stepName).Msgf("Creating service: %s", step.Name) | ||||
| 				// TODO: support ports setting | ||||
| 				// svc, err := Service(e.config.Namespace, step.Name, podName(step), step.Ports) | ||||
| 				svc, err := Service(e.config.Namespace, step.Name, podName(step), []string{}) | ||||
| 				// svc, err := Service(e.config.Namespace, step.Name, stepName, step.Ports) | ||||
| 				svc, err := Service(e.config.Namespace, step.Name, stepName, []string{}) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| @@ -161,16 +169,23 @@ func (e *kube) Setup(ctx context.Context, conf *types.Config) error { | ||||
|  | ||||
| // Start the pipeline step. | ||||
| func (e *kube) Exec(ctx context.Context, step *types.Step) error { | ||||
| 	pod := Pod(e.config.Namespace, step, e.config.PodLabels, e.config.PodAnnotations) | ||||
| 	pod, err := Pod(e.config.Namespace, step, e.config.PodLabels, e.config.PodAnnotations) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	log.Trace().Msgf("Creating pod: %s", pod.Name) | ||||
| 	_, err := e.client.CoreV1().Pods(e.config.Namespace).Create(ctx, pod, metav1.CreateOptions{}) | ||||
| 	_, err = e.client.CoreV1().Pods(e.config.Namespace).Create(ctx, pod, metav1.CreateOptions{}) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // Wait for the pipeline step to complete and returns | ||||
| // the completion results. | ||||
| func (e *kube) Wait(ctx context.Context, step *types.Step) (*types.State, error) { | ||||
| 	podName := podName(step) | ||||
| 	podName, err := dnsName(step.Name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	finished := make(chan bool) | ||||
|  | ||||
| @@ -225,7 +240,10 @@ func (e *kube) Wait(ctx context.Context, step *types.Step) (*types.State, error) | ||||
|  | ||||
| // Tail the pipeline step logs. | ||||
| func (e *kube) Tail(ctx context.Context, step *types.Step) (io.ReadCloser, error) { | ||||
| 	podName := podName(step) | ||||
| 	podName, err := dnsName(step.Name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	up := make(chan bool) | ||||
|  | ||||
| @@ -305,10 +323,14 @@ func (e *kube) Destroy(_ context.Context, conf *types.Config) error { | ||||
|  | ||||
| 	for _, stage := range conf.Stages { | ||||
| 		for _, step := range stage.Steps { | ||||
| 			log.Trace().Msgf("Deleting pod: %s", podName(step)) | ||||
| 			if err := e.client.CoreV1().Pods(e.config.Namespace).Delete(noContext, podName(step), deleteOpts); err != nil { | ||||
| 			stepName, err := dnsName(step.Name) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			log.Trace().Msgf("Deleting pod: %s", stepName) | ||||
| 			if err := e.client.CoreV1().Pods(e.config.Namespace).Delete(noContext, stepName, deleteOpts); err != nil { | ||||
| 				if errors.IsNotFound(err) { | ||||
| 					log.Trace().Err(err).Msgf("Unable to delete pod %s", podName(step)) | ||||
| 					log.Trace().Err(err).Msgf("Unable to delete pod %s", stepName) | ||||
| 				} else { | ||||
| 					return err | ||||
| 				} | ||||
| @@ -338,8 +360,11 @@ func (e *kube) Destroy(_ context.Context, conf *types.Config) error { | ||||
| 	} | ||||
|  | ||||
| 	for _, vol := range conf.Volumes { | ||||
| 		pvc := PersistentVolumeClaim(e.config.Namespace, vol.Name, e.config.StorageClass, e.config.VolumeSize, e.config.StorageRwx) | ||||
| 		err := e.client.CoreV1().PersistentVolumeClaims(e.config.Namespace).Delete(noContext, pvc.Name, deleteOpts) | ||||
| 		pvc, err := PersistentVolumeClaim(e.config.Namespace, vol.Name, e.config.StorageClass, e.config.VolumeSize, e.config.StorageRwx) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		err = e.client.CoreV1().PersistentVolumeClaims(e.config.Namespace).Delete(noContext, pvc.Name, deleteOpts) | ||||
| 		if err != nil { | ||||
| 			if errors.IsNotFound(err) { | ||||
| 				log.Trace().Err(err).Msgf("Unable to delete pvc %s", pvc.Name) | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import ( | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| ) | ||||
|  | ||||
| func Pod(namespace string, step *types.Step, labels, annotations map[string]string) *v1.Pod { | ||||
| func Pod(namespace string, step *types.Step, labels, annotations map[string]string) (*v1.Pod, error) { | ||||
| 	var ( | ||||
| 		vols       []v1.Volume | ||||
| 		volMounts  []v1.VolumeMount | ||||
| @@ -20,18 +20,23 @@ func Pod(namespace string, step *types.Step, labels, annotations map[string]stri | ||||
|  | ||||
| 	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(vol), | ||||
| 				Name: volumeName, | ||||
| 				VolumeSource: v1.VolumeSource{ | ||||
| 					PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{ | ||||
| 						ClaimName: volumeName(vol), | ||||
| 						ClaimName: volumeName, | ||||
| 						ReadOnly:  false, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}) | ||||
|  | ||||
| 			volMounts = append(volMounts, v1.VolumeMount{ | ||||
| 				Name:      volumeName(vol), | ||||
| 				Name:      volumeName, | ||||
| 				MountPath: volumeMountPath(vol), | ||||
| 			}) | ||||
| 		} | ||||
| @@ -88,11 +93,16 @@ func Pod(namespace string, step *types.Step, labels, annotations map[string]stri | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	labels["step"] = podName(step) | ||||
| 	podName, err := dnsName(step.Name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &v1.Pod{ | ||||
| 	labels["step"] = podName | ||||
|  | ||||
| 	pod := &v1.Pod{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:        podName(step), | ||||
| 			Name:        podName, | ||||
| 			Namespace:   namespace, | ||||
| 			Labels:      labels, | ||||
| 			Annotations: annotations, | ||||
| @@ -101,7 +111,7 @@ func Pod(namespace string, step *types.Step, labels, annotations map[string]stri | ||||
| 			RestartPolicy: v1.RestartPolicyNever, | ||||
| 			HostAliases:   hostAliases, | ||||
| 			Containers: []v1.Container{{ | ||||
| 				Name:            podName(step), | ||||
| 				Name:            podName, | ||||
| 				Image:           step.Image, | ||||
| 				ImagePullPolicy: pullPolicy, | ||||
| 				Command:         entrypoint, | ||||
| @@ -118,10 +128,8 @@ func Pod(namespace string, step *types.Step, labels, annotations map[string]stri | ||||
| 			Volumes:          vols, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func podName(s *types.Step) string { | ||||
| 	return dnsName(s.Name) | ||||
| 	return pod, nil | ||||
| } | ||||
|  | ||||
| func mapToEnvVars(m map[string]string) []v1.EnvVar { | ||||
|   | ||||
| @@ -22,9 +22,14 @@ func Service(namespace, name, podName string, ports []string) (*v1.Service, erro | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	dnsName, err := dnsName(name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return &v1.Service{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      dnsName(name), | ||||
| 			Name:      dnsName, | ||||
| 			Namespace: namespace, | ||||
| 		}, | ||||
| 		Spec: v1.ServiceSpec{ | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| package kubernetes | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
|  | ||||
| 	v1 "k8s.io/api/core/v1" | ||||
| @@ -10,8 +12,19 @@ import ( | ||||
| 	"k8s.io/client-go/tools/clientcmd" | ||||
| ) | ||||
|  | ||||
| func dnsName(i string) string { | ||||
| 	return strings.Replace(i, "_", "-", -1) | ||||
| var ( | ||||
| 	dnsPattern           = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) | ||||
| 	ErrDNSPatternInvalid = errors.New("name is not a valid kubernetes DNS name") | ||||
| ) | ||||
|  | ||||
| func dnsName(i string) (string, error) { | ||||
| 	res := strings.Replace(i, "_", "-", -1) | ||||
|  | ||||
| 	if found := dnsPattern.FindStringIndex(res); found == nil { | ||||
| 		return "", ErrDNSPatternInvalid | ||||
| 	} | ||||
|  | ||||
| 	return res, nil | ||||
| } | ||||
|  | ||||
| func isImagePullBackOffState(pod *v1.Pod) bool { | ||||
|   | ||||
| @@ -8,7 +8,7 @@ import ( | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| ) | ||||
|  | ||||
| func PersistentVolumeClaim(namespace, name, storageClass, size string, storageRwx bool) *v1.PersistentVolumeClaim { | ||||
| func PersistentVolumeClaim(namespace, name, storageClass, size string, storageRwx bool) (*v1.PersistentVolumeClaim, error) { | ||||
| 	_storageClass := &storageClass | ||||
| 	if storageClass == "" { | ||||
| 		_storageClass = nil | ||||
| @@ -22,9 +22,14 @@ func PersistentVolumeClaim(namespace, name, storageClass, size string, storageRw | ||||
| 		accessMode = v1.ReadWriteOnce | ||||
| 	} | ||||
|  | ||||
| 	return &v1.PersistentVolumeClaim{ | ||||
| 	volumeName, err := dnsName(strings.Split(name, ":")[0]) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	pvc := &v1.PersistentVolumeClaim{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      volumeName(name), | ||||
| 			Name:      volumeName, | ||||
| 			Namespace: namespace, | ||||
| 		}, | ||||
| 		Spec: v1.PersistentVolumeClaimSpec{ | ||||
| @@ -37,8 +42,6 @@ func PersistentVolumeClaim(namespace, name, storageClass, size string, storageRw | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func volumeName(i string) string { | ||||
| 	return dnsName(strings.Split(i, ":")[0]) | ||||
| 	return pvc, nil | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ func TestPersistentVolumeClaim(t *testing.T) { | ||||
| 	expectedRwx := ` | ||||
| 	{ | ||||
| 	  "metadata": { | ||||
| 	    "name": "someName", | ||||
| 	    "name": "somename", | ||||
| 	    "namespace": "someNamespace", | ||||
| 	    "creationTimestamp": null | ||||
| 	  }, | ||||
| @@ -32,7 +32,7 @@ func TestPersistentVolumeClaim(t *testing.T) { | ||||
| 	expectedRwo := ` | ||||
| 	{ | ||||
| 	  "metadata": { | ||||
| 	    "name": "someName", | ||||
| 	    "name": "somename", | ||||
| 	    "namespace": "someNamespace", | ||||
| 	    "creationTimestamp": null | ||||
| 	  }, | ||||
| @@ -50,13 +50,20 @@ func TestPersistentVolumeClaim(t *testing.T) { | ||||
| 	  "status": {} | ||||
| 	}` | ||||
|  | ||||
| 	pvc := PersistentVolumeClaim("someNamespace", "someName", "local-storage", "1Gi", true) | ||||
| 	pvc, err := PersistentVolumeClaim("someNamespace", "somename", "local-storage", "1Gi", true) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	j, err := json.Marshal(pvc) | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.JSONEq(t, expectedRwx, string(j)) | ||||
|  | ||||
| 	pvc = PersistentVolumeClaim("someNamespace", "someName", "local-storage", "1Gi", false) | ||||
| 	pvc, err = PersistentVolumeClaim("someNamespace", "somename", "local-storage", "1Gi", false) | ||||
| 	assert.Nil(t, err) | ||||
|  | ||||
| 	j, err = json.Marshal(pvc) | ||||
| 	assert.Nil(t, err) | ||||
| 	assert.JSONEq(t, expectedRwo, string(j)) | ||||
|  | ||||
| 	_, err = PersistentVolumeClaim("someNamespace", "some0INVALID3name", "local-storage", "1Gi", false) | ||||
| 	assert.NotNil(t, err) | ||||
| } | ||||
|   | ||||
| @@ -280,7 +280,7 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml.Config, environ map[ | ||||
| 		compiler.WithPrefix( | ||||
| 			fmt.Sprintf( | ||||
| 				"wp_%s_%d", | ||||
| 				ulid.Make().String(), | ||||
| 				strings.ToLower(ulid.Make().String()), | ||||
| 				stepID, | ||||
| 			), | ||||
| 		), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user