233 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			233 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			Go
		
	
	
	
package base
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"regexp"
 | 
						|
 | 
						|
	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha1"
 | 
						|
	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
 | 
						|
	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins"
 | 
						|
	"github.com/jenkinsci/kubernetes-operator/pkg/log"
 | 
						|
 | 
						|
	docker "github.com/docker/distribution/reference"
 | 
						|
	stackerr "github.com/pkg/errors"
 | 
						|
	corev1 "k8s.io/api/core/v1"
 | 
						|
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	dockerImageRegexp = regexp.MustCompile(`^` + docker.TagRegexp.String() + `$`)
 | 
						|
)
 | 
						|
 | 
						|
// Validate validates Jenkins CR Spec.master section
 | 
						|
func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha1.Jenkins) (bool, error) {
 | 
						|
	if !r.validateReservedVolumes() {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
	if valid, err := r.validateVolumes(); err != nil {
 | 
						|
		return false, err
 | 
						|
	} else if !valid {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if !r.validateContainer(jenkins.Spec.Master.Container) {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	for _, container := range jenkins.Spec.Master.Containers {
 | 
						|
		if !r.validateContainer(container) {
 | 
						|
			return false, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !r.validatePlugins(jenkins.Spec.Master.OperatorPlugins, jenkins.Spec.Master.Plugins) {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if !r.validateJenkinsMasterPodEnvs() {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return true, nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *ReconcileJenkinsBaseConfiguration) validateVolumes() (bool, error) {
 | 
						|
	valid := true
 | 
						|
	for _, volume := range r.jenkins.Spec.Master.Volumes {
 | 
						|
		switch {
 | 
						|
		case volume.ConfigMap != nil:
 | 
						|
			if ok, err := r.validateConfigMapVolume(volume); err != nil {
 | 
						|
				return false, err
 | 
						|
			} else if !ok {
 | 
						|
				valid = false
 | 
						|
			}
 | 
						|
		case volume.Secret != nil:
 | 
						|
			if ok, err := r.validateSecretVolume(volume); err != nil {
 | 
						|
				return false, err
 | 
						|
			} else if !ok {
 | 
						|
				valid = false
 | 
						|
			}
 | 
						|
		default: //TODO add support for rest of volumes
 | 
						|
			valid = false
 | 
						|
			r.logger.V(log.VWarn).Info(fmt.Sprintf("Unsupported volume '%+v'", volume))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return valid, nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *ReconcileJenkinsBaseConfiguration) validateConfigMapVolume(volume corev1.Volume) (bool, error) {
 | 
						|
	if volume.ConfigMap.Optional != nil && *volume.ConfigMap.Optional {
 | 
						|
		return true, nil
 | 
						|
	}
 | 
						|
 | 
						|
	configMap := &corev1.ConfigMap{}
 | 
						|
	err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.ConfigMap.Name, Namespace: r.jenkins.ObjectMeta.Namespace}, configMap)
 | 
						|
	if err != nil && apierrors.IsNotFound(err) {
 | 
						|
		r.logger.V(log.VWarn).Info(fmt.Sprintf("ConfigMap '%s' not found for volume '%+v'", volume.ConfigMap.Name, volume))
 | 
						|
		return false, nil
 | 
						|
	} else if err != nil && !apierrors.IsNotFound(err) {
 | 
						|
		return false, stackerr.WithStack(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return true, nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *ReconcileJenkinsBaseConfiguration) validateSecretVolume(volume corev1.Volume) (bool, error) {
 | 
						|
	if volume.Secret.Optional != nil && *volume.Secret.Optional {
 | 
						|
		return true, nil
 | 
						|
	}
 | 
						|
 | 
						|
	secret := &corev1.Secret{}
 | 
						|
	err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.Secret.SecretName, Namespace: r.jenkins.ObjectMeta.Namespace}, secret)
 | 
						|
	if err != nil && apierrors.IsNotFound(err) {
 | 
						|
		r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' not found for volume '%+v'", volume.Secret.SecretName, volume))
 | 
						|
		return false, nil
 | 
						|
	} else if err != nil && !apierrors.IsNotFound(err) {
 | 
						|
		return false, stackerr.WithStack(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return true, nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *ReconcileJenkinsBaseConfiguration) validateReservedVolumes() bool {
 | 
						|
	valid := true
 | 
						|
 | 
						|
	for _, baseVolume := range resources.GetJenkinsMasterPodBaseVolumes(r.jenkins) {
 | 
						|
		for _, volume := range r.jenkins.Spec.Master.Volumes {
 | 
						|
			if baseVolume.Name == volume.Name {
 | 
						|
				r.logger.V(log.VWarn).Info(fmt.Sprintf("Jenkins Master pod volume '%s' is reserved please choose different one", volume.Name))
 | 
						|
				valid = false
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return valid
 | 
						|
}
 | 
						|
 | 
						|
func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha1.Container) bool {
 | 
						|
	logger := r.logger.WithValues("container", container.Name)
 | 
						|
	if container.Image == "" {
 | 
						|
		logger.V(log.VWarn).Info("Image not set")
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	if !dockerImageRegexp.MatchString(container.Image) && !docker.ReferenceRegexp.MatchString(container.Image) {
 | 
						|
		logger.V(log.VWarn).Info("Invalid image")
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	if container.ImagePullPolicy == "" {
 | 
						|
		logger.V(log.VWarn).Info("Image pull policy not set")
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	if !r.validateContainerVolumeMounts(container) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func (r *ReconcileJenkinsBaseConfiguration) validateContainerVolumeMounts(container v1alpha1.Container) bool {
 | 
						|
	logger := r.logger.WithValues("container", container.Name)
 | 
						|
	allVolumes := append(resources.GetJenkinsMasterPodBaseVolumes(r.jenkins), r.jenkins.Spec.Master.Volumes...)
 | 
						|
	valid := true
 | 
						|
 | 
						|
	for _, volumeMount := range container.VolumeMounts {
 | 
						|
		if len(volumeMount.MountPath) == 0 {
 | 
						|
			logger.V(log.VWarn).Info(fmt.Sprintf("mountPath not set for '%s' volume mount", volumeMount.Name))
 | 
						|
			valid = false
 | 
						|
		}
 | 
						|
 | 
						|
		foundVolume := false
 | 
						|
		for _, volume := range allVolumes {
 | 
						|
			if volumeMount.Name == volume.Name {
 | 
						|
				foundVolume = true
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if !foundVolume {
 | 
						|
			logger.V(log.VWarn).Info(fmt.Sprintf("Not found volume for '%s' volume mount", volumeMount.Name))
 | 
						|
			valid = false
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return valid
 | 
						|
}
 | 
						|
 | 
						|
func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool {
 | 
						|
	baseEnvs := resources.GetJenkinsMasterPodBaseEnvs()
 | 
						|
	baseEnvNames := map[string]string{}
 | 
						|
	for _, env := range baseEnvs {
 | 
						|
		baseEnvNames[env.Name] = env.Value
 | 
						|
	}
 | 
						|
 | 
						|
	valid := true
 | 
						|
	for _, userEnv := range r.jenkins.Spec.Master.Env {
 | 
						|
		if _, overriding := baseEnvNames[userEnv.Name]; overriding {
 | 
						|
			r.logger.V(log.VWarn).Info(fmt.Sprintf("Jenkins Master pod env '%s' cannot be overridden", userEnv.Name))
 | 
						|
			valid = false
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return valid
 | 
						|
}
 | 
						|
 | 
						|
func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(pluginsWithVersionSlice ...map[string][]string) bool {
 | 
						|
	valid := true
 | 
						|
	allPlugins := map[plugins.Plugin][]plugins.Plugin{}
 | 
						|
 | 
						|
	for _, pluginsWithVersions := range pluginsWithVersionSlice {
 | 
						|
		for rootPluginName, dependentPluginNames := range pluginsWithVersions {
 | 
						|
			rootPlugin, err := plugins.New(rootPluginName)
 | 
						|
			if err != nil {
 | 
						|
				r.logger.V(log.VWarn).Info(fmt.Sprintf("Invalid root plugin name '%s'", rootPluginName))
 | 
						|
				valid = false
 | 
						|
			}
 | 
						|
 | 
						|
			var dependentPlugins []plugins.Plugin
 | 
						|
			for _, pluginName := range dependentPluginNames {
 | 
						|
				if p, err := plugins.New(pluginName); err != nil {
 | 
						|
					r.logger.V(log.VWarn).Info(fmt.Sprintf("Invalid dependent plugin name '%s' in root plugin '%s'", pluginName, rootPluginName))
 | 
						|
					valid = false
 | 
						|
				} else {
 | 
						|
					dependentPlugins = append(dependentPlugins, *p)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if rootPlugin != nil {
 | 
						|
				allPlugins[*rootPlugin] = dependentPlugins
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if valid {
 | 
						|
		return plugins.VerifyDependencies(allPlugins)
 | 
						|
	}
 | 
						|
 | 
						|
	return valid
 | 
						|
}
 |