#14 Add username/password authentication for seed jobs
This commit is contained in:
		
							parent
							
								
									6285f22170
								
							
						
					
					
						commit
						1d10d629ce
					
				|  | @ -107,19 +107,34 @@ type JenkinsList struct { | ||||||
| 	Items           []Jenkins `json:"items"` | 	Items           []Jenkins `json:"items"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // JenkinsCredentialType defines type of Jenkins credential used to seed job mechanisms
 | ||||||
|  | type JenkinsCredentialType string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// NoJenkinsCredentialCredentialType define none Jenkins credential type
 | ||||||
|  | 	NoJenkinsCredentialCredentialType JenkinsCredentialType = "" | ||||||
|  | 	// BasicSSHCredentialType define basic SSH Jenkins credential type
 | ||||||
|  | 	BasicSSHCredentialType JenkinsCredentialType = "basicSSHUserPrivateKey" | ||||||
|  | 	// UsernamePasswordCredentialType define username & password Jenkins credential type
 | ||||||
|  | 	UsernamePasswordCredentialType JenkinsCredentialType = "usernamePassword" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // AllowedJenkinsCredentialMap contains all allowed Jenkins credentials types
 | ||||||
|  | var AllowedJenkinsCredentialMap = map[string]string{ | ||||||
|  | 	string(NoJenkinsCredentialCredentialType): "", | ||||||
|  | 	string(BasicSSHCredentialType):            "", | ||||||
|  | 	string(UsernamePasswordCredentialType):    "", | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // SeedJob defined configuration for seed jobs and deploy keys
 | // SeedJob defined configuration for seed jobs and deploy keys
 | ||||||
| type SeedJob struct { | type SeedJob struct { | ||||||
| 	ID               string     `json:"id"` | 	ID                    string                `json:"id,omitempty"` | ||||||
|  | 	CredentialID          string                `json:"credentialID,omitempty"` | ||||||
| 	Description           string                `json:"description,omitempty"` | 	Description           string                `json:"description,omitempty"` | ||||||
| 	Targets               string                `json:"targets,omitempty"` | 	Targets               string                `json:"targets,omitempty"` | ||||||
| 	RepositoryBranch      string                `json:"repositoryBranch,omitempty"` | 	RepositoryBranch      string                `json:"repositoryBranch,omitempty"` | ||||||
| 	RepositoryURL    string     `json:"repositoryUrl"` | 	RepositoryURL         string                `json:"repositoryUrl,omitempty"` | ||||||
| 	PrivateKey       PrivateKey `json:"privateKey,omitempty"` | 	JenkinsCredentialType JenkinsCredentialType `json:"credentialType,omitempty"` | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // PrivateKey contains a private key
 |  | ||||||
| type PrivateKey struct { |  | ||||||
| 	SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef"` |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
|  |  | ||||||
|  | @ -185,9 +185,7 @@ func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) { | ||||||
| 	if in.SeedJobs != nil { | 	if in.SeedJobs != nil { | ||||||
| 		in, out := &in.SeedJobs, &out.SeedJobs | 		in, out := &in.SeedJobs, &out.SeedJobs | ||||||
| 		*out = make([]SeedJob, len(*in)) | 		*out = make([]SeedJob, len(*in)) | ||||||
| 		for i := range *in { | 		copy(*out, *in) | ||||||
| 			(*in)[i].DeepCopyInto(&(*out)[i]) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	in.Service.DeepCopyInto(&out.Service) | 	in.Service.DeepCopyInto(&out.Service) | ||||||
| 	in.SlaveService.DeepCopyInto(&out.SlaveService) | 	in.SlaveService.DeepCopyInto(&out.SlaveService) | ||||||
|  | @ -239,31 +237,9 @@ func (in *JenkinsStatus) DeepCopy() *JenkinsStatus { | ||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 |  | ||||||
| func (in *PrivateKey) DeepCopyInto(out *PrivateKey) { |  | ||||||
| 	*out = *in |  | ||||||
| 	if in.SecretKeyRef != nil { |  | ||||||
| 		in, out := &in.SecretKeyRef, &out.SecretKeyRef |  | ||||||
| 		*out = new(v1.SecretKeySelector) |  | ||||||
| 		(*in).DeepCopyInto(*out) |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateKey.
 |  | ||||||
| func (in *PrivateKey) DeepCopy() *PrivateKey { |  | ||||||
| 	if in == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	out := new(PrivateKey) |  | ||||||
| 	in.DeepCopyInto(out) |  | ||||||
| 	return out |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
| func (in *SeedJob) DeepCopyInto(out *SeedJob) { | func (in *SeedJob) DeepCopyInto(out *SeedJob) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
| 	in.PrivateKey.DeepCopyInto(&out.PrivateKey) |  | ||||||
| 	return | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -253,9 +253,8 @@ func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationConfigMap(met | ||||||
| 	} else if err != nil { | 	} else if err != nil { | ||||||
| 		return stackerr.WithStack(err) | 		return stackerr.WithStack(err) | ||||||
| 	} | 	} | ||||||
| 	valid := r.verifyLabelsForWatchedResource(currentConfigMap) | 	if !resources.VerifyIfLabelsAreSet(currentConfigMap, resources.BuildLabelsForWatchedResources(*r.jenkins)) { | ||||||
| 	if !valid { | 		currentConfigMap.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(*r.jenkins) | ||||||
| 		currentConfigMap.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(r.jenkins) |  | ||||||
| 		return stackerr.WithStack(r.k8sClient.Update(context.TODO(), currentConfigMap)) | 		return stackerr.WithStack(r.k8sClient.Update(context.TODO(), currentConfigMap)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -270,9 +269,8 @@ func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationSecret(meta m | ||||||
| 	} else if err != nil { | 	} else if err != nil { | ||||||
| 		return stackerr.WithStack(err) | 		return stackerr.WithStack(err) | ||||||
| 	} | 	} | ||||||
| 	valid := r.verifyLabelsForWatchedResource(currentSecret) | 	if !resources.VerifyIfLabelsAreSet(currentSecret, resources.BuildLabelsForWatchedResources(*r.jenkins)) { | ||||||
| 	if !valid { | 		currentSecret.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(*r.jenkins) | ||||||
| 		currentSecret.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(r.jenkins) |  | ||||||
| 		return stackerr.WithStack(r.k8sClient.Update(context.TODO(), currentSecret)) | 		return stackerr.WithStack(r.k8sClient.Update(context.TODO(), currentSecret)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -547,14 +545,3 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureBaseConfiguration(jenkinsClien | ||||||
| 
 | 
 | ||||||
| 	return reconcile.Result{}, nil | 	return reconcile.Result{}, nil | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func (r *ReconcileJenkinsBaseConfiguration) verifyLabelsForWatchedResource(object metav1.Object) bool { |  | ||||||
| 	requiredLabels := resources.BuildLabelsForWatchedResources(r.jenkins) |  | ||||||
| 	for key, value := range requiredLabels { |  | ||||||
| 		if object.GetLabels()[key] != value { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -155,6 +155,16 @@ if (jenkins.getView(jenkinsViewName) == null) { | ||||||
| jenkins.save() | jenkins.save() | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
|  | const disableJobDSLScriptApproval = ` | ||||||
|  | import jenkins.model.Jenkins | ||||||
|  | import javaposse.jobdsl.plugin.GlobalJobDslSecurityConfiguration | ||||||
|  | import jenkins.model.GlobalConfiguration | ||||||
|  | 
 | ||||||
|  | // disable Job DSL script approval
 | ||||||
|  | GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).useScriptSecurity=false | ||||||
|  | GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).save() | ||||||
|  | ` | ||||||
|  | 
 | ||||||
| // GetBaseConfigurationConfigMapName returns name of Kubernetes config map used to base configuration
 | // GetBaseConfigurationConfigMapName returns name of Kubernetes config map used to base configuration
 | ||||||
| func GetBaseConfigurationConfigMapName(jenkins *v1alpha1.Jenkins) string { | func GetBaseConfigurationConfigMapName(jenkins *v1alpha1.Jenkins) string { | ||||||
| 	return fmt.Sprintf("%s-base-configuration-%s", constants.OperatorName, jenkins.ObjectMeta.Name) | 	return fmt.Sprintf("%s-base-configuration-%s", constants.OperatorName, jenkins.ObjectMeta.Name) | ||||||
|  | @ -179,6 +189,7 @@ func NewBaseConfigurationConfigMap(meta metav1.ObjectMeta, jenkins *v1alpha1.Jen | ||||||
| 				fmt.Sprintf("%s.%s:%d", GetJenkinsSlavesServiceName(jenkins), jenkins.ObjectMeta.Namespace, jenkins.Spec.SlaveService.Port), | 				fmt.Sprintf("%s.%s:%d", GetJenkinsSlavesServiceName(jenkins), jenkins.ObjectMeta.Namespace, jenkins.Spec.SlaveService.Port), | ||||||
| 			), | 			), | ||||||
| 			"7-configure-views.groovy":                 configureViews, | 			"7-configure-views.groovy":                 configureViews, | ||||||
|  | 			"8-disable-job-dsl-script-approval.groovy": disableJobDSLScriptApproval, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ func BuildResourceLabels(jenkins *v1alpha1.Jenkins) map[string]string { | ||||||
| // BuildLabelsForWatchedResources returns labels for Kubernetes resources which operator want to watch
 | // BuildLabelsForWatchedResources returns labels for Kubernetes resources which operator want to watch
 | ||||||
| // resources with that labels should not be deleted after Jenkins CR deletion, to prevent this situation don't set
 | // resources with that labels should not be deleted after Jenkins CR deletion, to prevent this situation don't set
 | ||||||
| // any owner
 | // any owner
 | ||||||
| func BuildLabelsForWatchedResources(jenkins *v1alpha1.Jenkins) map[string]string { | func BuildLabelsForWatchedResources(jenkins v1alpha1.Jenkins) map[string]string { | ||||||
| 	return map[string]string{ | 	return map[string]string{ | ||||||
| 		constants.LabelAppKey:       constants.LabelAppValue, | 		constants.LabelAppKey:       constants.LabelAppValue, | ||||||
| 		constants.LabelJenkinsCRKey: jenkins.Name, | 		constants.LabelJenkinsCRKey: jenkins.Name, | ||||||
|  | @ -41,3 +41,14 @@ func BuildLabelsForWatchedResources(jenkins *v1alpha1.Jenkins) map[string]string | ||||||
| func GetResourceName(jenkins *v1alpha1.Jenkins) string { | func GetResourceName(jenkins *v1alpha1.Jenkins) string { | ||||||
| 	return fmt.Sprintf("%s-%s", constants.LabelAppValue, jenkins.ObjectMeta.Name) | 	return fmt.Sprintf("%s-%s", constants.LabelAppValue, jenkins.ObjectMeta.Name) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // VerifyIfLabelsAreSet check is selected labels are set for specific resource
 | ||||||
|  | func VerifyIfLabelsAreSet(object metav1.Object, requiredLabels map[string]string) bool { | ||||||
|  | 	for key, value := range requiredLabels { | ||||||
|  | 		if object.GetLabels()[key] != value { | ||||||
|  | 			return false | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return true | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -49,7 +49,7 @@ func NewUserConfigurationConfigMap(jenkins *v1alpha1.Jenkins) *corev1.ConfigMap | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:      GetUserConfigurationConfigMapNameFromJenkins(jenkins), | 			Name:      GetUserConfigurationConfigMapNameFromJenkins(jenkins), | ||||||
| 			Namespace: jenkins.ObjectMeta.Namespace, | 			Namespace: jenkins.ObjectMeta.Namespace, | ||||||
| 			Labels:    BuildLabelsForWatchedResources(jenkins), | 			Labels:    BuildLabelsForWatchedResources(*jenkins), | ||||||
| 		}, | 		}, | ||||||
| 		Data: map[string]string{ | 		Data: map[string]string{ | ||||||
| 			"1-configure-theme.groovy": configureTheme, | 			"1-configure-theme.groovy": configureTheme, | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ func NewUserConfigurationSecret(jenkins *v1alpha1.Jenkins) *corev1.Secret { | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:      GetUserConfigurationSecretNameFromJenkins(jenkins), | 			Name:      GetUserConfigurationSecretNameFromJenkins(jenkins), | ||||||
| 			Namespace: jenkins.ObjectMeta.Namespace, | 			Namespace: jenkins.ObjectMeta.Namespace, | ||||||
| 			Labels:    BuildLabelsForWatchedResources(jenkins), | 			Labels:    BuildLabelsForWatchedResources(*jenkins), | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -8,12 +8,14 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||||
| 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/jobs" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/jobs" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-logr/logr" | 	"github.com/go-logr/logr" | ||||||
| 	"k8s.io/api/core/v1" | 	stackerr "github.com/pkg/errors" | ||||||
|  | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
| 	k8s "sigs.k8s.io/controller-runtime/pkg/client" | 	k8s "sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| ) | ) | ||||||
|  | @ -22,12 +24,23 @@ const ( | ||||||
| 	// ConfigureSeedJobsName this is the fixed seed job name
 | 	// ConfigureSeedJobsName this is the fixed seed job name
 | ||||||
| 	ConfigureSeedJobsName = constants.OperatorName + "-configure-seed-job" | 	ConfigureSeedJobsName = constants.OperatorName + "-configure-seed-job" | ||||||
| 
 | 
 | ||||||
| 	deployKeyIDParameterName      = "DEPLOY_KEY_ID" | 	idParameterName               = "ID" | ||||||
| 	privateKeyParameterName       = "PRIVATE_KEY" | 	credentialIDParameterName     = "CREDENTIAL_ID" | ||||||
| 	repositoryURLParameterName    = "REPOSITORY_URL" | 	repositoryURLParameterName    = "REPOSITORY_URL" | ||||||
| 	repositoryBranchParameterName = "REPOSITORY_BRANCH" | 	repositoryBranchParameterName = "REPOSITORY_BRANCH" | ||||||
| 	targetsParameterName          = "TARGETS" | 	targetsParameterName          = "TARGETS" | ||||||
| 	displayNameParameterName      = "SEED_JOB_DISPLAY_NAME" | 	displayNameParameterName      = "SEED_JOB_DISPLAY_NAME" | ||||||
|  | 
 | ||||||
|  | 	// UsernameSecretKey is username data key in Kubernetes secret used to create Jenkins username/password credential
 | ||||||
|  | 	UsernameSecretKey = "username" | ||||||
|  | 	// PasswordSecretKey is password data key in Kubernetes secret used to create Jenkins username/password credential
 | ||||||
|  | 	PasswordSecretKey = "password" | ||||||
|  | 	// PrivateKeySecretKey is private key data key in Kubernetes secret used to create Jenkins SSH credential
 | ||||||
|  | 	PrivateKeySecretKey = "privateKey" | ||||||
|  | 
 | ||||||
|  | 	// JenkinsCredentialTypeLabelName is label for kubernetes-credentials-provider-plugin which determine Jenkins
 | ||||||
|  | 	// credential type
 | ||||||
|  | 	JenkinsCredentialTypeLabelName = "jenkins.io/credentials-type" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // SeedJobs defines API for configuring and ensuring Jenkins Seed Jobs and Deploy Keys
 | // SeedJobs defines API for configuring and ensuring Jenkins Seed Jobs and Deploy Keys
 | ||||||
|  | @ -48,11 +61,15 @@ func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, logger logr. | ||||||
| 
 | 
 | ||||||
| // EnsureSeedJobs configures seed job and runs it for every entry from Jenkins.Spec.SeedJobs
 | // EnsureSeedJobs configures seed job and runs it for every entry from Jenkins.Spec.SeedJobs
 | ||||||
| func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | ||||||
| 	err = s.createJob() | 	if err = s.createJob(); err != nil { | ||||||
| 	if err != nil { |  | ||||||
| 		s.logger.V(log.VWarn).Info("Couldn't create jenkins seed job") | 		s.logger.V(log.VWarn).Info("Couldn't create jenkins seed job") | ||||||
| 		return false, err | 		return false, err | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	if err = s.ensureLabelsForSecrets(*jenkins); err != nil { | ||||||
|  | 		return false, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	done, err = s.buildJobs(jenkins) | 	done, err = s.buildJobs(jenkins) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		s.logger.V(log.VWarn).Info("Couldn't build jenkins seed job") | 		s.logger.V(log.VWarn).Info("Couldn't build jenkins seed job") | ||||||
|  | @ -73,18 +90,46 @@ func (s *SeedJobs) createJob() error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ensureLabelsForSecrets adds labels to Kubernetes secrets where are Jenkins credentials used for seed jobs,
 | ||||||
|  | // thanks to them kubernetes-credentials-provider-plugin will create Jenkins credentials in Jenkins and
 | ||||||
|  | // Operator will able to watch any changes made to them
 | ||||||
|  | func (s *SeedJobs) ensureLabelsForSecrets(jenkins v1alpha1.Jenkins) error { | ||||||
|  | 	for _, seedJob := range jenkins.Spec.SeedJobs { | ||||||
|  | 		if seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha1.UsernamePasswordCredentialType { | ||||||
|  | 			requiredLabels := resources.BuildLabelsForWatchedResources(jenkins) | ||||||
|  | 			requiredLabels[JenkinsCredentialTypeLabelName] = string(seedJob.JenkinsCredentialType) | ||||||
|  | 
 | ||||||
|  | 			secret := &corev1.Secret{} | ||||||
|  | 			namespaceName := types.NamespacedName{Namespace: jenkins.ObjectMeta.Namespace, Name: seedJob.CredentialID} | ||||||
|  | 			err := s.k8sClient.Get(context.TODO(), namespaceName, secret) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return stackerr.WithStack(err) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if !resources.VerifyIfLabelsAreSet(secret, requiredLabels) { | ||||||
|  | 				secret.ObjectMeta.Labels = requiredLabels | ||||||
|  | 				err = stackerr.WithStack(s.k8sClient.Update(context.TODO(), secret)) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // buildJobs is responsible for running jenkins builds which configures jenkins seed jobs and deploy keys
 | // buildJobs is responsible for running jenkins builds which configures jenkins seed jobs and deploy keys
 | ||||||
| func (s *SeedJobs) buildJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | func (s *SeedJobs) buildJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | ||||||
| 	allDone := true | 	allDone := true | ||||||
| 	seedJobs := jenkins.Spec.SeedJobs | 	for _, seedJob := range jenkins.Spec.SeedJobs { | ||||||
| 	for _, seedJob := range seedJobs { | 		credentialValue, err := s.credentialValue(jenkins.Namespace, seedJob) | ||||||
| 		privateKey, err := s.privateKeyFromSecret(jenkins.Namespace, seedJob) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return false, err | 			return false, err | ||||||
| 		} | 		} | ||||||
| 		parameters := map[string]string{ | 		parameters := map[string]string{ | ||||||
| 			deployKeyIDParameterName:      seedJob.ID, | 			idParameterName:               seedJob.ID, | ||||||
| 			privateKeyParameterName:       privateKey, | 			credentialIDParameterName:     seedJob.CredentialID, | ||||||
| 			repositoryURLParameterName:    seedJob.RepositoryURL, | 			repositoryURLParameterName:    seedJob.RepositoryURL, | ||||||
| 			repositoryBranchParameterName: seedJob.RepositoryBranch, | 			repositoryBranchParameterName: seedJob.RepositoryBranch, | ||||||
| 			targetsParameterName:          seedJob.Targets, | 			targetsParameterName:          seedJob.Targets, | ||||||
|  | @ -92,8 +137,9 @@ func (s *SeedJobs) buildJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		hash := sha256.New() | 		hash := sha256.New() | ||||||
| 		hash.Write([]byte(parameters[deployKeyIDParameterName])) | 		hash.Write([]byte(parameters[idParameterName])) | ||||||
| 		hash.Write([]byte(parameters[privateKeyParameterName])) | 		hash.Write([]byte(parameters[credentialIDParameterName])) | ||||||
|  | 		hash.Write([]byte(credentialValue)) | ||||||
| 		hash.Write([]byte(parameters[repositoryURLParameterName])) | 		hash.Write([]byte(parameters[repositoryURLParameterName])) | ||||||
| 		hash.Write([]byte(parameters[repositoryBranchParameterName])) | 		hash.Write([]byte(parameters[repositoryBranchParameterName])) | ||||||
| 		hash.Write([]byte(parameters[targetsParameterName])) | 		hash.Write([]byte(parameters[targetsParameterName])) | ||||||
|  | @ -112,21 +158,23 @@ func (s *SeedJobs) buildJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | ||||||
| 	return allDone, nil | 	return allDone, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // privateKeyFromSecret it's utility function which extracts deploy key from the kubernetes secret
 | func (s *SeedJobs) credentialValue(namespace string, seedJob v1alpha1.SeedJob) (string, error) { | ||||||
| func (s *SeedJobs) privateKeyFromSecret(namespace string, seedJob v1alpha1.SeedJob) (string, error) { | 	if seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha1.UsernamePasswordCredentialType { | ||||||
| 	if seedJob.PrivateKey.SecretKeyRef != nil { | 		secret := &corev1.Secret{} | ||||||
| 		deployKeySecret := &v1.Secret{} | 		namespaceName := types.NamespacedName{Namespace: namespace, Name: seedJob.CredentialID} | ||||||
| 		namespaceName := types.NamespacedName{Namespace: namespace, Name: seedJob.PrivateKey.SecretKeyRef.Name} | 		err := s.k8sClient.Get(context.TODO(), namespaceName, secret) | ||||||
| 		err := s.k8sClient.Get(context.TODO(), namespaceName, deployKeySecret) |  | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return "", err | 			return "", err | ||||||
| 		} | 		} | ||||||
| 		return string(deployKeySecret.Data[seedJob.PrivateKey.SecretKeyRef.Key]), nil | 
 | ||||||
|  | 		if seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType { | ||||||
|  | 			return string(secret.Data[PrivateKeySecretKey]), nil | ||||||
|  | 		} | ||||||
|  | 		return string(secret.Data[UsernameSecretKey]) + string(secret.Data[PasswordSecretKey]), nil | ||||||
| 	} | 	} | ||||||
| 	return "", nil | 	return "", nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FIXME(antoniaklja) use mask-password plugin for params.PRIVATE_KEY
 |  | ||||||
| // seedJobConfigXML this is the XML representation of seed job
 | // seedJobConfigXML this is the XML representation of seed job
 | ||||||
| var seedJobConfigXML = ` | var seedJobConfigXML = ` | ||||||
| <flow-definition plugin="workflow-job@2.30"> | <flow-definition plugin="workflow-job@2.30"> | ||||||
|  | @ -137,15 +185,16 @@ var seedJobConfigXML = ` | ||||||
|     <hudson.model.ParametersDefinitionProperty> |     <hudson.model.ParametersDefinitionProperty> | ||||||
|       <parameterDefinitions> |       <parameterDefinitions> | ||||||
|         <hudson.model.StringParameterDefinition> |         <hudson.model.StringParameterDefinition> | ||||||
|           <name>` + deployKeyIDParameterName + `</name> |           <name>` + idParameterName + `</name> | ||||||
|           <description></description> |           <description></description> | ||||||
|           <defaultValue></defaultValue> |           <defaultValue></defaultValue> | ||||||
|           <trim>false</trim> |           <trim>false</trim> | ||||||
|         </hudson.model.StringParameterDefinition> |         </hudson.model.StringParameterDefinition> | ||||||
|         <hudson.model.StringParameterDefinition> |         <hudson.model.StringParameterDefinition> | ||||||
|           <name>` + privateKeyParameterName + `</name> |           <name>` + credentialIDParameterName + `</name> | ||||||
|           <description></description> |           <description></description> | ||||||
|           <defaultValue></defaultValue> |           <defaultValue></defaultValue> | ||||||
|  |           <trim>false</trim> | ||||||
|         </hudson.model.StringParameterDefinition> |         </hudson.model.StringParameterDefinition> | ||||||
|         <hudson.model.StringParameterDefinition> |         <hudson.model.StringParameterDefinition> | ||||||
|           <name>` + repositoryURLParameterName + `</name> |           <name>` + repositoryURLParameterName + `</name> | ||||||
|  | @ -175,11 +224,7 @@ var seedJobConfigXML = ` | ||||||
|     </hudson.model.ParametersDefinitionProperty> |     </hudson.model.ParametersDefinitionProperty> | ||||||
|   </properties> |   </properties> | ||||||
|   <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61"> |   <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61"> | ||||||
|     <script>import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey |     <script> | ||||||
| import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey.DirectEntryPrivateKeySource |  | ||||||
| import com.cloudbees.plugins.credentials.CredentialsScope |  | ||||||
| import com.cloudbees.plugins.credentials.SystemCredentialsProvider |  | ||||||
| import com.cloudbees.plugins.credentials.domains.Domain |  | ||||||
| import hudson.model.FreeStyleProject | import hudson.model.FreeStyleProject | ||||||
| import hudson.model.labels.LabelAtom | import hudson.model.labels.LabelAtom | ||||||
| import hudson.plugins.git.BranchSpec | import hudson.plugins.git.BranchSpec | ||||||
|  | @ -190,36 +235,19 @@ import javaposse.jobdsl.plugin.ExecuteDslScripts | ||||||
| import javaposse.jobdsl.plugin.LookupStrategy | import javaposse.jobdsl.plugin.LookupStrategy | ||||||
| import javaposse.jobdsl.plugin.RemovedJobAction | import javaposse.jobdsl.plugin.RemovedJobAction | ||||||
| import javaposse.jobdsl.plugin.RemovedViewAction | import javaposse.jobdsl.plugin.RemovedViewAction | ||||||
| import jenkins.model.Jenkins |  | ||||||
| import javaposse.jobdsl.plugin.GlobalJobDslSecurityConfiguration |  | ||||||
| import jenkins.model.GlobalConfiguration |  | ||||||
| 
 | 
 | ||||||
| import static com.google.common.collect.Lists.newArrayList | import static com.google.common.collect.Lists.newArrayList | ||||||
| 
 | 
 | ||||||
| // https://javadoc.jenkins.io/plugin/ssh-credentials/com/cloudbees/jenkins/plugins/sshcredentials/impl/BasicSSHUserPrivateKey.html
 |  | ||||||
| BasicSSHUserPrivateKey deployKeyPrivate = new BasicSSHUserPrivateKey( |  | ||||||
|         CredentialsScope.GLOBAL, |  | ||||||
|         "${params.DEPLOY_KEY_ID}", |  | ||||||
|         "git", |  | ||||||
|         new DirectEntryPrivateKeySource("${params.PRIVATE_KEY}"), |  | ||||||
|         "", |  | ||||||
|         "${params.DEPLOY_KEY_ID}" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // https://javadoc.jenkins.io/plugin/credentials/index.html?com/cloudbees/plugins/credentials/SystemCredentialsProvider.html
 |  | ||||||
| SystemCredentialsProvider.getInstance().getStore().addCredentials(Domain.global(), deployKeyPrivate) |  | ||||||
| 
 |  | ||||||
| Jenkins jenkins = Jenkins.instance | Jenkins jenkins = Jenkins.instance | ||||||
| 
 | 
 | ||||||
| def jobDslSeedName = "${params.DEPLOY_KEY_ID}-` + constants.SeedJobSuffix + `" | def jobDslSeedName = "${params.` + idParameterName + `}-` + constants.SeedJobSuffix + `" | ||||||
| def jobDslDeployKeyName = "${params.DEPLOY_KEY_ID}" |  | ||||||
| def jobRef = jenkins.getItem(jobDslSeedName) | def jobRef = jenkins.getItem(jobDslSeedName) | ||||||
| 
 | 
 | ||||||
| def repoList = GitSCM.createRepoList("${params.REPOSITORY_URL}", jobDslDeployKeyName) | def repoList = GitSCM.createRepoList("${params.` + repositoryURLParameterName + `}", "${params.` + credentialIDParameterName + `}") | ||||||
| def gitExtensions = [new CloneOption(true, true, "", 10)] | def gitExtensions = [new CloneOption(true, true, "", 10)] | ||||||
| def scm = new GitSCM( | def scm = new GitSCM( | ||||||
|         repoList, |         repoList, | ||||||
|         newArrayList(new BranchSpec("${params.REPOSITORY_BRANCH}")), |         newArrayList(new BranchSpec("${params.` + repositoryBranchParameterName + `}")), | ||||||
|         false, |         false, | ||||||
|         Collections.<SubmoduleConfig> emptyList(), |         Collections.<SubmoduleConfig> emptyList(), | ||||||
|         null, |         null, | ||||||
|  | @ -228,7 +256,7 @@ def scm = new GitSCM( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| def executeDslScripts = new ExecuteDslScripts() | def executeDslScripts = new ExecuteDslScripts() | ||||||
| executeDslScripts.setTargets("${params.TARGETS}") | executeDslScripts.setTargets("${params.` + targetsParameterName + `}") | ||||||
| executeDslScripts.setSandbox(false) | executeDslScripts.setSandbox(false) | ||||||
| executeDslScripts.setRemovedJobAction(RemovedJobAction.DELETE) | executeDslScripts.setRemovedJobAction(RemovedJobAction.DELETE) | ||||||
| executeDslScripts.setRemovedViewAction(RemovedViewAction.DELETE) | executeDslScripts.setRemovedViewAction(RemovedViewAction.DELETE) | ||||||
|  | @ -240,13 +268,11 @@ if (jobRef == null) { | ||||||
| } | } | ||||||
| jobRef.getBuildersList().clear() | jobRef.getBuildersList().clear() | ||||||
| jobRef.getBuildersList().add(executeDslScripts) | jobRef.getBuildersList().add(executeDslScripts) | ||||||
| jobRef.setDisplayName("${params.SEED_JOB_DISPLAY_NAME}") | jobRef.setDisplayName("${params.` + displayNameParameterName + `}") | ||||||
| jobRef.setScm(scm) | jobRef.setScm(scm) | ||||||
|  | // TODO don't use master executors
 | ||||||
| jobRef.setAssignedLabel(new LabelAtom("master")) | jobRef.setAssignedLabel(new LabelAtom("master")) | ||||||
| 
 | 
 | ||||||
| // disable Job DSL script approval
 |  | ||||||
| GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).useScriptSecurity=false |  | ||||||
| GlobalConfiguration.all().get(GlobalJobDslSecurityConfiguration.class).save() |  | ||||||
| jenkins.getQueue().schedule(jobRef) | jenkins.getQueue().schedule(jobRef) | ||||||
| </script> | </script> | ||||||
|     <sandbox>false</sandbox> |     <sandbox>false</sandbox> | ||||||
|  |  | ||||||
|  | @ -135,6 +135,7 @@ func jenkinsCustomResource() *v1alpha1.Jenkins { | ||||||
| 			SeedJobs: []v1alpha1.SeedJob{ | 			SeedJobs: []v1alpha1.SeedJob{ | ||||||
| 				{ | 				{ | ||||||
| 					ID: "jenkins-operator-e2e", | 					ID: "jenkins-operator-e2e", | ||||||
|  | 					JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||||
| 					Targets:               "cicd/jobs/*.jenkins", | 					Targets:               "cicd/jobs/*.jenkins", | ||||||
| 					Description:           "Jenkins Operator e2e tests repository", | 					Description:           "Jenkins Operator e2e tests repository", | ||||||
| 					RepositoryBranch:      "master", | 					RepositoryBranch:      "master", | ||||||
|  |  | ||||||
|  | @ -8,8 +8,10 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/go-logr/logr" | ||||||
| 	stackerr "github.com/pkg/errors" | 	stackerr "github.com/pkg/errors" | ||||||
| 	"k8s.io/api/core/v1" | 	"k8s.io/api/core/v1" | ||||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||||
|  | @ -28,52 +30,129 @@ func (r *ReconcileUserConfiguration) Validate(jenkins *v1alpha1.Jenkins) (bool, | ||||||
| 
 | 
 | ||||||
| func (r *ReconcileUserConfiguration) validateSeedJobs(jenkins *v1alpha1.Jenkins) (bool, error) { | func (r *ReconcileUserConfiguration) validateSeedJobs(jenkins *v1alpha1.Jenkins) (bool, error) { | ||||||
| 	valid := true | 	valid := true | ||||||
|  | 
 | ||||||
|  | 	// TODO id must be unique
 | ||||||
| 	if jenkins.Spec.SeedJobs != nil { | 	if jenkins.Spec.SeedJobs != nil { | ||||||
| 		for _, seedJob := range jenkins.Spec.SeedJobs { | 		for _, seedJob := range jenkins.Spec.SeedJobs { | ||||||
| 			logger := r.logger.WithValues("seedJob", fmt.Sprintf("%+v", seedJob)).V(log.VWarn) | 			logger := r.logger.WithValues("seedJob", fmt.Sprintf("%+v", seedJob)).V(log.VWarn) | ||||||
| 
 | 
 | ||||||
| 			// validate seed job id is not empty
 |  | ||||||
| 			if len(seedJob.ID) == 0 { | 			if len(seedJob.ID) == 0 { | ||||||
| 				logger.Info("seed job id can't be empty") | 				logger.Info("id can't be empty") | ||||||
|  | 				valid = false | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if len(seedJob.RepositoryBranch) == 0 { | ||||||
|  | 				logger.Info("repository branch can't be empty") | ||||||
|  | 				valid = false | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if len(seedJob.RepositoryURL) == 0 { | ||||||
|  | 				logger.Info("repository URL branch can't be empty") | ||||||
|  | 				valid = false | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if len(seedJob.Targets) == 0 { | ||||||
|  | 				logger.Info("targets can't be empty") | ||||||
|  | 				valid = false | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if _, ok := v1alpha1.AllowedJenkinsCredentialMap[string(seedJob.JenkinsCredentialType)]; !ok { | ||||||
|  | 				logger.Info("unknown credential type") | ||||||
|  | 				return false, nil | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType || | ||||||
|  | 				seedJob.JenkinsCredentialType == v1alpha1.UsernamePasswordCredentialType) && len(seedJob.CredentialID) == 0 { | ||||||
|  | 				logger.Info("credential ID can't be empty") | ||||||
| 				valid = false | 				valid = false | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// validate repository url match private key
 | 			// validate repository url match private key
 | ||||||
| 			if strings.Contains(seedJob.RepositoryURL, "git@") { | 			if strings.Contains(seedJob.RepositoryURL, "git@") && seedJob.JenkinsCredentialType == v1alpha1.NoJenkinsCredentialCredentialType { | ||||||
| 				if seedJob.PrivateKey.SecretKeyRef == nil { | 				logger.Info("Jenkins credential must be set while using ssh repository url") | ||||||
| 					logger.Info("private key can't be empty while using ssh repository url") |  | ||||||
| 				valid = false | 				valid = false | ||||||
| 			} | 			} | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			// validate private key from secret
 | 			if seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha1.UsernamePasswordCredentialType { | ||||||
| 			if seedJob.PrivateKey.SecretKeyRef != nil { | 				secret := &v1.Secret{} | ||||||
| 				deployKeySecret := &v1.Secret{} | 				namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: seedJob.CredentialID} | ||||||
| 				namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: seedJob.PrivateKey.SecretKeyRef.Name} | 				err := r.k8sClient.Get(context.TODO(), namespaceName, secret) | ||||||
| 				err := r.k8sClient.Get(context.TODO(), namespaceName, deployKeySecret) |  | ||||||
| 				if err != nil && apierrors.IsNotFound(err) { | 				if err != nil && apierrors.IsNotFound(err) { | ||||||
| 					logger.Info("secret not found") | 					logger.Info(fmt.Sprintf("required secret '%s' with Jenkins credential not found", seedJob.CredentialID)) | ||||||
| 					valid = false | 					return false, nil | ||||||
| 				} else if err != nil { | 				} else if err != nil { | ||||||
| 					return false, stackerr.WithStack(err) | 					return false, stackerr.WithStack(err) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				privateKey := string(deployKeySecret.Data[seedJob.PrivateKey.SecretKeyRef.Key]) | 				if seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType { | ||||||
| 				if privateKey == "" { | 					if ok := validateBasicSSHSecret(logger, *secret); !ok { | ||||||
| 					logger.Info("private key is empty") |  | ||||||
| 						valid = false | 						valid = false | ||||||
| 					} | 					} | ||||||
| 
 | 				} | ||||||
| 				if err := validatePrivateKey(privateKey); err != nil { | 				if seedJob.JenkinsCredentialType == v1alpha1.UsernamePasswordCredentialType { | ||||||
| 					logger.Info(fmt.Sprintf("private key is invalid: %s", err)) | 					if ok := validateUsernamePasswordSecret(logger, *secret); !ok { | ||||||
| 						valid = false | 						valid = false | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | 	} | ||||||
| 	return valid, nil | 	return valid, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func validateBasicSSHSecret(logger logr.InfoLogger, secret v1.Secret) bool { | ||||||
|  | 	valid := true | ||||||
|  | 	username, exists := secret.Data[seedjobs.UsernameSecretKey] | ||||||
|  | 	if !exists { | ||||||
|  | 		logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", seedjobs.UsernameSecretKey, secret.ObjectMeta.Name)) | ||||||
|  | 		valid = false | ||||||
|  | 	} | ||||||
|  | 	if len(username) == 0 { | ||||||
|  | 		logger.Info(fmt.Sprintf("required data '%s' is empty in secret '%s'", seedjobs.UsernameSecretKey, secret.ObjectMeta.Name)) | ||||||
|  | 		valid = false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	privateKey, exists := secret.Data[seedjobs.PrivateKeySecretKey] | ||||||
|  | 	if !exists { | ||||||
|  | 		logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", seedjobs.PrivateKeySecretKey, secret.ObjectMeta.Name)) | ||||||
|  | 		valid = false | ||||||
|  | 	} | ||||||
|  | 	if len(string(privateKey)) == 0 { | ||||||
|  | 		logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", seedjobs.PrivateKeySecretKey, secret.ObjectMeta.Name)) | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	if err := validatePrivateKey(string(privateKey)); err != nil { | ||||||
|  | 		logger.Info(fmt.Sprintf("private key '%s' invalid in secret '%s': %s", seedjobs.PrivateKeySecretKey, secret.ObjectMeta.Name, err)) | ||||||
|  | 		valid = false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return valid | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func validateUsernamePasswordSecret(logger logr.InfoLogger, secret v1.Secret) bool { | ||||||
|  | 	valid := true | ||||||
|  | 	username, exists := secret.Data[seedjobs.UsernameSecretKey] | ||||||
|  | 	if !exists { | ||||||
|  | 		logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", seedjobs.UsernameSecretKey, secret.ObjectMeta.Name)) | ||||||
|  | 		valid = false | ||||||
|  | 	} | ||||||
|  | 	if len(username) == 0 { | ||||||
|  | 		logger.Info(fmt.Sprintf("required data '%s' is empty in secret '%s'", seedjobs.UsernameSecretKey, secret.ObjectMeta.Name)) | ||||||
|  | 		valid = false | ||||||
|  | 	} | ||||||
|  | 	password, exists := secret.Data[seedjobs.PasswordSecretKey] | ||||||
|  | 	if !exists { | ||||||
|  | 		logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", seedjobs.PasswordSecretKey, secret.ObjectMeta.Name)) | ||||||
|  | 		valid = false | ||||||
|  | 	} | ||||||
|  | 	if len(password) == 0 { | ||||||
|  | 		logger.Info(fmt.Sprintf("required data '%s' is empty in secret '%s'", seedjobs.PasswordSecretKey, secret.ObjectMeta.Name)) | ||||||
|  | 		valid = false | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return valid | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func validatePrivateKey(privateKey string) error { | func validatePrivateKey(privateKey string) error { | ||||||
| 	block, _ := pem.Decode([]byte(privateKey)) | 	block, _ := pem.Decode([]byte(privateKey)) | ||||||
| 	if block == nil { | 	if block == nil { | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ package user | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" |  | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" | ||||||
| 
 | 
 | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | @ -40,8 +40,7 @@ JjihnIfoDu9MfU24GWDw49wGPTn+eI7GQC+8yxGg7fd24kohHSaCowoW16pbYVco | ||||||
| 6iLr5rkCgYBt0bcYJ3AOTH0UXS8kvJvnyce/RBIAMoUABwvdkZt9r5B4UzsoLq5e | 6iLr5rkCgYBt0bcYJ3AOTH0UXS8kvJvnyce/RBIAMoUABwvdkZt9r5B4UzsoLq5e | ||||||
| WrrU6fSRsE6lSsBd83pOAQ46tv+vntQ+0EihD9/0INhkQM99lBw1TFdFTgGSAs1e | WrrU6fSRsE6lSsBd83pOAQ46tv+vntQ+0EihD9/0INhkQM99lBw1TFdFTgGSAs1e | ||||||
| ns4JGP6f5uIuwqu/nbqPqMyDovjkGbX2znuGBcvki90Pi97XL7MMWw== | ns4JGP6f5uIuwqu/nbqPqMyDovjkGbX2znuGBcvki90Pi97XL7MMWw== | ||||||
| -----END RSA PRIVATE KEY----- | -----END RSA PRIVATE KEY-----` | ||||||
| ` |  | ||||||
| 
 | 
 | ||||||
| var fakeInvalidPrivateKey = `-----BEGIN RSA PRIVATE KEY----- | var fakeInvalidPrivateKey = `-----BEGIN RSA PRIVATE KEY----- | ||||||
| MIIEpAIBAAKCAQEArK4ld6i2iqW6L3jaTZaKD/v7PjDn+Ik9MXp+kvLcUw/+wEGm | MIIEpAIBAAKCAQEArK4ld6i2iqW6L3jaTZaKD/v7PjDn+Ik9MXp+kvLcUw/+wEGm | ||||||
|  | @ -50,189 +49,398 @@ SwiLd8TWAvXkxdXm8fDOGAZbYK2alMV+M+9E2OpZsBUCxmb/3FAofF6JccKoJOH8 | ||||||
| ` | ` | ||||||
| 
 | 
 | ||||||
| func TestValidateSeedJobs(t *testing.T) { | func TestValidateSeedJobs(t *testing.T) { | ||||||
| 	data := []struct { | 	secretTypeMeta := metav1.TypeMeta{ | ||||||
| 		description    string |  | ||||||
| 		jenkins        *v1alpha1.Jenkins |  | ||||||
| 		secret         *corev1.Secret |  | ||||||
| 		expectedResult bool |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			description: "Valid with public repository and without private key", |  | ||||||
| 			jenkins: &v1alpha1.Jenkins{ |  | ||||||
| 				Spec: v1alpha1.JenkinsSpec{ |  | ||||||
| 					SeedJobs: []v1alpha1.SeedJob{ |  | ||||||
| 						{ |  | ||||||
| 							ID:               "jenkins-operator-e2e", |  | ||||||
| 							Targets:          "cicd/jobs/*.jenkins", |  | ||||||
| 							Description:      "Jenkins Operator e2e tests repository", |  | ||||||
| 							RepositoryBranch: "master", |  | ||||||
| 							RepositoryURL:    "https://github.com/jenkinsci/kubernetes-operator.git", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			expectedResult: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			description: "Invalid without id", |  | ||||||
| 			jenkins: &v1alpha1.Jenkins{ |  | ||||||
| 				Spec: v1alpha1.JenkinsSpec{ |  | ||||||
| 					SeedJobs: []v1alpha1.SeedJob{ |  | ||||||
| 						{ |  | ||||||
| 							Targets:          "cicd/jobs/*.jenkins", |  | ||||||
| 							Description:      "Jenkins Operator e2e tests repository", |  | ||||||
| 							RepositoryBranch: "master", |  | ||||||
| 							RepositoryURL:    "https://github.com/jenkinsci/kubernetes-operator.git", |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			expectedResult: false, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			description: "Valid with private key and secret", |  | ||||||
| 			jenkins: &v1alpha1.Jenkins{ |  | ||||||
| 				Spec: v1alpha1.JenkinsSpec{ |  | ||||||
| 					SeedJobs: []v1alpha1.SeedJob{ |  | ||||||
| 						{ |  | ||||||
| 							ID:               "jenkins-operator-e2e", |  | ||||||
| 							Targets:          "cicd/jobs/*.jenkins", |  | ||||||
| 							Description:      "Jenkins Operator e2e tests repository", |  | ||||||
| 							RepositoryBranch: "master", |  | ||||||
| 							RepositoryURL:    "https://github.com/jenkinsci/kubernetes-operator.git", |  | ||||||
| 							PrivateKey: v1alpha1.PrivateKey{ |  | ||||||
| 								SecretKeyRef: &corev1.SecretKeySelector{ |  | ||||||
| 									LocalObjectReference: corev1.LocalObjectReference{ |  | ||||||
| 										Name: "deploy-keys", |  | ||||||
| 									}, |  | ||||||
| 									Key: "jenkins-operator-e2e", |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			secret: &corev1.Secret{ |  | ||||||
| 				TypeMeta: metav1.TypeMeta{ |  | ||||||
| 		Kind:       "Secret", | 		Kind:       "Secret", | ||||||
| 		APIVersion: "v1", | 		APIVersion: "v1", | ||||||
| 				}, | 	} | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ | 	secretObjectMeta := metav1.ObjectMeta{ | ||||||
| 		Name:      "deploy-keys", | 		Name:      "deploy-keys", | ||||||
| 		Namespace: "default", | 		Namespace: "default", | ||||||
| 				}, | 	} | ||||||
| 				Data: map[string][]byte{ | 	t.Run("Valid with public repository and without private key", func(t *testing.T) { | ||||||
| 					"jenkins-operator-e2e": []byte(fakePrivateKey), | 		jenkins := &v1alpha1.Jenkins{ | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			expectedResult: true, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			description: "Invalid private key in secret", |  | ||||||
| 			jenkins: &v1alpha1.Jenkins{ |  | ||||||
| 			Spec: v1alpha1.JenkinsSpec{ | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
| 				SeedJobs: []v1alpha1.SeedJob{ | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
| 					{ | 					{ | ||||||
| 							ID:               "jenkins-operator-e2e", | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "jenkins-operator-e2e", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||||
| 						Targets:               "cicd/jobs/*.jenkins", | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
| 							Description:      "Jenkins Operator e2e tests repository", |  | ||||||
| 						RepositoryBranch:      "master", | 						RepositoryBranch:      "master", | ||||||
| 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
| 							PrivateKey: v1alpha1.PrivateKey{ |  | ||||||
| 								SecretKeyRef: &corev1.SecretKeySelector{ |  | ||||||
| 									LocalObjectReference: corev1.LocalObjectReference{ |  | ||||||
| 										Name: "deploy-keys", |  | ||||||
| 									}, |  | ||||||
| 									Key: "jenkins-operator-e2e", |  | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 					}, | 		} | ||||||
| 				}, | 
 | ||||||
| 			}, | 		userReconcileLoop := New(fake.NewFakeClient(), nil, logf.ZapLogger(false), nil) | ||||||
| 			secret: &corev1.Secret{ | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
| 				TypeMeta: metav1.TypeMeta{ | 
 | ||||||
| 					Kind:       "Secret", | 		assert.NoError(t, err) | ||||||
| 					APIVersion: "v1", | 		assert.Equal(t, true, result) | ||||||
| 				}, | 	}) | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ | 	t.Run("Invalid without id", func(t *testing.T) { | ||||||
| 					Name:      "deploy-keys", | 		jenkins := &v1alpha1.Jenkins{ | ||||||
| 					Namespace: "default", |  | ||||||
| 				}, |  | ||||||
| 				Data: map[string][]byte{ |  | ||||||
| 					"jenkins-operator-e2e": []byte(fakeInvalidPrivateKey), |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			expectedResult: false, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			description: "Invalid with PrivateKey and empty Secret data", |  | ||||||
| 			jenkins: &v1alpha1.Jenkins{ |  | ||||||
| 			Spec: v1alpha1.JenkinsSpec{ | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
| 				SeedJobs: []v1alpha1.SeedJob{ | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
| 					{ | 					{ | ||||||
| 							ID:               "jenkins-operator-e2e", | 						JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||||
| 						Targets:               "cicd/jobs/*.jenkins", | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
| 							Description:      "Jenkins Operator e2e tests repository", |  | ||||||
| 						RepositoryBranch:      "master", | 						RepositoryBranch:      "master", | ||||||
| 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
| 							PrivateKey: v1alpha1.PrivateKey{ |  | ||||||
| 								SecretKeyRef: &corev1.SecretKeySelector{ |  | ||||||
| 									LocalObjectReference: corev1.LocalObjectReference{ |  | ||||||
| 										Name: "deploy-keys", |  | ||||||
| 									}, |  | ||||||
| 									Key: "jenkins-operator-e2e", |  | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 					}, | 		} | ||||||
| 				}, | 
 | ||||||
| 			}, | 		userReconcileLoop := New(fake.NewFakeClient(), nil, logf.ZapLogger(false), nil) | ||||||
| 			secret: &corev1.Secret{ | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
| 				TypeMeta: metav1.TypeMeta{ | 
 | ||||||
| 					Kind:       "Secret", | 		assert.NoError(t, err) | ||||||
| 					APIVersion: "v1", | 		assert.Equal(t, false, result) | ||||||
| 				}, | 	}) | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ | 	t.Run("Valid with private key and secret", func(t *testing.T) { | ||||||
| 					Name:      "deploy-keys", | 		jenkins := &v1alpha1.Jenkins{ | ||||||
| 					Namespace: "default", |  | ||||||
| 				}, |  | ||||||
| 				Data: map[string][]byte{ |  | ||||||
| 					"jenkins-operator-e2e": []byte(""), |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			expectedResult: false, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			description: "Invalid with ssh RepositoryURL and empty PrivateKey", |  | ||||||
| 			jenkins: &v1alpha1.Jenkins{ |  | ||||||
| 			Spec: v1alpha1.JenkinsSpec{ | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
| 				SeedJobs: []v1alpha1.SeedJob{ | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
| 					{ | 					{ | ||||||
| 							ID:               "jenkins-operator-e2e", | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "deploy-keys", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.BasicSSHCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		secret := &corev1.Secret{ | ||||||
|  | 			TypeMeta:   secretTypeMeta, | ||||||
|  | 			ObjectMeta: secretObjectMeta, | ||||||
|  | 			Data: map[string][]byte{ | ||||||
|  | 				seedjobs.UsernameSecretKey:   []byte("username"), | ||||||
|  | 				seedjobs.PrivateKeySecretKey: []byte(fakePrivateKey), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), secret) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, true, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid private key in secret", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "deploy-keys", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.BasicSSHCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		secret := &corev1.Secret{ | ||||||
|  | 			TypeMeta:   secretTypeMeta, | ||||||
|  | 			ObjectMeta: secretObjectMeta, | ||||||
|  | 			Data: map[string][]byte{ | ||||||
|  | 				seedjobs.UsernameSecretKey:   []byte("username"), | ||||||
|  | 				seedjobs.PrivateKeySecretKey: []byte(fakeInvalidPrivateKey), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), secret) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, false, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid with PrivateKey and empty Secret data", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "deploy-keys", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.BasicSSHCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		secret := &corev1.Secret{ | ||||||
|  | 			TypeMeta:   secretTypeMeta, | ||||||
|  | 			ObjectMeta: secretObjectMeta, | ||||||
|  | 			Data: map[string][]byte{ | ||||||
|  | 				seedjobs.UsernameSecretKey:   []byte("username"), | ||||||
|  | 				seedjobs.PrivateKeySecretKey: []byte(""), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), secret) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, false, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid with ssh RepositoryURL and empty PrivateKey", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "jenkins-operator-e2e", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.BasicSSHCredentialType, | ||||||
| 						Targets:               "cicd/jobs/*.jenkins", | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
| 							Description:      "Jenkins Operator e2e tests repository", |  | ||||||
| 						RepositoryBranch:      "master", | 						RepositoryBranch:      "master", | ||||||
| 						RepositoryURL:         "git@github.com:jenkinsci/kubernetes-operator.git", | 						RepositoryURL:         "git@github.com:jenkinsci/kubernetes-operator.git", | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fake.NewFakeClient(), nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, false, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid without targets", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID: "example", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			expectedResult: false, |  | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 	for _, testingData := range data { | 		userReconcileLoop := New(fake.NewFakeClient(), nil, logf.ZapLogger(false), nil) | ||||||
| 		t.Run(fmt.Sprintf("Testing '%s'", testingData.description), func(t *testing.T) { | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
| 			fakeClient := fake.NewFakeClient() | 
 | ||||||
| 			if testingData.secret != nil { |  | ||||||
| 				err := fakeClient.Create(context.TODO(), testingData.secret) |  | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, false, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid without repository URL", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID: "example", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
| 		} | 		} | ||||||
| 			userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | 
 | ||||||
| 			result, err := userReconcileLoop.validateSeedJobs(testingData.jenkins) | 		userReconcileLoop := New(fake.NewFakeClient(), nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 			assert.Equal(t, testingData.expectedResult, result) | 		assert.Equal(t, false, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid without repository branch", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID: "example", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fake.NewFakeClient(), nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, false, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Valid with username and password", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "deploy-keys", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.UsernamePasswordCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		secret := &corev1.Secret{ | ||||||
|  | 			TypeMeta:   secretTypeMeta, | ||||||
|  | 			ObjectMeta: secretObjectMeta, | ||||||
|  | 			Data: map[string][]byte{ | ||||||
|  | 				seedjobs.UsernameSecretKey: []byte("some-username"), | ||||||
|  | 				seedjobs.PasswordSecretKey: []byte("some-password"), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), secret) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, true, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid with empty username", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "deploy-keys", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.UsernamePasswordCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		secret := &corev1.Secret{ | ||||||
|  | 			TypeMeta:   secretTypeMeta, | ||||||
|  | 			ObjectMeta: secretObjectMeta, | ||||||
|  | 			Data: map[string][]byte{ | ||||||
|  | 				seedjobs.UsernameSecretKey: []byte(""), | ||||||
|  | 				seedjobs.PasswordSecretKey: []byte("some-password"), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), secret) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, false, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid with empty password", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "deploy-keys", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.UsernamePasswordCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		secret := &corev1.Secret{ | ||||||
|  | 			TypeMeta:   secretTypeMeta, | ||||||
|  | 			ObjectMeta: secretObjectMeta, | ||||||
|  | 			Data: map[string][]byte{ | ||||||
|  | 				seedjobs.UsernameSecretKey: []byte("some-username"), | ||||||
|  | 				seedjobs.PasswordSecretKey: []byte(""), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), secret) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, false, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid without username", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "deploy-keys", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.UsernamePasswordCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		secret := &corev1.Secret{ | ||||||
|  | 			TypeMeta:   secretTypeMeta, | ||||||
|  | 			ObjectMeta: secretObjectMeta, | ||||||
|  | 			Data: map[string][]byte{ | ||||||
|  | 				seedjobs.PasswordSecretKey: []byte("some-password"), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), secret) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, false, result) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("Invalid without password", func(t *testing.T) { | ||||||
|  | 		jenkins := &v1alpha1.Jenkins{ | ||||||
|  | 			Spec: v1alpha1.JenkinsSpec{ | ||||||
|  | 				SeedJobs: []v1alpha1.SeedJob{ | ||||||
|  | 					{ | ||||||
|  | 						ID:                    "example", | ||||||
|  | 						CredentialID:          "deploy-keys", | ||||||
|  | 						JenkinsCredentialType: v1alpha1.UsernamePasswordCredentialType, | ||||||
|  | 						Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 						RepositoryBranch:      "master", | ||||||
|  | 						RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		secret := &corev1.Secret{ | ||||||
|  | 			TypeMeta:   secretTypeMeta, | ||||||
|  | 			ObjectMeta: secretObjectMeta, | ||||||
|  | 			Data: map[string][]byte{ | ||||||
|  | 				seedjobs.UsernameSecretKey: []byte("some-username"), | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		fakeClient := fake.NewFakeClient() | ||||||
|  | 		err := fakeClient.Create(context.TODO(), secret) | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 		userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | ||||||
|  | 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||||
|  | 
 | ||||||
|  | 		assert.NoError(t, err) | ||||||
|  | 		assert.Equal(t, false, result) | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -403,6 +403,7 @@ func jenkinsCustomResource() *v1alpha1.Jenkins { | ||||||
| 			SeedJobs: []v1alpha1.SeedJob{ | 			SeedJobs: []v1alpha1.SeedJob{ | ||||||
| 				{ | 				{ | ||||||
| 					ID: "jenkins-operator-e2e", | 					ID: "jenkins-operator-e2e", | ||||||
|  | 					JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||||
| 					Targets:               "cicd/jobs/*.jenkins", | 					Targets:               "cicd/jobs/*.jenkins", | ||||||
| 					Description:           "Jenkins Operator e2e tests repository", | 					Description:           "Jenkins Operator e2e tests repository", | ||||||
| 					RepositoryBranch:      "master", | 					RepositoryBranch:      "master", | ||||||
|  |  | ||||||
|  | @ -5,12 +5,10 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" |  | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||||
| 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" |  | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||||
| 
 | 
 | ||||||
| 	"github.com/bndr/gojenkins" | 	"github.com/bndr/gojenkins" | ||||||
|  | @ -19,8 +17,6 @@ import ( | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/api/resource" | 	"k8s.io/apimachinery/pkg/api/resource" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/types" |  | ||||||
| 	"k8s.io/apimachinery/pkg/util/wait" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestConfiguration(t *testing.T) { | func TestConfiguration(t *testing.T) { | ||||||
|  | @ -33,14 +29,24 @@ func TestConfiguration(t *testing.T) { | ||||||
| 	numberOfExecutors := 6 | 	numberOfExecutors := 6 | ||||||
| 	systemMessage := "Configuration as Code integration works!!!" | 	systemMessage := "Configuration as Code integration works!!!" | ||||||
| 	systemMessageEnvName := "SYSTEM_MESSAGE" | 	systemMessageEnvName := "SYSTEM_MESSAGE" | ||||||
| 	jenkinsCredentialName := "kubernetes-credentials-provider-plugin" | 	mySeedJob := seedJobConfig{ | ||||||
|  | 		SeedJob: v1alpha1.SeedJob{ | ||||||
|  | 			ID:                    "jenkins-operator", | ||||||
|  | 			CredentialID:          "jenkins-operator", | ||||||
|  | 			JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||||
|  | 			Targets:               "cicd/jobs/*.jenkins", | ||||||
|  | 			Description:           "Jenkins Operator repository", | ||||||
|  | 			RepositoryBranch:      "master", | ||||||
|  | 			RepositoryURL:         "https://github.com/jenkinsci/kubernetes-operator.git", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// base
 | 	// base
 | ||||||
| 	createUserConfigurationSecret(t, jenkinsCRName, namespace, systemMessageEnvName, systemMessage) | 	createUserConfigurationSecret(t, jenkinsCRName, namespace, systemMessageEnvName, systemMessage) | ||||||
| 	createUserConfigurationConfigMap(t, jenkinsCRName, namespace, numberOfExecutors, fmt.Sprintf("${%s}", systemMessageEnvName)) | 	createUserConfigurationConfigMap(t, jenkinsCRName, namespace, numberOfExecutors, fmt.Sprintf("${%s}", systemMessageEnvName)) | ||||||
| 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace) | 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &[]v1alpha1.SeedJob{mySeedJob.SeedJob}) | ||||||
| 	createDefaultLimitsForContainersInNamespace(t, namespace) | 	createDefaultLimitsForContainersInNamespace(t, namespace) | ||||||
| 	createKubernetesCredentialsProviderSecret(t, namespace, jenkinsCredentialName) | 	createKubernetesCredentialsProviderSecret(t, namespace, mySeedJob) | ||||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||||
| 
 | 
 | ||||||
| 	verifyJenkinsMasterPodAttributes(t, jenkins) | 	verifyJenkinsMasterPodAttributes(t, jenkins) | ||||||
|  | @ -49,9 +55,8 @@ func TestConfiguration(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	// user
 | 	// user
 | ||||||
| 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | ||||||
| 	verifyJenkinsSeedJobs(t, client, jenkins) |  | ||||||
| 	verifyUserConfiguration(t, client, numberOfExecutors, systemMessage) | 	verifyUserConfiguration(t, client, numberOfExecutors, systemMessage) | ||||||
| 	verifyIfJenkinsCredentialExists(t, client, jenkinsCredentialName) | 	verifyJenkinsSeedJobs(t, client, []seedJobConfig{mySeedJob}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createUserConfigurationSecret(t *testing.T, jenkinsCRName string, namespace string, systemMessageEnvName, systemMessage string) { | func createUserConfigurationSecret(t *testing.T, jenkinsCRName string, namespace string, systemMessageEnvName, systemMessage string) { | ||||||
|  | @ -71,30 +76,6 @@ func createUserConfigurationSecret(t *testing.T, jenkinsCRName string, namespace | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createKubernetesCredentialsProviderSecret(t *testing.T, namespace, name string) { |  | ||||||
| 	secret := &corev1.Secret{ |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      name, |  | ||||||
| 			Namespace: namespace, |  | ||||||
| 			Annotations: map[string]string{ |  | ||||||
| 				"jenkins.io/credentials-description": "credentials from Kubernetes", |  | ||||||
| 			}, |  | ||||||
| 			Labels: map[string]string{ |  | ||||||
| 				"jenkins.io/credentials-type": "usernamePassword", |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 		StringData: map[string]string{ |  | ||||||
| 			"username": "user", |  | ||||||
| 			"password": "pass", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	t.Logf("Secret for Kubernetes credentials provider plugin %+v", *secret) |  | ||||||
| 	if err := framework.Global.Client.Create(context.TODO(), secret, nil); err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func createUserConfigurationConfigMap(t *testing.T, jenkinsCRName string, namespace string, numberOfExecutors int, systemMessage string) { | func createUserConfigurationConfigMap(t *testing.T, jenkinsCRName string, namespace string, numberOfExecutors int, systemMessage string) { | ||||||
| 	userConfiguration := &corev1.ConfigMap{ | 	userConfiguration := &corev1.ConfigMap{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | @ -223,43 +204,6 @@ func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (* | ||||||
| 	return p, requiredPlugin.Version == p.Version | 	return p, requiredPlugin.Version == p.Version | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func verifyJenkinsSeedJobs(t *testing.T, client jenkinsclient.Jenkins, jenkins *v1alpha1.Jenkins) { |  | ||||||
| 	t.Logf("Attempting to get configure seed job status '%v'", seedjobs.ConfigureSeedJobsName) |  | ||||||
| 
 |  | ||||||
| 	configureSeedJobs, err := client.GetJob(seedjobs.ConfigureSeedJobsName) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	assert.NotNil(t, configureSeedJobs) |  | ||||||
| 	build, err := configureSeedJobs.GetLastSuccessfulBuild() |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	assert.NotNil(t, build) |  | ||||||
| 
 |  | ||||||
| 	seedJobName := "jenkins-operator-configure-seed-job" |  | ||||||
| 	t.Logf("Attempting to verify if seed job has been created '%v'", seedJobName) |  | ||||||
| 	seedJob, err := client.GetJob(seedJobName) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	assert.NotNil(t, seedJob) |  | ||||||
| 
 |  | ||||||
| 	build, err = seedJob.GetLastSuccessfulBuild() |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| 	assert.NotNil(t, build) |  | ||||||
| 
 |  | ||||||
| 	err = framework.Global.Client.Get(context.TODO(), types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name}, jenkins) |  | ||||||
| 	assert.NoError(t, err, "couldn't get jenkins custom resource") |  | ||||||
| 	assert.NotNil(t, jenkins.Status.Builds) |  | ||||||
| 	assert.NotEmpty(t, jenkins.Status.Builds) |  | ||||||
| 
 |  | ||||||
| 	jobCreatedByDSLPluginName := "build-jenkins-operator" |  | ||||||
| 	err = wait.Poll(time.Second*10, time.Minute*2, func() (bool, error) { |  | ||||||
| 		t.Logf("Attempting to verify if job '%s' has been created ", jobCreatedByDSLPluginName) |  | ||||||
| 		seedJob, err := client.GetJob(jobCreatedByDSLPluginName) |  | ||||||
| 		if err != nil || seedJob == nil { |  | ||||||
| 			return false, nil |  | ||||||
| 		} |  | ||||||
| 		return true, nil |  | ||||||
| 	}) |  | ||||||
| 	assert.NoError(t, err) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func verifyUserConfiguration(t *testing.T, jenkinsClient jenkinsclient.Jenkins, amountOfExecutors int, systemMessage string) { | func verifyUserConfiguration(t *testing.T, jenkinsClient jenkinsclient.Jenkins, amountOfExecutors int, systemMessage string) { | ||||||
| 	checkConfigurationViaGroovyScript := fmt.Sprintf(` | 	checkConfigurationViaGroovyScript := fmt.Sprintf(` | ||||||
| if (!new Integer(%d).equals(Jenkins.instance.numExecutors)) { | if (!new Integer(%d).equals(Jenkins.instance.numExecutors)) { | ||||||
|  | @ -275,32 +219,3 @@ if (!"%s".equals(Jenkins.instance.systemMessage)) { | ||||||
| 	logs, err = jenkinsClient.ExecuteScript(checkConfigurationAsCode) | 	logs, err = jenkinsClient.ExecuteScript(checkConfigurationAsCode) | ||||||
| 	assert.NoError(t, err, logs) | 	assert.NoError(t, err, logs) | ||||||
| } | } | ||||||
| 
 |  | ||||||
| func verifyIfJenkinsCredentialExists(t *testing.T, jenkinsClient jenkinsclient.Jenkins, credentialName string) { |  | ||||||
| 	groovyScriptFmt := `import com.cloudbees.plugins.credentials.Credentials |  | ||||||
| 
 |  | ||||||
| Set<Credentials> allCredentials = new HashSet<Credentials>(); |  | ||||||
| 
 |  | ||||||
| def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials( |  | ||||||
| 	com.cloudbees.plugins.credentials.Credentials.class |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| allCredentials.addAll(creds) |  | ||||||
| 
 |  | ||||||
| Jenkins.instance.getAllItems(com.cloudbees.hudson.plugins.folder.Folder.class).each{ f -> |  | ||||||
| 	creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials( |  | ||||||
|       	com.cloudbees.plugins.credentials.Credentials.class, f) |  | ||||||
| 	allCredentials.addAll(creds) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| def found = false |  | ||||||
| for (c in allCredentials) { |  | ||||||
| 	if("%s".equals(c.id)) found = true |  | ||||||
| } |  | ||||||
| if(!found) { |  | ||||||
| 	throw new Exception("Expected credential not found") |  | ||||||
| }` |  | ||||||
| 	groovyScript := fmt.Sprintf(groovyScriptFmt, credentialName) |  | ||||||
| 	logs, err := jenkinsClient.ExecuteScript(groovyScript) |  | ||||||
| 	assert.NoError(t, err, logs) |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -58,7 +58,12 @@ func createJenkinsAPIClient(jenkins *v1alpha1.Jenkins) (jenkinsclient.Jenkins, e | ||||||
| 	) | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createJenkinsCR(t *testing.T, name, namespace string) *v1alpha1.Jenkins { | func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha1.SeedJob) *v1alpha1.Jenkins { | ||||||
|  | 	var seedJobs []v1alpha1.SeedJob | ||||||
|  | 	if seedJob != nil { | ||||||
|  | 		seedJobs = append(seedJobs, *seedJob...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	jenkins := &v1alpha1.Jenkins{ | 	jenkins := &v1alpha1.Jenkins{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
| 			Name:      name, | 			Name:      name, | ||||||
|  | @ -80,16 +85,7 @@ func createJenkinsCR(t *testing.T, name, namespace string) *v1alpha1.Jenkins { | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			//TODO(bantoniak) add seed job with private key
 | 			SeedJobs: seedJobs, | ||||||
| 			SeedJobs: []v1alpha1.SeedJob{ |  | ||||||
| 				{ |  | ||||||
| 					ID:               "jenkins-operator", |  | ||||||
| 					Targets:          "cicd/jobs/*.jenkins", |  | ||||||
| 					Description:      "Jenkins Operator repository", |  | ||||||
| 					RepositoryBranch: "master", |  | ||||||
| 					RepositoryURL:    "https://github.com/jenkinsci/kubernetes-operator.git", |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package e2e | package e2e | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"flag" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis" | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis" | ||||||
|  | @ -15,9 +16,15 @@ import ( | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	jenkinsOperatorDeploymentName     = constants.OperatorName | 	jenkinsOperatorDeploymentName     = constants.OperatorName | ||||||
|  | 	seedJobConfigurationParameterName = "seed-job-config" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	seedJobConfigurationFile *string | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestMain(m *testing.M) { | func TestMain(m *testing.M) { | ||||||
|  | 	seedJobConfigurationFile = flag.String(seedJobConfigurationParameterName, "", "path to seed job config") | ||||||
| 	f.MainEntry(m) | 	f.MainEntry(m) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ func TestJenkinsMasterPodRestart(t *testing.T) { | ||||||
| 	// Deletes test namespace
 | 	// Deletes test namespace
 | ||||||
| 	defer ctx.Cleanup() | 	defer ctx.Cleanup() | ||||||
| 
 | 
 | ||||||
| 	jenkins := createJenkinsCR(t, "e2e", namespace) | 	jenkins := createJenkinsCR(t, "e2e", namespace, nil) | ||||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||||
| 	restartJenkinsMasterPod(t, jenkins) | 	restartJenkinsMasterPod(t, jenkins) | ||||||
| 	waitForRecreateJenkinsMasterPod(t, jenkins) | 	waitForRecreateJenkinsMasterPod(t, jenkins) | ||||||
|  | @ -37,7 +37,7 @@ func TestSafeRestart(t *testing.T) { | ||||||
| 
 | 
 | ||||||
| 	jenkinsCRName := "e2e" | 	jenkinsCRName := "e2e" | ||||||
| 	configureAuthorizationToUnSecure(t, jenkinsCRName, namespace) | 	configureAuthorizationToUnSecure(t, jenkinsCRName, namespace) | ||||||
| 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace) | 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, nil) | ||||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||||
| 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | ||||||
| 	jenkinsClient := verifyJenkinsAPIConnection(t, jenkins) | 	jenkinsClient := verifyJenkinsAPIConnection(t, jenkins) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,153 @@ | ||||||
|  | package e2e | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
|  | 	"testing" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/internal/try" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkinsio/v1alpha1" | ||||||
|  | 	jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/client" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" | ||||||
|  | 
 | ||||||
|  | 	framework "github.com/operator-framework/operator-sdk/pkg/test" | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
|  | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type seedJobConfig struct { | ||||||
|  | 	v1alpha1.SeedJob | ||||||
|  | 	JobNames   []string `json:"jobNames,omitempty"` | ||||||
|  | 	Username   string   `json:"username,omitempty"` | ||||||
|  | 	Password   string   `json:"password,omitempty"` | ||||||
|  | 	PrivateKey string   `json:"privateKey,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type seedJobsConfig struct { | ||||||
|  | 	SeedJobs []seedJobConfig `json:"seedJobs,omitempty"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestSeedJobs(t *testing.T) { | ||||||
|  | 	t.Parallel() | ||||||
|  | 	if seedJobConfigurationFile == nil || len(*seedJobConfigurationFile) == 0 { | ||||||
|  | 		t.Skipf("Skipping test because flag '%+v' is not set", seedJobConfigurationFile) | ||||||
|  | 	} | ||||||
|  | 	seedJobsConfig := loadSeedJobsConfig(t) | ||||||
|  | 	namespace, ctx := setupTest(t) | ||||||
|  | 	// Deletes test namespace
 | ||||||
|  | 	defer ctx.Cleanup() | ||||||
|  | 
 | ||||||
|  | 	jenkinsCRName := "e2e" | ||||||
|  | 	var seedJobs []v1alpha1.SeedJob | ||||||
|  | 
 | ||||||
|  | 	// base
 | ||||||
|  | 	for _, seedJobConfig := range seedJobsConfig.SeedJobs { | ||||||
|  | 		createKubernetesCredentialsProviderSecret(t, namespace, seedJobConfig) | ||||||
|  | 		seedJobs = append(seedJobs, seedJobConfig.SeedJob) | ||||||
|  | 	} | ||||||
|  | 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &seedJobs) | ||||||
|  | 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||||
|  | 
 | ||||||
|  | 	verifyJenkinsMasterPodAttributes(t, jenkins) | ||||||
|  | 	client := verifyJenkinsAPIConnection(t, jenkins) | ||||||
|  | 	verifyPlugins(t, client, jenkins) | ||||||
|  | 
 | ||||||
|  | 	// user
 | ||||||
|  | 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | ||||||
|  | 	verifyJenkinsSeedJobs(t, client, seedJobsConfig.SeedJobs) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func loadSeedJobsConfig(t *testing.T) seedJobsConfig { | ||||||
|  | 	jsonFile, err := os.Open(*seedJobConfigurationFile) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	defer func() { _ = jsonFile.Close() }() | ||||||
|  | 
 | ||||||
|  | 	byteValue, err := ioutil.ReadAll(jsonFile) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 
 | ||||||
|  | 	var result seedJobsConfig | ||||||
|  | 	err = json.Unmarshal([]byte(byteValue), &result) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.NotEmpty(t, result.SeedJobs) | ||||||
|  | 	return result | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func createKubernetesCredentialsProviderSecret(t *testing.T, namespace string, config seedJobConfig) { | ||||||
|  | 	if config.JenkinsCredentialType == v1alpha1.NoJenkinsCredentialCredentialType { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	secret := &corev1.Secret{ | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name:      config.CredentialID, | ||||||
|  | 			Namespace: namespace, | ||||||
|  | 			Annotations: map[string]string{ | ||||||
|  | 				"jenkins.io/credentials-description": "credentials from Kubernetes " + config.ID, | ||||||
|  | 			}, | ||||||
|  | 			Labels: map[string]string{ | ||||||
|  | 				seedjobs.JenkinsCredentialTypeLabelName: string(config.CredentialID), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		StringData: map[string]string{ | ||||||
|  | 			seedjobs.UsernameSecretKey:   config.Username, | ||||||
|  | 			seedjobs.PasswordSecretKey:   config.Password, | ||||||
|  | 			seedjobs.PrivateKeySecretKey: config.PrivateKey, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	err := framework.Global.Client.Create(context.TODO(), secret, nil) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func verifyJenkinsSeedJobs(t *testing.T, jenkinsClient jenkinsclient.Jenkins, seedJobs []seedJobConfig) { | ||||||
|  | 	var err error | ||||||
|  | 	for _, seedJob := range seedJobs { | ||||||
|  | 		if seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha1.UsernamePasswordCredentialType { | ||||||
|  | 			err = verifyIfJenkinsCredentialExists(jenkinsClient, seedJob.CredentialID) | ||||||
|  | 			assert.NoErrorf(t, err, "Jenkins credential '%s' not created for seed job ID '%s'", seedJob.CredentialID, seedJob.ID) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, requireJobName := range seedJob.JobNames { | ||||||
|  | 			err = try.Until(func() (end bool, err error) { | ||||||
|  | 				_, err = jenkinsClient.GetJob(requireJobName) | ||||||
|  | 				return err == nil, err | ||||||
|  | 			}, time.Second*2, time.Minute*2) | ||||||
|  | 			assert.NoErrorf(t, err, "Jenkins job '%s' not created by seed job ID '%s'", requireJobName, seedJob.ID) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func verifyIfJenkinsCredentialExists(jenkinsClient jenkinsclient.Jenkins, credentialName string) error { | ||||||
|  | 	groovyScriptFmt := `import com.cloudbees.plugins.credentials.Credentials | ||||||
|  | 
 | ||||||
|  | Set<Credentials> allCredentials = new HashSet<Credentials>(); | ||||||
|  | 
 | ||||||
|  | def creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials( | ||||||
|  | 	com.cloudbees.plugins.credentials.Credentials.class | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | allCredentials.addAll(creds) | ||||||
|  | 
 | ||||||
|  | Jenkins.instance.getAllItems(com.cloudbees.hudson.plugins.folder.Folder.class).each{ f -> | ||||||
|  | 	creds = com.cloudbees.plugins.credentials.CredentialsProvider.lookupCredentials( | ||||||
|  |       	com.cloudbees.plugins.credentials.Credentials.class, f) | ||||||
|  | 	allCredentials.addAll(creds) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | def found = false | ||||||
|  | for (c in allCredentials) { | ||||||
|  | 	if("%s".equals(c.id)) found = true | ||||||
|  | } | ||||||
|  | if(!found) { | ||||||
|  | 	throw new Exception("Expected credential not found") | ||||||
|  | }` | ||||||
|  | 	groovyScript := fmt.Sprintf(groovyScriptFmt, credentialName) | ||||||
|  | 	_, err := jenkinsClient.ExecuteScript(groovyScript) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue