#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"` | ||||
| } | ||||
| 
 | ||||
| // 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
 | ||||
| type SeedJob struct { | ||||
| 	ID               string     `json:"id"` | ||||
| 	ID                    string                `json:"id,omitempty"` | ||||
| 	CredentialID          string                `json:"credentialID,omitempty"` | ||||
| 	Description           string                `json:"description,omitempty"` | ||||
| 	Targets               string                `json:"targets,omitempty"` | ||||
| 	RepositoryBranch      string                `json:"repositoryBranch,omitempty"` | ||||
| 	RepositoryURL    string     `json:"repositoryUrl"` | ||||
| 	PrivateKey       PrivateKey `json:"privateKey,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // PrivateKey contains a private key
 | ||||
| type PrivateKey struct { | ||||
| 	SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef"` | ||||
| 	RepositoryURL         string                `json:"repositoryUrl,omitempty"` | ||||
| 	JenkinsCredentialType JenkinsCredentialType `json:"credentialType,omitempty"` | ||||
| } | ||||
| 
 | ||||
| func init() { | ||||
|  |  | |||
|  | @ -185,9 +185,7 @@ func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) { | |||
| 	if in.SeedJobs != nil { | ||||
| 		in, out := &in.SeedJobs, &out.SeedJobs | ||||
| 		*out = make([]SeedJob, len(*in)) | ||||
| 		for i := range *in { | ||||
| 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||
| 		} | ||||
| 		copy(*out, *in) | ||||
| 	} | ||||
| 	in.Service.DeepCopyInto(&out.Service) | ||||
| 	in.SlaveService.DeepCopyInto(&out.SlaveService) | ||||
|  | @ -239,31 +237,9 @@ func (in *JenkinsStatus) DeepCopy() *JenkinsStatus { | |||
| 	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.
 | ||||
| func (in *SeedJob) DeepCopyInto(out *SeedJob) { | ||||
| 	*out = *in | ||||
| 	in.PrivateKey.DeepCopyInto(&out.PrivateKey) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -253,9 +253,8 @@ func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationConfigMap(met | |||
| 	} else if err != nil { | ||||
| 		return stackerr.WithStack(err) | ||||
| 	} | ||||
| 	valid := r.verifyLabelsForWatchedResource(currentConfigMap) | ||||
| 	if !valid { | ||||
| 		currentConfigMap.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(r.jenkins) | ||||
| 	if !resources.VerifyIfLabelsAreSet(currentConfigMap, resources.BuildLabelsForWatchedResources(*r.jenkins)) { | ||||
| 		currentConfigMap.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(*r.jenkins) | ||||
| 		return stackerr.WithStack(r.k8sClient.Update(context.TODO(), currentConfigMap)) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -270,9 +269,8 @@ func (r *ReconcileJenkinsBaseConfiguration) createUserConfigurationSecret(meta m | |||
| 	} else if err != nil { | ||||
| 		return stackerr.WithStack(err) | ||||
| 	} | ||||
| 	valid := r.verifyLabelsForWatchedResource(currentSecret) | ||||
| 	if !valid { | ||||
| 		currentSecret.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(r.jenkins) | ||||
| 	if !resources.VerifyIfLabelsAreSet(currentSecret, resources.BuildLabelsForWatchedResources(*r.jenkins)) { | ||||
| 		currentSecret.ObjectMeta.Labels = resources.BuildLabelsForWatchedResources(*r.jenkins) | ||||
| 		return stackerr.WithStack(r.k8sClient.Update(context.TODO(), currentSecret)) | ||||
| 	} | ||||
| 
 | ||||
|  | @ -547,14 +545,3 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureBaseConfiguration(jenkinsClien | |||
| 
 | ||||
| 	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() | ||||
| ` | ||||
| 
 | ||||
| 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
 | ||||
| func GetBaseConfigurationConfigMapName(jenkins *v1alpha1.Jenkins) string { | ||||
| 	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), | ||||
| 			), | ||||
| 			"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
 | ||||
| // resources with that labels should not be deleted after Jenkins CR deletion, to prevent this situation don't set
 | ||||
| // any owner
 | ||||
| func BuildLabelsForWatchedResources(jenkins *v1alpha1.Jenkins) map[string]string { | ||||
| func BuildLabelsForWatchedResources(jenkins v1alpha1.Jenkins) map[string]string { | ||||
| 	return map[string]string{ | ||||
| 		constants.LabelAppKey:       constants.LabelAppValue, | ||||
| 		constants.LabelJenkinsCRKey: jenkins.Name, | ||||
|  | @ -41,3 +41,14 @@ func BuildLabelsForWatchedResources(jenkins *v1alpha1.Jenkins) map[string]string | |||
| func GetResourceName(jenkins *v1alpha1.Jenkins) string { | ||||
| 	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{ | ||||
| 			Name:      GetUserConfigurationConfigMapNameFromJenkins(jenkins), | ||||
| 			Namespace: jenkins.ObjectMeta.Namespace, | ||||
| 			Labels:    BuildLabelsForWatchedResources(jenkins), | ||||
| 			Labels:    BuildLabelsForWatchedResources(*jenkins), | ||||
| 		}, | ||||
| 		Data: map[string]string{ | ||||
| 			"1-configure-theme.groovy": configureTheme, | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ func NewUserConfigurationSecret(jenkins *v1alpha1.Jenkins) *corev1.Secret { | |||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			Name:      GetUserConfigurationSecretNameFromJenkins(jenkins), | ||||
| 			Namespace: jenkins.ObjectMeta.Namespace, | ||||
| 			Labels:    BuildLabelsForWatchedResources(jenkins), | ||||
| 			Labels:    BuildLabelsForWatchedResources(*jenkins), | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -8,12 +8,14 @@ import ( | |||
| 
 | ||||
| 	"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/base/resources" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/jobs" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/log" | ||||
| 
 | ||||
| 	"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 "sigs.k8s.io/controller-runtime/pkg/client" | ||||
| ) | ||||
|  | @ -22,12 +24,23 @@ const ( | |||
| 	// ConfigureSeedJobsName this is the fixed seed job name
 | ||||
| 	ConfigureSeedJobsName = constants.OperatorName + "-configure-seed-job" | ||||
| 
 | ||||
| 	deployKeyIDParameterName      = "DEPLOY_KEY_ID" | ||||
| 	privateKeyParameterName       = "PRIVATE_KEY" | ||||
| 	idParameterName               = "ID" | ||||
| 	credentialIDParameterName     = "CREDENTIAL_ID" | ||||
| 	repositoryURLParameterName    = "REPOSITORY_URL" | ||||
| 	repositoryBranchParameterName = "REPOSITORY_BRANCH" | ||||
| 	targetsParameterName          = "TARGETS" | ||||
| 	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
 | ||||
|  | @ -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
 | ||||
| func (s *SeedJobs) EnsureSeedJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | ||||
| 	err = s.createJob() | ||||
| 	if err != nil { | ||||
| 	if err = s.createJob(); err != nil { | ||||
| 		s.logger.V(log.VWarn).Info("Couldn't create jenkins seed job") | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	if err = s.ensureLabelsForSecrets(*jenkins); err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 
 | ||||
| 	done, err = s.buildJobs(jenkins) | ||||
| 	if err != nil { | ||||
| 		s.logger.V(log.VWarn).Info("Couldn't build jenkins seed job") | ||||
|  | @ -73,18 +90,46 @@ func (s *SeedJobs) createJob() error { | |||
| 	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
 | ||||
| func (s *SeedJobs) buildJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | ||||
| 	allDone := true | ||||
| 	seedJobs := jenkins.Spec.SeedJobs | ||||
| 	for _, seedJob := range seedJobs { | ||||
| 		privateKey, err := s.privateKeyFromSecret(jenkins.Namespace, seedJob) | ||||
| 	for _, seedJob := range jenkins.Spec.SeedJobs { | ||||
| 		credentialValue, err := s.credentialValue(jenkins.Namespace, seedJob) | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
| 		parameters := map[string]string{ | ||||
| 			deployKeyIDParameterName:      seedJob.ID, | ||||
| 			privateKeyParameterName:       privateKey, | ||||
| 			idParameterName:               seedJob.ID, | ||||
| 			credentialIDParameterName:     seedJob.CredentialID, | ||||
| 			repositoryURLParameterName:    seedJob.RepositoryURL, | ||||
| 			repositoryBranchParameterName: seedJob.RepositoryBranch, | ||||
| 			targetsParameterName:          seedJob.Targets, | ||||
|  | @ -92,8 +137,9 @@ func (s *SeedJobs) buildJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | |||
| 		} | ||||
| 
 | ||||
| 		hash := sha256.New() | ||||
| 		hash.Write([]byte(parameters[deployKeyIDParameterName])) | ||||
| 		hash.Write([]byte(parameters[privateKeyParameterName])) | ||||
| 		hash.Write([]byte(parameters[idParameterName])) | ||||
| 		hash.Write([]byte(parameters[credentialIDParameterName])) | ||||
| 		hash.Write([]byte(credentialValue)) | ||||
| 		hash.Write([]byte(parameters[repositoryURLParameterName])) | ||||
| 		hash.Write([]byte(parameters[repositoryBranchParameterName])) | ||||
| 		hash.Write([]byte(parameters[targetsParameterName])) | ||||
|  | @ -112,21 +158,23 @@ func (s *SeedJobs) buildJobs(jenkins *v1alpha1.Jenkins) (done bool, err error) { | |||
| 	return allDone, nil | ||||
| } | ||||
| 
 | ||||
| // privateKeyFromSecret it's utility function which extracts deploy key from the kubernetes secret
 | ||||
| func (s *SeedJobs) privateKeyFromSecret(namespace string, seedJob v1alpha1.SeedJob) (string, error) { | ||||
| 	if seedJob.PrivateKey.SecretKeyRef != nil { | ||||
| 		deployKeySecret := &v1.Secret{} | ||||
| 		namespaceName := types.NamespacedName{Namespace: namespace, Name: seedJob.PrivateKey.SecretKeyRef.Name} | ||||
| 		err := s.k8sClient.Get(context.TODO(), namespaceName, deployKeySecret) | ||||
| func (s *SeedJobs) credentialValue(namespace string, seedJob v1alpha1.SeedJob) (string, error) { | ||||
| 	if seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha1.UsernamePasswordCredentialType { | ||||
| 		secret := &corev1.Secret{} | ||||
| 		namespaceName := types.NamespacedName{Namespace: namespace, Name: seedJob.CredentialID} | ||||
| 		err := s.k8sClient.Get(context.TODO(), namespaceName, secret) | ||||
| 		if err != nil { | ||||
| 			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 | ||||
| } | ||||
| 
 | ||||
| // FIXME(antoniaklja) use mask-password plugin for params.PRIVATE_KEY
 | ||||
| // seedJobConfigXML this is the XML representation of seed job
 | ||||
| var seedJobConfigXML = ` | ||||
| <flow-definition plugin="workflow-job@2.30"> | ||||
|  | @ -137,15 +185,16 @@ var seedJobConfigXML = ` | |||
|     <hudson.model.ParametersDefinitionProperty> | ||||
|       <parameterDefinitions> | ||||
|         <hudson.model.StringParameterDefinition> | ||||
|           <name>` + deployKeyIDParameterName + `</name> | ||||
|           <name>` + idParameterName + `</name> | ||||
|           <description></description> | ||||
|           <defaultValue></defaultValue> | ||||
|           <trim>false</trim> | ||||
|         </hudson.model.StringParameterDefinition> | ||||
|         <hudson.model.StringParameterDefinition> | ||||
|           <name>` + privateKeyParameterName + `</name> | ||||
|           <name>` + credentialIDParameterName + `</name> | ||||
|           <description></description> | ||||
|           <defaultValue></defaultValue> | ||||
|           <trim>false</trim> | ||||
|         </hudson.model.StringParameterDefinition> | ||||
|         <hudson.model.StringParameterDefinition> | ||||
|           <name>` + repositoryURLParameterName + `</name> | ||||
|  | @ -175,11 +224,7 @@ var seedJobConfigXML = ` | |||
|     </hudson.model.ParametersDefinitionProperty> | ||||
|   </properties> | ||||
|   <definition class="org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition" plugin="workflow-cps@2.61"> | ||||
|     <script>import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey | ||||
| 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 | ||||
|     <script> | ||||
| import hudson.model.FreeStyleProject | ||||
| import hudson.model.labels.LabelAtom | ||||
| import hudson.plugins.git.BranchSpec | ||||
|  | @ -190,36 +235,19 @@ import javaposse.jobdsl.plugin.ExecuteDslScripts | |||
| import javaposse.jobdsl.plugin.LookupStrategy | ||||
| import javaposse.jobdsl.plugin.RemovedJobAction | ||||
| 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 | ||||
| 
 | ||||
| // 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 | ||||
| 
 | ||||
| def jobDslSeedName = "${params.DEPLOY_KEY_ID}-` + constants.SeedJobSuffix + `" | ||||
| def jobDslDeployKeyName = "${params.DEPLOY_KEY_ID}" | ||||
| def jobDslSeedName = "${params.` + idParameterName + `}-` + constants.SeedJobSuffix + `" | ||||
| 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 scm = new GitSCM( | ||||
|         repoList, | ||||
|         newArrayList(new BranchSpec("${params.REPOSITORY_BRANCH}")), | ||||
|         newArrayList(new BranchSpec("${params.` + repositoryBranchParameterName + `}")), | ||||
|         false, | ||||
|         Collections.<SubmoduleConfig> emptyList(), | ||||
|         null, | ||||
|  | @ -228,7 +256,7 @@ def scm = new GitSCM( | |||
| ) | ||||
| 
 | ||||
| def executeDslScripts = new ExecuteDslScripts() | ||||
| executeDslScripts.setTargets("${params.TARGETS}") | ||||
| executeDslScripts.setTargets("${params.` + targetsParameterName + `}") | ||||
| executeDslScripts.setSandbox(false) | ||||
| executeDslScripts.setRemovedJobAction(RemovedJobAction.DELETE) | ||||
| executeDslScripts.setRemovedViewAction(RemovedViewAction.DELETE) | ||||
|  | @ -240,13 +268,11 @@ if (jobRef == null) { | |||
| } | ||||
| jobRef.getBuildersList().clear() | ||||
| jobRef.getBuildersList().add(executeDslScripts) | ||||
| jobRef.setDisplayName("${params.SEED_JOB_DISPLAY_NAME}") | ||||
| jobRef.setDisplayName("${params.` + displayNameParameterName + `}") | ||||
| jobRef.setScm(scm) | ||||
| // TODO don't use master executors
 | ||||
| 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) | ||||
| </script> | ||||
|     <sandbox>false</sandbox> | ||||
|  |  | |||
|  | @ -135,6 +135,7 @@ func jenkinsCustomResource() *v1alpha1.Jenkins { | |||
| 			SeedJobs: []v1alpha1.SeedJob{ | ||||
| 				{ | ||||
| 					ID: "jenkins-operator-e2e", | ||||
| 					JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||
| 					Targets:               "cicd/jobs/*.jenkins", | ||||
| 					Description:           "Jenkins Operator e2e tests repository", | ||||
| 					RepositoryBranch:      "master", | ||||
|  |  | |||
|  | @ -8,8 +8,10 @@ import ( | |||
| 	"strings" | ||||
| 
 | ||||
| 	"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/go-logr/logr" | ||||
| 	stackerr "github.com/pkg/errors" | ||||
| 	"k8s.io/api/core/v1" | ||||
| 	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) { | ||||
| 	valid := true | ||||
| 
 | ||||
| 	// TODO id must be unique
 | ||||
| 	if jenkins.Spec.SeedJobs != nil { | ||||
| 		for _, seedJob := range jenkins.Spec.SeedJobs { | ||||
| 			logger := r.logger.WithValues("seedJob", fmt.Sprintf("%+v", seedJob)).V(log.VWarn) | ||||
| 
 | ||||
| 			// validate seed job id is not empty
 | ||||
| 			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 | ||||
| 			} | ||||
| 
 | ||||
| 			// validate repository url match private key
 | ||||
| 			if strings.Contains(seedJob.RepositoryURL, "git@") { | ||||
| 				if seedJob.PrivateKey.SecretKeyRef == nil { | ||||
| 					logger.Info("private key can't be empty while using ssh repository url") | ||||
| 			if strings.Contains(seedJob.RepositoryURL, "git@") && seedJob.JenkinsCredentialType == v1alpha1.NoJenkinsCredentialCredentialType { | ||||
| 				logger.Info("Jenkins credential must be set while using ssh repository url") | ||||
| 				valid = false | ||||
| 			} | ||||
| 			} | ||||
| 
 | ||||
| 			// validate private key from secret
 | ||||
| 			if seedJob.PrivateKey.SecretKeyRef != nil { | ||||
| 				deployKeySecret := &v1.Secret{} | ||||
| 				namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: seedJob.PrivateKey.SecretKeyRef.Name} | ||||
| 				err := r.k8sClient.Get(context.TODO(), namespaceName, deployKeySecret) | ||||
| 			if seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha1.UsernamePasswordCredentialType { | ||||
| 				secret := &v1.Secret{} | ||||
| 				namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: seedJob.CredentialID} | ||||
| 				err := r.k8sClient.Get(context.TODO(), namespaceName, secret) | ||||
| 				if err != nil && apierrors.IsNotFound(err) { | ||||
| 					logger.Info("secret not found") | ||||
| 					valid = false | ||||
| 					logger.Info(fmt.Sprintf("required secret '%s' with Jenkins credential not found", seedJob.CredentialID)) | ||||
| 					return false, nil | ||||
| 				} else if err != nil { | ||||
| 					return false, stackerr.WithStack(err) | ||||
| 				} | ||||
| 
 | ||||
| 				privateKey := string(deployKeySecret.Data[seedJob.PrivateKey.SecretKeyRef.Key]) | ||||
| 				if privateKey == "" { | ||||
| 					logger.Info("private key is empty") | ||||
| 				if seedJob.JenkinsCredentialType == v1alpha1.BasicSSHCredentialType { | ||||
| 					if ok := validateBasicSSHSecret(logger, *secret); !ok { | ||||
| 						valid = false | ||||
| 					} | ||||
| 
 | ||||
| 				if err := validatePrivateKey(privateKey); err != nil { | ||||
| 					logger.Info(fmt.Sprintf("private key is invalid: %s", err)) | ||||
| 				} | ||||
| 				if seedJob.JenkinsCredentialType == v1alpha1.UsernamePasswordCredentialType { | ||||
| 					if ok := validateUsernamePasswordSecret(logger, *secret); !ok { | ||||
| 						valid = false | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	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 { | ||||
| 	block, _ := pem.Decode([]byte(privateKey)) | ||||
| 	if block == nil { | ||||
|  |  | |||
|  | @ -2,10 +2,10 @@ package user | |||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"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" | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
|  | @ -40,8 +40,7 @@ JjihnIfoDu9MfU24GWDw49wGPTn+eI7GQC+8yxGg7fd24kohHSaCowoW16pbYVco | |||
| 6iLr5rkCgYBt0bcYJ3AOTH0UXS8kvJvnyce/RBIAMoUABwvdkZt9r5B4UzsoLq5e | ||||
| WrrU6fSRsE6lSsBd83pOAQ46tv+vntQ+0EihD9/0INhkQM99lBw1TFdFTgGSAs1e | ||||
| ns4JGP6f5uIuwqu/nbqPqMyDovjkGbX2znuGBcvki90Pi97XL7MMWw== | ||||
| -----END RSA PRIVATE KEY----- | ||||
| ` | ||||
| -----END RSA PRIVATE KEY-----` | ||||
| 
 | ||||
| var fakeInvalidPrivateKey = `-----BEGIN RSA PRIVATE KEY----- | ||||
| MIIEpAIBAAKCAQEArK4ld6i2iqW6L3jaTZaKD/v7PjDn+Ik9MXp+kvLcUw/+wEGm | ||||
|  | @ -50,189 +49,398 @@ SwiLd8TWAvXkxdXm8fDOGAZbYK2alMV+M+9E2OpZsBUCxmb/3FAofF6JccKoJOH8 | |||
| ` | ||||
| 
 | ||||
| func TestValidateSeedJobs(t *testing.T) { | ||||
| 	data := []struct { | ||||
| 		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{ | ||||
| 	secretTypeMeta := metav1.TypeMeta{ | ||||
| 		Kind:       "Secret", | ||||
| 		APIVersion: "v1", | ||||
| 				}, | ||||
| 				ObjectMeta: metav1.ObjectMeta{ | ||||
| 	} | ||||
| 	secretObjectMeta := metav1.ObjectMeta{ | ||||
| 		Name:      "deploy-keys", | ||||
| 		Namespace: "default", | ||||
| 				}, | ||||
| 				Data: map[string][]byte{ | ||||
| 					"jenkins-operator-e2e": []byte(fakePrivateKey), | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedResult: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			description: "Invalid private key in secret", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 	} | ||||
| 	t.Run("Valid with public repository and without private key", func(t *testing.T) { | ||||
| 		jenkins := &v1alpha1.Jenkins{ | ||||
| 			Spec: v1alpha1.JenkinsSpec{ | ||||
| 				SeedJobs: []v1alpha1.SeedJob{ | ||||
| 					{ | ||||
| 							ID:               "jenkins-operator-e2e", | ||||
| 						ID:                    "example", | ||||
| 						CredentialID:          "jenkins-operator-e2e", | ||||
| 						JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||
| 						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", | ||||
| 					APIVersion: "v1", | ||||
| 				}, | ||||
| 				ObjectMeta: metav1.ObjectMeta{ | ||||
| 					Name:      "deploy-keys", | ||||
| 					Namespace: "default", | ||||
| 				}, | ||||
| 				Data: map[string][]byte{ | ||||
| 					"jenkins-operator-e2e": []byte(fakeInvalidPrivateKey), | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedResult: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			description: "Invalid with PrivateKey and empty Secret data", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 		} | ||||
| 
 | ||||
| 		userReconcileLoop := New(fake.NewFakeClient(), nil, logf.ZapLogger(false), nil) | ||||
| 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||
| 
 | ||||
| 		assert.NoError(t, err) | ||||
| 		assert.Equal(t, true, result) | ||||
| 	}) | ||||
| 	t.Run("Invalid without id", func(t *testing.T) { | ||||
| 		jenkins := &v1alpha1.Jenkins{ | ||||
| 			Spec: v1alpha1.JenkinsSpec{ | ||||
| 				SeedJobs: []v1alpha1.SeedJob{ | ||||
| 					{ | ||||
| 							ID:               "jenkins-operator-e2e", | ||||
| 						JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||
| 						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", | ||||
| 					APIVersion: "v1", | ||||
| 				}, | ||||
| 				ObjectMeta: metav1.ObjectMeta{ | ||||
| 					Name:      "deploy-keys", | ||||
| 					Namespace: "default", | ||||
| 				}, | ||||
| 				Data: map[string][]byte{ | ||||
| 					"jenkins-operator-e2e": []byte(""), | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedResult: false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			description: "Invalid with ssh RepositoryURL and empty PrivateKey", | ||||
| 			jenkins: &v1alpha1.Jenkins{ | ||||
| 		} | ||||
| 
 | ||||
| 		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 private key and secret", func(t *testing.T) { | ||||
| 		jenkins := &v1alpha1.Jenkins{ | ||||
| 			Spec: v1alpha1.JenkinsSpec{ | ||||
| 				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", | ||||
| 							Description:      "Jenkins Operator e2e tests repository", | ||||
| 						RepositoryBranch:      "master", | ||||
| 						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 { | ||||
| 		t.Run(fmt.Sprintf("Testing '%s'", testingData.description), func(t *testing.T) { | ||||
| 			fakeClient := fake.NewFakeClient() | ||||
| 			if testingData.secret != nil { | ||||
| 				err := fakeClient.Create(context.TODO(), testingData.secret) | ||||
| 		userReconcileLoop := New(fake.NewFakeClient(), nil, logf.ZapLogger(false), nil) | ||||
| 		result, err := userReconcileLoop.validateSeedJobs(jenkins) | ||||
| 
 | ||||
| 		assert.NoError(t, err) | ||||
| 			} | ||||
| 			userReconcileLoop := New(fakeClient, nil, logf.ZapLogger(false), nil) | ||||
| 			result, err := userReconcileLoop.validateSeedJobs(testingData.jenkins) | ||||
| 			assert.NoError(t, err) | ||||
| 			assert.Equal(t, testingData.expectedResult, result) | ||||
| 		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(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 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{ | ||||
| 				{ | ||||
| 					ID: "jenkins-operator-e2e", | ||||
| 					JenkinsCredentialType: v1alpha1.NoJenkinsCredentialCredentialType, | ||||
| 					Targets:               "cicd/jobs/*.jenkins", | ||||
| 					Description:           "Jenkins Operator e2e tests repository", | ||||
| 					RepositoryBranch:      "master", | ||||
|  |  | |||
|  | @ -5,12 +5,10 @@ import ( | |||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"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/base/resources" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user/seedjobs" | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins" | ||||
| 
 | ||||
| 	"github.com/bndr/gojenkins" | ||||
|  | @ -19,8 +17,6 @@ import ( | |||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/api/resource" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| ) | ||||
| 
 | ||||
| func TestConfiguration(t *testing.T) { | ||||
|  | @ -33,14 +29,24 @@ func TestConfiguration(t *testing.T) { | |||
| 	numberOfExecutors := 6 | ||||
| 	systemMessage := "Configuration as Code integration works!!!" | ||||
| 	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
 | ||||
| 	createUserConfigurationSecret(t, jenkinsCRName, namespace, systemMessageEnvName, systemMessage) | ||||
| 	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) | ||||
| 	createKubernetesCredentialsProviderSecret(t, namespace, jenkinsCredentialName) | ||||
| 	createKubernetesCredentialsProviderSecret(t, namespace, mySeedJob) | ||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||
| 
 | ||||
| 	verifyJenkinsMasterPodAttributes(t, jenkins) | ||||
|  | @ -49,9 +55,8 @@ func TestConfiguration(t *testing.T) { | |||
| 
 | ||||
| 	// user
 | ||||
| 	waitForJenkinsUserConfigurationToComplete(t, jenkins) | ||||
| 	verifyJenkinsSeedJobs(t, client, jenkins) | ||||
| 	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) { | ||||
|  | @ -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) { | ||||
| 	userConfiguration := &corev1.ConfigMap{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
|  | @ -223,43 +204,6 @@ func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (* | |||
| 	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) { | ||||
| 	checkConfigurationViaGroovyScript := fmt.Sprintf(` | ||||
| if (!new Integer(%d).equals(Jenkins.instance.numExecutors)) { | ||||
|  | @ -275,32 +219,3 @@ if (!"%s".equals(Jenkins.instance.systemMessage)) { | |||
| 	logs, err = jenkinsClient.ExecuteScript(checkConfigurationAsCode) | ||||
| 	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{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{ | ||||
| 			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: []v1alpha1.SeedJob{ | ||||
| 				{ | ||||
| 					ID:               "jenkins-operator", | ||||
| 					Targets:          "cicd/jobs/*.jenkins", | ||||
| 					Description:      "Jenkins Operator repository", | ||||
| 					RepositoryBranch: "master", | ||||
| 					RepositoryURL:    "https://github.com/jenkinsci/kubernetes-operator.git", | ||||
| 				}, | ||||
| 			}, | ||||
| 			SeedJobs: seedJobs, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package e2e | ||||
| 
 | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/apis" | ||||
|  | @ -15,9 +16,15 @@ import ( | |||
| 
 | ||||
| const ( | ||||
| 	jenkinsOperatorDeploymentName     = constants.OperatorName | ||||
| 	seedJobConfigurationParameterName = "seed-job-config" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	seedJobConfigurationFile *string | ||||
| ) | ||||
| 
 | ||||
| func TestMain(m *testing.M) { | ||||
| 	seedJobConfigurationFile = flag.String(seedJobConfigurationParameterName, "", "path to seed job config") | ||||
| 	f.MainEntry(m) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ func TestJenkinsMasterPodRestart(t *testing.T) { | |||
| 	// Deletes test namespace
 | ||||
| 	defer ctx.Cleanup() | ||||
| 
 | ||||
| 	jenkins := createJenkinsCR(t, "e2e", namespace) | ||||
| 	jenkins := createJenkinsCR(t, "e2e", namespace, nil) | ||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||
| 	restartJenkinsMasterPod(t, jenkins) | ||||
| 	waitForRecreateJenkinsMasterPod(t, jenkins) | ||||
|  | @ -37,7 +37,7 @@ func TestSafeRestart(t *testing.T) { | |||
| 
 | ||||
| 	jenkinsCRName := "e2e" | ||||
| 	configureAuthorizationToUnSecure(t, jenkinsCRName, namespace) | ||||
| 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace) | ||||
| 	jenkins := createJenkinsCR(t, jenkinsCRName, namespace, nil) | ||||
| 	waitForJenkinsBaseConfigurationToComplete(t, jenkins) | ||||
| 	waitForJenkinsUserConfigurationToComplete(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