Prepare for Security Validator release (#680)
* Tidy up k8s events logging * Increase memory and decrease CPU in resources * Standarize API fields * Refactor tests * Change image and resources for e2e tests * Increase readability
This commit is contained in:
		
							parent
							
								
									89fa53ae08
								
							
						
					
					
						commit
						7e94bc623f
					
				|  | @ -20,7 +20,7 @@ type JenkinsSpec struct { | ||||||
| 
 | 
 | ||||||
| 	// ValidateSecurityWarnings enables or disables validating potential security warnings in Jenkins plugins via admission webhooks.
 | 	// ValidateSecurityWarnings enables or disables validating potential security warnings in Jenkins plugins via admission webhooks.
 | ||||||
| 	//+optional
 | 	//+optional
 | ||||||
| 	ValidateSecurityWarnings bool `json:"ValidateSecurityWarnings,omitempty"` | 	ValidateSecurityWarnings bool `json:"validateSecurityWarnings,omitempty"` | ||||||
| 
 | 
 | ||||||
| 	// Notifications defines list of a services which are used to inform about Jenkins status
 | 	// Notifications defines list of a services which are used to inform about Jenkins status
 | ||||||
| 	// Can be used to integrate chat services like Slack, Microsoft Teams or Mailgun
 | 	// Can be used to integrate chat services like Slack, Microsoft Teams or Mailgun
 | ||||||
|  |  | ||||||
|  | @ -37,12 +37,19 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| 	jenkinslog                   = logf.Log.WithName("jenkins-resource") // log is for logging in this package.
 | 	jenkinslog                                                = logf.Log.WithName("jenkins-resource") // log is for logging in this package.
 | ||||||
| 	PluginsMgr PluginDataManager = *NewPluginsDataManager("/tmp/plugins.json.gzip", "/tmp/plugins.json", false, time.Duration(1000)*time.Second) | 	SecValidator                                              = *NewSecurityValidator() | ||||||
| 	_          webhook.Validator = &Jenkins{} | 	_                                       webhook.Validator = &Jenkins{} | ||||||
|  | 	initialSecurityWarningsDownloadSucceded                   = false | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const Hosturl = "https://ci.jenkins.io/job/Infra/job/plugin-site-api/job/generate-data/lastSuccessfulBuild/artifact/plugins.json.gzip" | const ( | ||||||
|  | 	Hosturl                 = "https://ci.jenkins.io/job/Infra/job/plugin-site-api/job/generate-data/lastSuccessfulBuild/artifact/plugins.json.gzip" | ||||||
|  | 	CompressedFilePath      = "/tmp/plugins.json.gzip" | ||||||
|  | 	PluginDataFile          = "/tmp/plugins.json" | ||||||
|  | 	shortenedCheckingPeriod = 1 * time.Hour | ||||||
|  | 	defaultCheckingPeriod   = 12 * time.Minute | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| func (in *Jenkins) SetupWebhookWithManager(mgr ctrl.Manager) error { | func (in *Jenkins) SetupWebhookWithManager(mgr ctrl.Manager) error { | ||||||
| 	return ctrl.NewWebhookManagedBy(mgr). | 	return ctrl.NewWebhookManagedBy(mgr). | ||||||
|  | @ -50,8 +57,6 @@ func (in *Jenkins) SetupWebhookWithManager(mgr ctrl.Manager) error { | ||||||
| 		Complete() | 		Complete() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
 |  | ||||||
| 
 |  | ||||||
| // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
 | // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
 | ||||||
| // +kubebuilder:webhook:path=/validate-jenkins-io-jenkins-io-v1alpha2-jenkins,mutating=false,failurePolicy=fail,sideEffects=None,groups=jenkins.io.jenkins.io,resources=jenkins,verbs=create;update,versions=v1alpha2,name=vjenkins.kb.io,admissionReviewVersions={v1,v1beta1}
 | // +kubebuilder:webhook:path=/validate-jenkins-io-jenkins-io-v1alpha2-jenkins,mutating=false,failurePolicy=fail,sideEffects=None,groups=jenkins.io.jenkins.io,resources=jenkins,verbs=create;update,versions=v1alpha2,name=vjenkins.kb.io,admissionReviewVersions={v1,v1beta1}
 | ||||||
| 
 | 
 | ||||||
|  | @ -79,14 +84,11 @@ func (in *Jenkins) ValidateDelete() error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type PluginDataManager struct { | type SecurityValidator struct { | ||||||
| 	PluginDataCache    PluginsInfo | 	PluginDataCache PluginsInfo | ||||||
| 	Timeout            time.Duration | 	isCached        bool | ||||||
| 	CompressedFilePath string | 	Attempts        int | ||||||
| 	PluginDataFile     string | 	checkingPeriod  time.Duration | ||||||
| 	IsCached           bool |  | ||||||
| 	Attempts           int |  | ||||||
| 	SleepTime          time.Duration |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type PluginsInfo struct { | type PluginsInfo struct { | ||||||
|  | @ -118,7 +120,7 @@ type PluginData struct { | ||||||
| 
 | 
 | ||||||
| // Validates security warnings for both updating and creating a Jenkins CR
 | // Validates security warnings for both updating and creating a Jenkins CR
 | ||||||
| func Validate(r Jenkins) error { | func Validate(r Jenkins) error { | ||||||
| 	if !PluginsMgr.IsCached { | 	if !SecValidator.isCached { | ||||||
| 		return errors.New("plugins data has not been fetched") | 		return errors.New("plugins data has not been fetched") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -140,7 +142,7 @@ func Validate(r Jenkins) error { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for _, plugin := range PluginsMgr.PluginDataCache.Plugins { | 	for _, plugin := range SecValidator.PluginDataCache.Plugins { | ||||||
| 		if pluginData, ispresent := pluginSet[plugin.Name]; ispresent { | 		if pluginData, ispresent := pluginSet[plugin.Name]; ispresent { | ||||||
| 			var hasVulnerabilities bool | 			var hasVulnerabilities bool | ||||||
| 			for _, warning := range plugin.SecurityWarnings { | 			for _, warning := range plugin.SecurityWarnings { | ||||||
|  | @ -184,44 +186,42 @@ func Validate(r Jenkins) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewPluginsDataManager(compressedFilePath string, pluginDataFile string, isCached bool, timeout time.Duration) *PluginDataManager { | // NewMonitor creates a new worker and instantiates all the data structures required
 | ||||||
| 	return &PluginDataManager{ | func NewSecurityValidator() *SecurityValidator { | ||||||
| 		CompressedFilePath: compressedFilePath, | 	return &SecurityValidator{ | ||||||
| 		PluginDataFile:     pluginDataFile, | 		isCached:       false, | ||||||
| 		IsCached:           isCached, | 		Attempts:       0, | ||||||
| 		Timeout:            timeout, | 		checkingPeriod: shortenedCheckingPeriod, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (in *PluginDataManager) ManagePluginData(sig chan bool) { | func (in *SecurityValidator) MonitorSecurityWarnings(securityWarningsFetched chan bool) { | ||||||
| 	var isInit bool | 	jenkinslog.Info("Security warnings check: enabled\n") | ||||||
| 	var retryInterval time.Duration |  | ||||||
| 	for { | 	for { | ||||||
| 		var isCached bool | 		in.checkForSecurityVulnerabilities(securityWarningsFetched) | ||||||
| 		err := in.fetchPluginData() | 		<-time.After(in.checkingPeriod) | ||||||
| 		if err == nil { | 	} | ||||||
| 			isCached = true | } | ||||||
| 		} else { |  | ||||||
| 			jenkinslog.Info("Cache plugin data", "failed to fetch plugin data", err) |  | ||||||
| 		} |  | ||||||
| 		// should only be executed once when the operator starts
 |  | ||||||
| 		if !isInit { |  | ||||||
| 			sig <- isCached // sending signal to main to continue
 |  | ||||||
| 			isInit = true |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		in.IsCached = in.IsCached || isCached | func (in *SecurityValidator) checkForSecurityVulnerabilities(securityWarningsFetched chan bool) { | ||||||
| 		if !isCached { | 	err := in.fetchPluginData() | ||||||
| 			retryInterval = time.Duration(1) * time.Hour | 	if err != nil { | ||||||
| 		} else { | 		jenkinslog.Info("Cache plugin data", "failed to fetch plugin data", err) | ||||||
| 			retryInterval = time.Duration(12) * time.Hour | 		in.checkingPeriod = shortenedCheckingPeriod | ||||||
| 		} | 		return | ||||||
| 		time.Sleep(retryInterval) | 	} | ||||||
|  | 	in.isCached = true | ||||||
|  | 	in.checkingPeriod = defaultCheckingPeriod | ||||||
|  | 
 | ||||||
|  | 	// should only be executed once when the operator starts
 | ||||||
|  | 	if !initialSecurityWarningsDownloadSucceded { | ||||||
|  | 		securityWarningsFetched <- in.isCached | ||||||
|  | 		initialSecurityWarningsDownloadSucceded = true | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Downloads extracts and reads the JSON data in every 12 hours
 | // Downloads extracts and reads the JSON data in every 12 hours
 | ||||||
| func (in *PluginDataManager) fetchPluginData() error { | func (in *SecurityValidator) fetchPluginData() error { | ||||||
| 	jenkinslog.Info("Initializing/Updating the plugin data cache") | 	jenkinslog.Info("Initializing/Updating the plugin data cache") | ||||||
| 	var err error | 	var err error | ||||||
| 	for in.Attempts = 0; in.Attempts < 5; in.Attempts++ { | 	for in.Attempts = 0; in.Attempts < 5; in.Attempts++ { | ||||||
|  | @ -262,29 +262,36 @@ func (in *PluginDataManager) fetchPluginData() error { | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (in *PluginDataManager) download() error { | func (in *SecurityValidator) download() error { | ||||||
| 	out, err := os.Create(in.CompressedFilePath) | 	out, err := os.Create(CompressedFilePath) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	defer out.Close() | 	defer out.Close() | ||||||
| 
 | 
 | ||||||
| 	client := http.Client{ | 	req, err := http.NewRequest(http.MethodGet, Hosturl, nil) | ||||||
| 		Timeout: in.Timeout, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	resp, err := client.Get(Hosturl) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	defer resp.Body.Close() | 	req.Header.Set("Content-Type", "application/json") | ||||||
| 
 | 
 | ||||||
| 	_, err = io.Copy(out, resp.Body) | 	Client := http.Client{ | ||||||
|  | 		Timeout: 1 * time.Minute, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	response, err := Client.Do(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer response.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	_, err = io.Copy(out, response.Body) | ||||||
| 	return err | 	return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (in *PluginDataManager) extract() error { | func (in *SecurityValidator) extract() error { | ||||||
| 	reader, err := os.Open(in.CompressedFilePath) | 	reader, err := os.Open(CompressedFilePath) | ||||||
| 
 | 
 | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
|  | @ -296,7 +303,7 @@ func (in *PluginDataManager) extract() error { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	defer archive.Close() | 	defer archive.Close() | ||||||
| 	writer, err := os.Create(in.PluginDataFile) | 	writer, err := os.Create(PluginDataFile) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -307,8 +314,8 @@ func (in *PluginDataManager) extract() error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Loads the JSON data into memory and stores it
 | // Loads the JSON data into memory and stores it
 | ||||||
| func (in *PluginDataManager) cache() error { | func (in *SecurityValidator) cache() error { | ||||||
| 	jsonFile, err := os.Open(in.PluginDataFile) | 	jsonFile, err := os.Open(PluginDataFile) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -83,9 +83,9 @@ func TestValidate(t *testing.T) { | ||||||
| 		assert.Equal(t, got, errors.New("plugins data has not been fetched")) | 		assert.Equal(t, got, errors.New("plugins data has not been fetched")) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	PluginsMgr.IsCached = true | 	SecValidator.isCached = true | ||||||
| 	t.Run("Validating a Jenkins CR with plugins not having security warnings and validation is turned on", func(t *testing.T) { | 	t.Run("Validating a Jenkins CR with plugins not having security warnings and validation is turned on", func(t *testing.T) { | ||||||
| 		PluginsMgr.PluginDataCache = PluginsInfo{Plugins: []PluginInfo{ | 		SecValidator.PluginDataCache = PluginsInfo{Plugins: []PluginInfo{ | ||||||
| 			{Name: "security-script"}, | 			{Name: "security-script"}, | ||||||
| 			{Name: "git-client"}, | 			{Name: "git-client"}, | ||||||
| 			{Name: "git"}, | 			{Name: "git"}, | ||||||
|  | @ -100,7 +100,7 @@ func TestValidate(t *testing.T) { | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	t.Run("Validating a Jenkins CR with some of the plugins having security warnings and validation is turned on", func(t *testing.T) { | 	t.Run("Validating a Jenkins CR with some of the plugins having security warnings and validation is turned on", func(t *testing.T) { | ||||||
| 		PluginsMgr.PluginDataCache = PluginsInfo{Plugins: []PluginInfo{ | 		SecValidator.PluginDataCache = PluginsInfo{Plugins: []PluginInfo{ | ||||||
| 			{Name: "security-script", SecurityWarnings: createSecurityWarnings("1.2", "2.2")}, | 			{Name: "security-script", SecurityWarnings: createSecurityWarnings("1.2", "2.2")}, | ||||||
| 			{Name: "workflow-cps", SecurityWarnings: createSecurityWarnings("2.59", "")}, | 			{Name: "workflow-cps", SecurityWarnings: createSecurityWarnings("2.59", "")}, | ||||||
| 			{Name: "git-client"}, | 			{Name: "git-client"}, | ||||||
|  | @ -118,7 +118,7 @@ func TestValidate(t *testing.T) { | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	t.Run("Updating a Jenkins CR with some of the plugins having security warnings and validation is turned on", func(t *testing.T) { | 	t.Run("Updating a Jenkins CR with some of the plugins having security warnings and validation is turned on", func(t *testing.T) { | ||||||
| 		PluginsMgr.PluginDataCache = PluginsInfo{Plugins: []PluginInfo{ | 		SecValidator.PluginDataCache = PluginsInfo{Plugins: []PluginInfo{ | ||||||
| 			{Name: "handy-uri-templates-2-api", SecurityWarnings: createSecurityWarnings("2.1.8-1.0", "2.2.8-1.0")}, | 			{Name: "handy-uri-templates-2-api", SecurityWarnings: createSecurityWarnings("2.1.8-1.0", "2.2.8-1.0")}, | ||||||
| 			{Name: "workflow-cps", SecurityWarnings: createSecurityWarnings("2.59", "")}, | 			{Name: "workflow-cps", SecurityWarnings: createSecurityWarnings("2.59", "")}, | ||||||
| 			{Name: "resource-disposer", SecurityWarnings: createSecurityWarnings("0.7", "1.2")}, | 			{Name: "resource-disposer", SecurityWarnings: createSecurityWarnings("0.7", "1.2")}, | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ package v1alpha2 | ||||||
| import ( | import ( | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	rbacv1 "k8s.io/api/rbac/v1" | 	rbacv1 "k8s.io/api/rbac/v1" | ||||||
| 	runtime "k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // 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.
 | ||||||
|  | @ -356,6 +356,13 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { | ||||||
| 		*out = make([]Plugin, len(*in)) | 		*out = make([]Plugin, len(*in)) | ||||||
| 		copy(*out, *in) | 		copy(*out, *in) | ||||||
| 	} | 	} | ||||||
|  | 	if in.HostAliases != nil { | ||||||
|  | 		in, out := &in.HostAliases, &out.HostAliases | ||||||
|  | 		*out = make([]corev1.HostAlias, len(*in)) | ||||||
|  | 		for i := range *in { | ||||||
|  | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsMaster.
 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsMaster.
 | ||||||
|  | @ -528,6 +535,65 @@ func (in *Plugin) DeepCopy() *Plugin { | ||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
|  | func (in *PluginData) DeepCopyInto(out *PluginData) { | ||||||
|  | 	*out = *in | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginData.
 | ||||||
|  | func (in *PluginData) DeepCopy() *PluginData { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(PluginData) | ||||||
|  | 	in.DeepCopyInto(out) | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
|  | func (in *PluginInfo) DeepCopyInto(out *PluginInfo) { | ||||||
|  | 	*out = *in | ||||||
|  | 	if in.SecurityWarnings != nil { | ||||||
|  | 		in, out := &in.SecurityWarnings, &out.SecurityWarnings | ||||||
|  | 		*out = make([]Warning, len(*in)) | ||||||
|  | 		for i := range *in { | ||||||
|  | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginInfo.
 | ||||||
|  | func (in *PluginInfo) DeepCopy() *PluginInfo { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(PluginInfo) | ||||||
|  | 	in.DeepCopyInto(out) | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
|  | func (in *PluginsInfo) DeepCopyInto(out *PluginsInfo) { | ||||||
|  | 	*out = *in | ||||||
|  | 	if in.Plugins != nil { | ||||||
|  | 		in, out := &in.Plugins, &out.Plugins | ||||||
|  | 		*out = make([]PluginInfo, len(*in)) | ||||||
|  | 		for i := range *in { | ||||||
|  | 			(*in)[i].DeepCopyInto(&(*out)[i]) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PluginsInfo.
 | ||||||
|  | func (in *PluginsInfo) DeepCopy() *PluginsInfo { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(PluginsInfo) | ||||||
|  | 	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 *Restore) DeepCopyInto(out *Restore) { | func (in *Restore) DeepCopyInto(out *Restore) { | ||||||
| 	*out = *in | 	*out = *in | ||||||
|  | @ -593,6 +659,22 @@ func (in *SecretRef) DeepCopy() *SecretRef { | ||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
|  | func (in *SecurityValidator) DeepCopyInto(out *SecurityValidator) { | ||||||
|  | 	*out = *in | ||||||
|  | 	in.PluginDataCache.DeepCopyInto(&out.PluginDataCache) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecurityValidator.
 | ||||||
|  | func (in *SecurityValidator) DeepCopy() *SecurityValidator { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(SecurityValidator) | ||||||
|  | 	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 | ||||||
|  | @ -679,3 +761,38 @@ func (in *Slack) DeepCopy() *Slack { | ||||||
| 	in.DeepCopyInto(out) | 	in.DeepCopyInto(out) | ||||||
| 	return out | 	return out | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
|  | func (in *Version) DeepCopyInto(out *Version) { | ||||||
|  | 	*out = *in | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Version.
 | ||||||
|  | func (in *Version) DeepCopy() *Version { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(Version) | ||||||
|  | 	in.DeepCopyInto(out) | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 | ||||||
|  | func (in *Warning) DeepCopyInto(out *Warning) { | ||||||
|  | 	*out = *in | ||||||
|  | 	if in.Versions != nil { | ||||||
|  | 		in, out := &in.Versions, &out.Versions | ||||||
|  | 		*out = make([]Version, len(*in)) | ||||||
|  | 		copy(*out, *in) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Warning.
 | ||||||
|  | func (in *Warning) DeepCopy() *Warning { | ||||||
|  | 	if in == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	out := new(Warning) | ||||||
|  | 	in.DeepCopyInto(out) | ||||||
|  | 	return out | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -36,10 +36,6 @@ spec: | ||||||
|           spec: |           spec: | ||||||
|             description: Spec defines the desired state of the Jenkins |             description: Spec defines the desired state of the Jenkins | ||||||
|             properties: |             properties: | ||||||
|               ValidateSecurityWarnings: |  | ||||||
|                 description: ValidateSecurityWarnings enables or disables validating |  | ||||||
|                   potential security warnings in Jenkins plugins via admission webhooks. |  | ||||||
|                 type: boolean |  | ||||||
|               backup: |               backup: | ||||||
|                 description: 'Backup defines configuration of Jenkins backup More |                 description: 'Backup defines configuration of Jenkins backup More | ||||||
|                   info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' |                   info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' | ||||||
|  | @ -1108,6 +1104,22 @@ spec: | ||||||
|                     description: DisableCSRFProtection allows you to toggle CSRF Protection |                     description: DisableCSRFProtection allows you to toggle CSRF Protection | ||||||
|                       on Jenkins |                       on Jenkins | ||||||
|                     type: boolean |                     type: boolean | ||||||
|  |                   hostAliases: | ||||||
|  |                     description: HostAliases for Jenkins master pod and SeedJob agent | ||||||
|  |                     items: | ||||||
|  |                       description: HostAlias holds the mapping between IP and hostnames | ||||||
|  |                         that will be injected as an entry in the pod's hosts file. | ||||||
|  |                       properties: | ||||||
|  |                         hostnames: | ||||||
|  |                           description: Hostnames for the above IP address. | ||||||
|  |                           items: | ||||||
|  |                             type: string | ||||||
|  |                           type: array | ||||||
|  |                         ip: | ||||||
|  |                           description: IP address of the host file entry. | ||||||
|  |                           type: string | ||||||
|  |                       type: object | ||||||
|  |                     type: array | ||||||
|                   imagePullSecrets: |                   imagePullSecrets: | ||||||
|                     description: 'ImagePullSecrets is an optional list of references |                     description: 'ImagePullSecrets is an optional list of references | ||||||
|                       to secrets in the same namespace to use for pulling any of the |                       to secrets in the same namespace to use for pulling any of the | ||||||
|  | @ -3320,6 +3332,10 @@ spec: | ||||||
|                       More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' |                       More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' | ||||||
|                     type: string |                     type: string | ||||||
|                 type: object |                 type: object | ||||||
|  |               validateSecurityWarnings: | ||||||
|  |                 description: ValidateSecurityWarnings enables or disables validating | ||||||
|  |                   potential security warnings in Jenkins plugins via admission webhooks. | ||||||
|  |                 type: boolean | ||||||
|             required: |             required: | ||||||
|             - jenkinsAPISettings |             - jenkinsAPISettings | ||||||
|             - master |             - master | ||||||
|  |  | ||||||
|  | @ -145,7 +145,7 @@ spec: | ||||||
|     securityContext: |     securityContext: | ||||||
|     {{- toYaml . | nindent 6 }} |     {{- toYaml . | nindent 6 }} | ||||||
|     {{- end }} |     {{- end }} | ||||||
|   ValidateSecurityWarnings: {{ .Values.jenkins.ValidateSecurityWarnings }} |   validateSecurityWarnings: {{ .Values.jenkins.validateSecurityWarnings }} | ||||||
|   {{- with .Values.jenkins.seedJobs }} |   {{- with .Values.jenkins.seedJobs }} | ||||||
|   seedJobs: {{- toYaml . | nindent 4 }} |   seedJobs: {{- toYaml . | nindent 4 }} | ||||||
|   {{- end }} |   {{- end }} | ||||||
|  |  | ||||||
|  | @ -48,8 +48,8 @@ jenkins: | ||||||
|   disableCSRFProtection: false |   disableCSRFProtection: false | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   # ValidateSecurityWarnings enables or disables validating potential security warnings in Jenkins plugins via admission webhooks. |   # validateSecurityWarnings enables or disables validating potential security warnings in Jenkins plugins via admission webhooks. | ||||||
|   ValidateSecurityWarnings: false |   validateSecurityWarnings: false | ||||||
|    |    | ||||||
|   # imagePullSecrets is used if you want to pull images from private repository |   # imagePullSecrets is used if you want to pull images from private repository | ||||||
|   # See https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration/#pulling-docker-images-from-private-repositories for more info |   # See https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration/#pulling-docker-images-from-private-repositories for more info | ||||||
|  | @ -110,7 +110,7 @@ jenkins: | ||||||
|   # See https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ for details |   # See https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ for details | ||||||
|   resources: |   resources: | ||||||
|     limits: |     limits: | ||||||
|       cpu: 1500m |       cpu: 1000m | ||||||
|       memory: 3Gi |       memory: 3Gi | ||||||
|     requests: |     requests: | ||||||
|       cpu: 1 |       cpu: 1 | ||||||
|  | @ -146,26 +146,26 @@ jenkins: | ||||||
| 
 | 
 | ||||||
|   # LivenessProbe for Jenkins Master pod |   # LivenessProbe for Jenkins Master pod | ||||||
|   livenessProbe: |   livenessProbe: | ||||||
|     failureThreshold: 12 |     failureThreshold: 20 | ||||||
|     httpGet: |     httpGet: | ||||||
|       path: /login |       path: /login | ||||||
|       port: http |       port: http | ||||||
|       scheme: HTTP |       scheme: HTTP | ||||||
|     initialDelaySeconds: 80 |     initialDelaySeconds: 100 | ||||||
|     periodSeconds: 10 |     periodSeconds: 10 | ||||||
|     successThreshold: 1 |     successThreshold: 1 | ||||||
|     timeoutSeconds: 5 |     timeoutSeconds: 8 | ||||||
|   # ReadinessProbe for Jenkins Master pod |   # ReadinessProbe for Jenkins Master pod | ||||||
|   readinessProbe: |   readinessProbe: | ||||||
|     failureThreshold: 3 |     failureThreshold: 60 | ||||||
|     httpGet: |     httpGet: | ||||||
|       path: /login |       path: /login | ||||||
|       port: http |       port: http | ||||||
|       scheme: HTTP |       scheme: HTTP | ||||||
|     initialDelaySeconds: 30 |     initialDelaySeconds: 120 | ||||||
|     periodSeconds: 10 |     periodSeconds: 10 | ||||||
|     successThreshold: 1 |     successThreshold: 1 | ||||||
|     timeoutSeconds: 1 |     timeoutSeconds: 8 | ||||||
| 
 | 
 | ||||||
|   # backup is section for configuring operator's backup feature |   # backup is section for configuring operator's backup feature | ||||||
|   # By default backup feature is enabled and pre-configured |   # By default backup feature is enabled and pre-configured | ||||||
|  | @ -215,11 +215,11 @@ jenkins: | ||||||
|     # resources used by backup container |     # resources used by backup container | ||||||
|     resources: |     resources: | ||||||
|       limits: |       limits: | ||||||
|         cpu: 1500m |         cpu: 1000m | ||||||
|         memory: 1Gi |         memory: 2Gi | ||||||
|       requests: |       requests: | ||||||
|         cpu: 100m |         cpu: 100m | ||||||
|         memory: 256Mi |         memory: 500Mi | ||||||
| 
 | 
 | ||||||
|     # env contains container environment variables |     # env contains container environment variables | ||||||
|     # PVC backup provider handles these variables: |     # PVC backup provider handles these variables: | ||||||
|  |  | ||||||
|  | @ -36,10 +36,6 @@ spec: | ||||||
|           spec: |           spec: | ||||||
|             description: Spec defines the desired state of the Jenkins |             description: Spec defines the desired state of the Jenkins | ||||||
|             properties: |             properties: | ||||||
|               ValidateSecurityWarnings: |  | ||||||
|                 description: ValidateSecurityWarnings enables or disables validating |  | ||||||
|                   potential security warnings in Jenkins plugins via admission webhooks. |  | ||||||
|                 type: boolean |  | ||||||
|               backup: |               backup: | ||||||
|                 description: 'Backup defines configuration of Jenkins backup More |                 description: 'Backup defines configuration of Jenkins backup More | ||||||
|                   info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' |                   info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' | ||||||
|  | @ -3336,6 +3332,10 @@ spec: | ||||||
|                       More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' |                       More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' | ||||||
|                     type: string |                     type: string | ||||||
|                 type: object |                 type: object | ||||||
|  |               validateSecurityWarnings: | ||||||
|  |                 description: ValidateSecurityWarnings enables or disables validating | ||||||
|  |                   potential security warnings in Jenkins plugins via admission webhooks. | ||||||
|  |                 type: boolean | ||||||
|             required: |             required: | ||||||
|             - jenkinsAPISettings |             - jenkinsAPISettings | ||||||
|             - master |             - master | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								main.go
								
								
								
								
							
							
						
						
									
										14
									
								
								main.go
								
								
								
								
							|  | @ -78,7 +78,7 @@ func main() { | ||||||
| 	var metricsAddr string | 	var metricsAddr string | ||||||
| 	var enableLeaderElection bool | 	var enableLeaderElection bool | ||||||
| 	var probeAddr string | 	var probeAddr string | ||||||
| 	var ValidateSecurityWarnings bool | 	var validateSecurityWarnings bool | ||||||
| 
 | 
 | ||||||
| 	isRunningInCluster, err := resources.IsRunningInCluster() | 	isRunningInCluster, err := resources.IsRunningInCluster() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -89,7 +89,7 @@ func main() { | ||||||
| 	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") | 	flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") | ||||||
| 	flag.BoolVar(&enableLeaderElection, "leader-elect", isRunningInCluster, "Enable leader election for controller manager. "+ | 	flag.BoolVar(&enableLeaderElection, "leader-elect", isRunningInCluster, "Enable leader election for controller manager. "+ | ||||||
| 		"Enabling this will ensure there is only one active controller manager.") | 		"Enabling this will ensure there is only one active controller manager.") | ||||||
| 	flag.BoolVar(&ValidateSecurityWarnings, "validate-security-warnings", false, "Enable validation for potential security warnings in jenkins custom resource plugins") | 	flag.BoolVar(&validateSecurityWarnings, "validate-security-warnings", false, "Enable validation for potential security warnings in jenkins custom resource plugins") | ||||||
| 	hostname := flag.String("jenkins-api-hostname", "", "Hostname or IP of Jenkins API. It can be service name, node IP or localhost.") | 	hostname := flag.String("jenkins-api-hostname", "", "Hostname or IP of Jenkins API. It can be service name, node IP or localhost.") | ||||||
| 	port := flag.Int("jenkins-api-port", 0, "The port on which Jenkins API is running. Note: If you want to use nodePort don't set this setting and --jenkins-api-use-nodeport must be true.") | 	port := flag.Int("jenkins-api-port", 0, "The port on which Jenkins API is running. Note: If you want to use nodePort don't set this setting and --jenkins-api-use-nodeport must be true.") | ||||||
| 	useNodePort := flag.Bool("jenkins-api-use-nodeport", false, "Connect to Jenkins API using the service nodePort instead of service port. If you want to set this as true - don't set --jenkins-api-port.") | 	useNodePort := flag.Bool("jenkins-api-use-nodeport", false, "Connect to Jenkins API using the service nodePort instead of service port. If you want to set this as true - don't set --jenkins-api-port.") | ||||||
|  | @ -111,11 +111,11 @@ func main() { | ||||||
| 	} | 	} | ||||||
| 	logger.Info(fmt.Sprintf("Watch namespace: %v", namespace)) | 	logger.Info(fmt.Sprintf("Watch namespace: %v", namespace)) | ||||||
| 
 | 
 | ||||||
| 	if ValidateSecurityWarnings { | 	if validateSecurityWarnings { | ||||||
| 		isInitialized := make(chan bool) | 		securityWarningsFetched := make(chan bool) | ||||||
| 		go v1alpha2.PluginsMgr.ManagePluginData(isInitialized) | 		go v1alpha2.SecValidator.MonitorSecurityWarnings(securityWarningsFetched) | ||||||
| 
 | 
 | ||||||
| 		if !<-isInitialized { | 		if !<-securityWarningsFetched { | ||||||
| 			logger.Info("Unable to get the plugins data") | 			logger.Info("Unable to get the plugins data") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -180,7 +180,7 @@ func main() { | ||||||
| 		fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug) | 		fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if ValidateSecurityWarnings { | 	if validateSecurityWarnings { | ||||||
| 		if err = (&v1alpha2.Jenkins{}).SetupWebhookWithManager(mgr); err != nil { | 		if err = (&v1alpha2.Jenkins{}).SetupWebhookWithManager(mgr); err != nil { | ||||||
| 			fatal(errors.Wrap(err, "unable to create Webhook"), *debug) | 			fatal(errors.Wrap(err, "unable to create Webhook"), *debug) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -1,11 +1,13 @@ | ||||||
| package e2e | package e2e | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/api/v1alpha2" | 	"github.com/jenkinsci/kubernetes-operator/api/v1alpha2" | ||||||
| 
 | 
 | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
|  | 	. "github.com/onsi/gomega" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	// +kubebuilder:scaffold:imports
 | 	// +kubebuilder:scaffold:imports
 | ||||||
| ) | ) | ||||||
|  | @ -74,10 +76,10 @@ var _ = Describe("Jenkins controller configuration", func() { | ||||||
| 
 | 
 | ||||||
| 	BeforeEach(func() { | 	BeforeEach(func() { | ||||||
| 		namespace = CreateNamespace() | 		namespace = CreateNamespace() | ||||||
| 
 |  | ||||||
| 		createUserConfigurationSecret(namespace.Name, userConfigurationSecretData) | 		createUserConfigurationSecret(namespace.Name, userConfigurationSecretData) | ||||||
| 		createUserConfigurationConfigMap(namespace.Name, numberOfExecutorsEnvName, fmt.Sprintf("${%s}", systemMessageEnvName)) | 		createUserConfigurationConfigMap(namespace.Name, numberOfExecutorsEnvName, fmt.Sprintf("${%s}", systemMessageEnvName)) | ||||||
| 		jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) | 		jenkins = RenderJenkinsCR(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) | ||||||
|  | 		Expect(K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) | ||||||
| 		createDefaultLimitsForContainersInNamespace(namespace.Name) | 		createDefaultLimitsForContainersInNamespace(namespace.Name) | ||||||
| 		createKubernetesCredentialsProviderSecret(namespace.Name, mySeedJob) | 		createKubernetesCredentialsProviderSecret(namespace.Name, mySeedJob) | ||||||
| 	}) | 	}) | ||||||
|  | @ -125,7 +127,8 @@ var _ = Describe("Jenkins controller priority class", func() { | ||||||
| 
 | 
 | ||||||
| 	BeforeEach(func() { | 	BeforeEach(func() { | ||||||
| 		namespace = CreateNamespace() | 		namespace = CreateNamespace() | ||||||
| 		jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, nil, groovyScripts, casc, priorityClassName) | 		jenkins = RenderJenkinsCR(jenkinsCRName, namespace.Name, nil, groovyScripts, casc, priorityClassName) | ||||||
|  | 		Expect(K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	AfterEach(func() { | 	AfterEach(func() { | ||||||
|  | @ -177,7 +180,8 @@ var _ = Describe("Jenkins controller plugins test", func() { | ||||||
| 
 | 
 | ||||||
| 	BeforeEach(func() { | 	BeforeEach(func() { | ||||||
| 		namespace = CreateNamespace() | 		namespace = CreateNamespace() | ||||||
| 		jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) | 		jenkins = RenderJenkinsCR(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) | ||||||
|  | 		Expect(K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	AfterEach(func() { | 	AfterEach(func() { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,8 @@ | ||||||
| package e2e | package e2e | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/api/v1alpha2" | 	"github.com/jenkinsci/kubernetes-operator/api/v1alpha2" | ||||||
| 
 | 
 | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
|  | @ -35,7 +37,8 @@ var _ = Describe("Jenkins controller", func() { | ||||||
| 		namespace = CreateNamespace() | 		namespace = CreateNamespace() | ||||||
| 
 | 
 | ||||||
| 		configureAuthorizationToUnSecure(namespace.Name, userConfigurationConfigMapName) | 		configureAuthorizationToUnSecure(namespace.Name, userConfigurationConfigMapName) | ||||||
| 		jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, nil, groovyScripts, casc, priorityClassName) | 		jenkins = RenderJenkinsCR(jenkinsCRName, namespace.Name, nil, groovyScripts, casc, priorityClassName) | ||||||
|  | 		Expect(K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	AfterEach(func() { | 	AfterEach(func() { | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ import ( | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	rbacv1 "k8s.io/api/rbac/v1" | 	rbacv1 "k8s.io/api/rbac/v1" | ||||||
|  | 	"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/labels" | 	"k8s.io/apimachinery/pkg/labels" | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
|  | @ -46,112 +47,6 @@ func getJenkinsMasterPod(jenkins *v1alpha2.Jenkins) *corev1.Pod { | ||||||
| 	return &pods.Items[0] | 	return &pods.Items[0] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { |  | ||||||
| 	var seedJobs []v1alpha2.SeedJob |  | ||||||
| 	if seedJob != nil { |  | ||||||
| 		seedJobs = append(seedJobs, *seedJob...) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	jenkins := &v1alpha2.Jenkins{ |  | ||||||
| 		TypeMeta: v1alpha2.JenkinsTypeMeta(), |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      name, |  | ||||||
| 			Namespace: namespace, |  | ||||||
| 		}, |  | ||||||
| 		Spec: v1alpha2.JenkinsSpec{ |  | ||||||
| 			GroovyScripts:       groovyScripts, |  | ||||||
| 			ConfigurationAsCode: casc, |  | ||||||
| 			Master: v1alpha2.JenkinsMaster{ |  | ||||||
| 				Annotations: map[string]string{"test": "label"}, |  | ||||||
| 				Containers: []v1alpha2.Container{ |  | ||||||
| 					{ |  | ||||||
| 						Name: resources.JenkinsMasterContainerName, |  | ||||||
| 						Env: []corev1.EnvVar{ |  | ||||||
| 							{ |  | ||||||
| 								Name:  "TEST_ENV", |  | ||||||
| 								Value: "test_env_value", |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 						ReadinessProbe: &corev1.Probe{ |  | ||||||
| 							Handler: corev1.Handler{ |  | ||||||
| 								HTTPGet: &corev1.HTTPGetAction{ |  | ||||||
| 									Path:   "/login", |  | ||||||
| 									Port:   intstr.FromString("http"), |  | ||||||
| 									Scheme: corev1.URISchemeHTTP, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 							InitialDelaySeconds: int32(100), |  | ||||||
| 							TimeoutSeconds:      int32(4), |  | ||||||
| 							FailureThreshold:    int32(12), |  | ||||||
| 							SuccessThreshold:    int32(1), |  | ||||||
| 							PeriodSeconds:       int32(1), |  | ||||||
| 						}, |  | ||||||
| 						LivenessProbe: &corev1.Probe{ |  | ||||||
| 							Handler: corev1.Handler{ |  | ||||||
| 								HTTPGet: &corev1.HTTPGetAction{ |  | ||||||
| 									Path:   "/login", |  | ||||||
| 									Port:   intstr.FromString("http"), |  | ||||||
| 									Scheme: corev1.URISchemeHTTP, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 							InitialDelaySeconds: int32(80), |  | ||||||
| 							TimeoutSeconds:      int32(4), |  | ||||||
| 							FailureThreshold:    int32(30), |  | ||||||
| 							SuccessThreshold:    int32(1), |  | ||||||
| 							PeriodSeconds:       int32(5), |  | ||||||
| 						}, |  | ||||||
| 						VolumeMounts: []corev1.VolumeMount{ |  | ||||||
| 							{ |  | ||||||
| 								Name:      "plugins-cache", |  | ||||||
| 								MountPath: "/usr/share/jenkins/ref/plugins", |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 					{ |  | ||||||
| 						Name:  "envoyproxy", |  | ||||||
| 						Image: "envoyproxy/envoy-alpine:v1.14.1", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Plugins: []v1alpha2.Plugin{ |  | ||||||
| 					{Name: "audit-trail", Version: "3.10"}, |  | ||||||
| 					{Name: "simple-theme-plugin", Version: "0.7"}, |  | ||||||
| 					{Name: "github", Version: "1.34.1"}, |  | ||||||
| 					{Name: "devoptics", Version: "1.1934", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1934/devoptics.hpi"}, |  | ||||||
| 				}, |  | ||||||
| 				PriorityClassName: priorityClassName, |  | ||||||
| 				NodeSelector:      map[string]string{"kubernetes.io/os": "linux"}, |  | ||||||
| 				Volumes: []corev1.Volume{ |  | ||||||
| 					{ |  | ||||||
| 						Name: "plugins-cache", |  | ||||||
| 						VolumeSource: corev1.VolumeSource{ |  | ||||||
| 							EmptyDir: &corev1.EmptyDirVolumeSource{}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			SeedJobs: seedJobs, |  | ||||||
| 			Service: v1alpha2.Service{ |  | ||||||
| 				Type: corev1.ServiceTypeNodePort, |  | ||||||
| 				Port: constants.DefaultHTTPPortInt32, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	jenkins.Spec.Roles = []rbacv1.RoleRef{ |  | ||||||
| 		{ |  | ||||||
| 			APIGroup: "rbac.authorization.k8s.io", |  | ||||||
| 			Kind:     "Role", |  | ||||||
| 			Name:     resources.GetResourceName(jenkins), |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	updateJenkinsCR(jenkins) |  | ||||||
| 
 |  | ||||||
| 	_, _ = fmt.Fprintf(GinkgoWriter, "Jenkins CR %+v\n", *jenkins) |  | ||||||
| 
 |  | ||||||
| 	Expect(K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) |  | ||||||
| 
 |  | ||||||
| 	return jenkins |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func createJenkinsCRSafeRestart(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { | func createJenkinsCRSafeRestart(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { | ||||||
| 	var seedJobs []v1alpha2.SeedJob | 	var seedJobs []v1alpha2.SeedJob | ||||||
| 	if seedJob != nil { | 	if seedJob != nil { | ||||||
|  | @ -171,7 +66,8 @@ func createJenkinsCRSafeRestart(name, namespace string, seedJob *[]v1alpha2.Seed | ||||||
| 				Annotations: map[string]string{"test": "label"}, | 				Annotations: map[string]string{"test": "label"}, | ||||||
| 				Containers: []v1alpha2.Container{ | 				Containers: []v1alpha2.Container{ | ||||||
| 					{ | 					{ | ||||||
| 						Name: resources.JenkinsMasterContainerName, | 						Name:  resources.JenkinsMasterContainerName, | ||||||
|  | 						Image: JenkinsTestImage, | ||||||
| 						Env: []corev1.EnvVar{ | 						Env: []corev1.EnvVar{ | ||||||
| 							{ | 							{ | ||||||
| 								Name:  "TEST_ENV", | 								Name:  "TEST_ENV", | ||||||
|  | @ -206,6 +102,16 @@ func createJenkinsCRSafeRestart(name, namespace string, seedJob *[]v1alpha2.Seed | ||||||
| 							SuccessThreshold:    int32(1), | 							SuccessThreshold:    int32(1), | ||||||
| 							PeriodSeconds:       int32(5), | 							PeriodSeconds:       int32(5), | ||||||
| 						}, | 						}, | ||||||
|  | 						Resources: corev1.ResourceRequirements{ | ||||||
|  | 							Requests: corev1.ResourceList{ | ||||||
|  | 								corev1.ResourceCPU:    resource.MustParse("1"), | ||||||
|  | 								corev1.ResourceMemory: resource.MustParse("500Mi"), | ||||||
|  | 							}, | ||||||
|  | 							Limits: corev1.ResourceList{ | ||||||
|  | 								corev1.ResourceCPU:    resource.MustParse("1000m"), | ||||||
|  | 								corev1.ResourceMemory: resource.MustParse("3Gi"), | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
| 						VolumeMounts: []corev1.VolumeMount{ | 						VolumeMounts: []corev1.VolumeMount{ | ||||||
| 							{ | 							{ | ||||||
| 								Name:      "plugins-cache", | 								Name:      "plugins-cache", | ||||||
|  |  | ||||||
|  | @ -8,7 +8,6 @@ import ( | ||||||
| 	"sort" | 	"sort" | ||||||
| 
 | 
 | ||||||
| 	"github.com/onsi/ginkgo" | 	"github.com/onsi/ginkgo" | ||||||
| 
 |  | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/api/events/v1beta1" | 	"k8s.io/api/events/v1beta1" | ||||||
| 	"k8s.io/apimachinery/pkg/labels" | 	"k8s.io/apimachinery/pkg/labels" | ||||||
|  | @ -109,17 +108,18 @@ func printKubernetesEvents(namespace string) { | ||||||
| 		_, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "Last %d events from kubernetes:\n", kubernetesEventsLimit) | 		_, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "Last %d events from kubernetes:\n", kubernetesEventsLimit) | ||||||
| 
 | 
 | ||||||
| 		for _, event := range events { | 		for _, event := range events { | ||||||
| 			_, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "%+v\n\n", event) | 			_, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "Event CreationTime: %s, Type: %s, Reason: %s, Object: %s %s/%s, Action: %s\n", | ||||||
|  | 				event.CreationTimestamp, event.Type, event.Reason, event.Regarding.Kind, event.Regarding.Namespace, event.Regarding.Name, event.Note) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func printKubernetesPods(namespace string) { | func printKubernetesPods(namespace string) { | ||||||
| 	_, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "All pods in '%s' namespace:\n", namespace) | 	_, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "\nAll pods in '%s' namespace:\n", namespace) | ||||||
| 
 | 
 | ||||||
| 	pod, err := getOperatorPod(namespace) | 	pod, err := getOperatorPod(namespace) | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| 		_, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "%+v\n\n", pod) | 		_, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "%s: %+v \n", pod.Name, pod.Status.Conditions) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -108,7 +108,8 @@ func createJenkinsWithBackupAndRestoreConfigured(name, namespace string) *v1alph | ||||||
| 			Master: v1alpha2.JenkinsMaster{ | 			Master: v1alpha2.JenkinsMaster{ | ||||||
| 				Containers: []v1alpha2.Container{ | 				Containers: []v1alpha2.Container{ | ||||||
| 					{ | 					{ | ||||||
| 						Name: resources.JenkinsMasterContainerName, | 						Name:  resources.JenkinsMasterContainerName, | ||||||
|  | 						Image: JenkinsTestImage, | ||||||
| 						VolumeMounts: []corev1.VolumeMount{ | 						VolumeMounts: []corev1.VolumeMount{ | ||||||
| 							{ | 							{ | ||||||
| 								Name:      "plugins-cache", | 								Name:      "plugins-cache", | ||||||
|  | @ -143,6 +144,16 @@ func createJenkinsWithBackupAndRestoreConfigured(name, namespace string) *v1alph | ||||||
| 							SuccessThreshold:    int32(1), | 							SuccessThreshold:    int32(1), | ||||||
| 							PeriodSeconds:       int32(5), | 							PeriodSeconds:       int32(5), | ||||||
| 						}, | 						}, | ||||||
|  | 						Resources: corev1.ResourceRequirements{ | ||||||
|  | 							Requests: corev1.ResourceList{ | ||||||
|  | 								corev1.ResourceCPU:    resource.MustParse("1"), | ||||||
|  | 								corev1.ResourceMemory: resource.MustParse("500Mi"), | ||||||
|  | 							}, | ||||||
|  | 							Limits: corev1.ResourceList{ | ||||||
|  | 								corev1.ResourceCPU:    resource.MustParse("1000m"), | ||||||
|  | 								corev1.ResourceMemory: resource.MustParse("3Gi"), | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
| 					}, | 					}, | ||||||
| 					{ | 					{ | ||||||
| 						Name:            containerName, | 						Name:            containerName, | ||||||
|  |  | ||||||
|  | @ -5,15 +5,24 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/api/v1alpha2" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" | ||||||
|  | 	"github.com/jenkinsci/kubernetes-operator/pkg/constants" | ||||||
|  | 
 | ||||||
| 	"github.com/onsi/ginkgo" | 	"github.com/onsi/ginkgo" | ||||||
| 	"github.com/onsi/gomega" | 	"github.com/onsi/gomega" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	rbacv1 "k8s.io/api/rbac/v1" | ||||||
|  | 	"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/util/intstr" | ||||||
| 	"k8s.io/client-go/rest" | 	"k8s.io/client-go/rest" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/client" | 	"sigs.k8s.io/controller-runtime/pkg/client" | ||||||
| 	"sigs.k8s.io/controller-runtime/pkg/envtest" | 	"sigs.k8s.io/controller-runtime/pkg/envtest" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | const JenkinsTestImage = "jenkins/jenkins:2.303.2-lts" | ||||||
|  | 
 | ||||||
| var ( | var ( | ||||||
| 	Cfg       *rest.Config | 	Cfg       *rest.Config | ||||||
| 	K8sClient client.Client | 	K8sClient client.Client | ||||||
|  | @ -59,3 +68,113 @@ func DestroyNamespace(namespace *corev1.Namespace) { | ||||||
| 		return !exists, nil | 		return !exists, nil | ||||||
| 	}, time.Second*120, time.Second).Should(gomega.BeTrue()) | 	}, time.Second*120, time.Second).Should(gomega.BeTrue()) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func RenderJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { | ||||||
|  | 	var seedJobs []v1alpha2.SeedJob | ||||||
|  | 	if seedJob != nil { | ||||||
|  | 		seedJobs = append(seedJobs, *seedJob...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &v1alpha2.Jenkins{ | ||||||
|  | 		TypeMeta: v1alpha2.JenkinsTypeMeta(), | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name:      name, | ||||||
|  | 			Namespace: namespace, | ||||||
|  | 		}, | ||||||
|  | 		Spec: v1alpha2.JenkinsSpec{ | ||||||
|  | 			GroovyScripts:       groovyScripts, | ||||||
|  | 			ConfigurationAsCode: casc, | ||||||
|  | 			Master: v1alpha2.JenkinsMaster{ | ||||||
|  | 				Annotations: map[string]string{"test": "label"}, | ||||||
|  | 				Containers: []v1alpha2.Container{ | ||||||
|  | 					{ | ||||||
|  | 						Name:  resources.JenkinsMasterContainerName, | ||||||
|  | 						Image: JenkinsTestImage, | ||||||
|  | 						Env: []corev1.EnvVar{ | ||||||
|  | 							{ | ||||||
|  | 								Name:  "TEST_ENV", | ||||||
|  | 								Value: "test_env_value", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						ReadinessProbe: &corev1.Probe{ | ||||||
|  | 							Handler: corev1.Handler{ | ||||||
|  | 								HTTPGet: &corev1.HTTPGetAction{ | ||||||
|  | 									Path:   "/login", | ||||||
|  | 									Port:   intstr.FromString("http"), | ||||||
|  | 									Scheme: corev1.URISchemeHTTP, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							InitialDelaySeconds: int32(100), | ||||||
|  | 							TimeoutSeconds:      int32(4), | ||||||
|  | 							FailureThreshold:    int32(12), | ||||||
|  | 							SuccessThreshold:    int32(1), | ||||||
|  | 							PeriodSeconds:       int32(1), | ||||||
|  | 						}, | ||||||
|  | 						LivenessProbe: &corev1.Probe{ | ||||||
|  | 							Handler: corev1.Handler{ | ||||||
|  | 								HTTPGet: &corev1.HTTPGetAction{ | ||||||
|  | 									Path:   "/login", | ||||||
|  | 									Port:   intstr.FromString("http"), | ||||||
|  | 									Scheme: corev1.URISchemeHTTP, | ||||||
|  | 								}, | ||||||
|  | 							}, | ||||||
|  | 							InitialDelaySeconds: int32(80), | ||||||
|  | 							TimeoutSeconds:      int32(4), | ||||||
|  | 							FailureThreshold:    int32(30), | ||||||
|  | 							SuccessThreshold:    int32(1), | ||||||
|  | 							PeriodSeconds:       int32(5), | ||||||
|  | 						}, | ||||||
|  | 						Resources: corev1.ResourceRequirements{ | ||||||
|  | 							Requests: corev1.ResourceList{ | ||||||
|  | 								corev1.ResourceCPU:    resource.MustParse("1"), | ||||||
|  | 								corev1.ResourceMemory: resource.MustParse("500Mi"), | ||||||
|  | 							}, | ||||||
|  | 							Limits: corev1.ResourceList{ | ||||||
|  | 								corev1.ResourceCPU:    resource.MustParse("1000m"), | ||||||
|  | 								corev1.ResourceMemory: resource.MustParse("3Gi"), | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 						VolumeMounts: []corev1.VolumeMount{ | ||||||
|  | 							{ | ||||||
|  | 								Name:      "plugins-cache", | ||||||
|  | 								MountPath: "/usr/share/jenkins/ref/plugins", | ||||||
|  | 							}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 					{ | ||||||
|  | 						Name:  "envoyproxy", | ||||||
|  | 						Image: "envoyproxy/envoy-alpine:v1.14.1", | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 				Plugins: []v1alpha2.Plugin{ | ||||||
|  | 					{Name: "audit-trail", Version: "3.10"}, | ||||||
|  | 					{Name: "simple-theme-plugin", Version: "0.7"}, | ||||||
|  | 					{Name: "github", Version: "1.34.1"}, | ||||||
|  | 					{Name: "devoptics", Version: "1.1934", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1934/devoptics.hpi"}, | ||||||
|  | 				}, | ||||||
|  | 				PriorityClassName: priorityClassName, | ||||||
|  | 				NodeSelector:      map[string]string{"kubernetes.io/os": "linux"}, | ||||||
|  | 				Volumes: []corev1.Volume{ | ||||||
|  | 					{ | ||||||
|  | 						Name: "plugins-cache", | ||||||
|  | 						VolumeSource: corev1.VolumeSource{ | ||||||
|  | 							EmptyDir: &corev1.EmptyDirVolumeSource{}, | ||||||
|  | 						}, | ||||||
|  | 					}, | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 			SeedJobs: seedJobs, | ||||||
|  | 			Service: v1alpha2.Service{ | ||||||
|  | 				Type: corev1.ServiceTypeNodePort, | ||||||
|  | 				Port: constants.DefaultHTTPPortInt32, | ||||||
|  | 			}, | ||||||
|  | 			Roles: []rbacv1.RoleRef{ | ||||||
|  | 				{ | ||||||
|  | 					APIGroup: "rbac.authorization.k8s.io", | ||||||
|  | 					Kind:     "Role", | ||||||
|  | 					Name:     "jenkins-operator-jenkins", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -7,20 +7,18 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/api/v1alpha2" | 	"github.com/jenkinsci/kubernetes-operator/api/v1alpha2" | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" |  | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/pkg/constants" |  | ||||||
| 	"github.com/jenkinsci/kubernetes-operator/test/e2e" | 	"github.com/jenkinsci/kubernetes-operator/test/e2e" | ||||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |  | ||||||
| 	"k8s.io/apimachinery/pkg/util/intstr" |  | ||||||
| 
 | 
 | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
| 	. "github.com/onsi/gomega" | 	. "github.com/onsi/gomega" | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
| 	// +kubebuilder:scaffold:imports
 | 	// +kubebuilder:scaffold:imports
 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var _ = Describe("Jenkins Controller with webhook", func() { | const jenkinsCRName = "jenkins" | ||||||
| 
 | 
 | ||||||
|  | var _ = Describe("Jenkins Controller", func() { | ||||||
| 	var ( | 	var ( | ||||||
| 		namespace *corev1.Namespace | 		namespace *corev1.Namespace | ||||||
| 	) | 	) | ||||||
|  | @ -42,14 +40,15 @@ var _ = Describe("Jenkins Controller with webhook", func() { | ||||||
| 			jenkins := &v1alpha2.Jenkins{ | 			jenkins := &v1alpha2.Jenkins{ | ||||||
| 				TypeMeta: v1alpha2.JenkinsTypeMeta(), | 				TypeMeta: v1alpha2.JenkinsTypeMeta(), | ||||||
| 				ObjectMeta: metav1.ObjectMeta{ | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
| 					Name:      "jenkins", | 					Name:      jenkinsCRName, | ||||||
| 					Namespace: namespace.Name, | 					Namespace: namespace.Name, | ||||||
| 				}, | 				}, | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | ||||||
| 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), | 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), | ||||||
| 				"--set-string", fmt.Sprintf("operator.image=%s", *imageName), "--install", "--wait") | 				"--set-string", fmt.Sprintf("jenkins.image=%s", "jenkins/jenkins:2.303.2-lts"), | ||||||
|  | 				"--set-string", fmt.Sprintf("operator.image=%s", *imageName), "--install") | ||||||
| 			output, err := cmd.CombinedOutput() | 			output, err := cmd.CombinedOutput() | ||||||
| 			Expect(err).NotTo(HaveOccurred(), string(output)) | 			Expect(err).NotTo(HaveOccurred(), string(output)) | ||||||
| 
 | 
 | ||||||
|  | @ -58,47 +57,77 @@ var _ = Describe("Jenkins Controller with webhook", func() { | ||||||
| 
 | 
 | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
|  | }) | ||||||
| 
 | 
 | ||||||
| 	Context("Deploys jenkins operator with helm charts with validating webhook and jenkins instance disabled", func() { | var _ = Describe("Jenkins Controller with security validator", func() { | ||||||
| 		It("Deploys operator,denies creating a jenkins cr and creates jenkins cr with validation turned off", func() { |  | ||||||
| 
 | 
 | ||||||
|  | 	var ( | ||||||
|  | 		namespace     *corev1.Namespace | ||||||
|  | 		seedJobs      = &[]v1alpha2.SeedJob{} | ||||||
|  | 		groovyScripts = v1alpha2.GroovyScripts{ | ||||||
|  | 			Customization: v1alpha2.Customization{ | ||||||
|  | 				Configurations: []v1alpha2.ConfigMapRef{}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		casc = v1alpha2.ConfigurationAsCode{ | ||||||
|  | 			Customization: v1alpha2.Customization{ | ||||||
|  | 				Configurations: []v1alpha2.ConfigMapRef{}, | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		invalidPlugins = []v1alpha2.Plugin{ | ||||||
|  | 			{Name: "simple-theme-plugin", Version: "0.6"}, | ||||||
|  | 			{Name: "audit-trail", Version: "3.5"}, | ||||||
|  | 			{Name: "github", Version: "1.29.0"}, | ||||||
|  | 		} | ||||||
|  | 		validPlugins = []v1alpha2.Plugin{ | ||||||
|  | 			{Name: "simple-theme-plugin", Version: "0.6"}, | ||||||
|  | 			{Name: "audit-trail", Version: "3.8"}, | ||||||
|  | 			{Name: "github", Version: "1.31.0"}, | ||||||
|  | 		} | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	BeforeEach(func() { | ||||||
|  | 		namespace = e2e.CreateNamespace() | ||||||
|  | 	}) | ||||||
|  | 	AfterEach(func() { | ||||||
|  | 		cmd := exec.Command("../../bin/helm", "delete", "jenkins", "--namespace", namespace.Name) | ||||||
|  | 		output, err := cmd.CombinedOutput() | ||||||
|  | 		Expect(err).NotTo(HaveOccurred(), string(output)) | ||||||
|  | 
 | ||||||
|  | 		e2e.ShowLogsIfTestHasFailed(CurrentGinkgoTestDescription().Failed, namespace.Name) | ||||||
|  | 		e2e.DestroyNamespace(namespace) | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	Context("When Jenkins CR contains plugins with security warnings", func() { | ||||||
|  | 		It("Denies creating a jenkins CR with a warning", func() { | ||||||
| 			By("Deploying the operator along with webhook and cert-manager") | 			By("Deploying the operator along with webhook and cert-manager") | ||||||
| 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | ||||||
| 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), "--set-string", fmt.Sprintf("operator.image=%s", *imageName), | 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), | ||||||
| 				"--set", fmt.Sprintf("webhook.enabled=%t", true), "--set", fmt.Sprintf("jenkins.enabled=%t", false), "--install", "--wait") | 				"--set-string", fmt.Sprintf("operator.image=%s", *imageName), | ||||||
|  | 				"--set", fmt.Sprintf("jenkins.securityValidator=%t", true), | ||||||
|  | 				"--set", fmt.Sprintf("jenkins.enabled=%t", false), | ||||||
|  | 				"--set", fmt.Sprintf("webhook.enabled=%t", true), "--install") | ||||||
| 			output, err := cmd.CombinedOutput() | 			output, err := cmd.CombinedOutput() | ||||||
| 			Expect(err).NotTo(HaveOccurred(), string(output)) | 			Expect(err).NotTo(HaveOccurred(), string(output)) | ||||||
| 
 | 
 | ||||||
| 			By("Waiting for the operator to fetch the plugin data ") | 			By("Waiting for the operator to fetch the plugin data") | ||||||
| 			time.Sleep(time.Duration(200) * time.Second) | 			time.Sleep(time.Duration(200) * time.Second) | ||||||
| 
 | 
 | ||||||
| 			By("Denying a create request for a Jenkins custom resource with some plugins having security warnings and validation is turned on") | 			By("Denying a create request for a Jenkins custom resource") | ||||||
| 			userplugins := []v1alpha2.Plugin{ | 			jenkins := e2e.RenderJenkinsCR(jenkinsCRName, namespace.Name, seedJobs, groovyScripts, casc, "") | ||||||
| 				{Name: "simple-theme-plugin", Version: "0.6"}, | 			jenkins.Spec.Master.Plugins = invalidPlugins | ||||||
| 				{Name: "audit-trail", Version: "3.5"}, | 			jenkins.Spec.ValidateSecurityWarnings = true | ||||||
| 				{Name: "github", Version: "1.29.0"}, |  | ||||||
| 			} |  | ||||||
| 			jenkins := CreateJenkinsCR("jenkins", namespace.Name, userplugins, true) |  | ||||||
| 			Expect(e2e.K8sClient.Create(context.TODO(), jenkins)).Should(MatchError("admission webhook \"vjenkins.kb.io\" denied the request: security vulnerabilities detected in the following user-defined plugins: \naudit-trail:3.5\ngithub:1.29.0")) | 			Expect(e2e.K8sClient.Create(context.TODO(), jenkins)).Should(MatchError("admission webhook \"vjenkins.kb.io\" denied the request: security vulnerabilities detected in the following user-defined plugins: \naudit-trail:3.5\ngithub:1.29.0")) | ||||||
| 
 |  | ||||||
| 			By("Creating the Jenkins resource with plugins not having any security warnings and validation is turned on") |  | ||||||
| 			userplugins = []v1alpha2.Plugin{ |  | ||||||
| 				{Name: "simple-theme-plugin", Version: "0.6"}, |  | ||||||
| 				{Name: "audit-trail", Version: "3.8"}, |  | ||||||
| 				{Name: "github", Version: "1.31.0"}, |  | ||||||
| 			} |  | ||||||
| 			jenkins = CreateJenkinsCR("jenkins", namespace.Name, userplugins, true) |  | ||||||
| 			Expect(e2e.K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) |  | ||||||
| 			e2e.WaitForJenkinsBaseConfigurationToComplete(jenkins) |  | ||||||
| 			e2e.WaitForJenkinsUserConfigurationToComplete(jenkins) |  | ||||||
| 
 |  | ||||||
| 		}) | 		}) | ||||||
| 
 | 	}) | ||||||
| 		It("Deploys operator, creates a jenkins cr and denies update request for another one", func() { | 	Context("When Jenkins CR doesn't contain plugins with security warnings", func() { | ||||||
|  | 		It("Jenkins instance is successfully created", func() { | ||||||
| 			By("Deploying the operator along with webhook and cert-manager") | 			By("Deploying the operator along with webhook and cert-manager") | ||||||
| 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | 			cmd := exec.Command("../../bin/helm", "upgrade", "jenkins", "../../chart/jenkins-operator", "--namespace", namespace.Name, "--debug", | ||||||
| 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), "--set-string", fmt.Sprintf("operator.image=%s", *imageName), | 				"--set-string", fmt.Sprintf("jenkins.namespace=%s", namespace.Name), | ||||||
| 				"--set", fmt.Sprintf("webhook.enabled=%t", true), "--set", fmt.Sprintf("jenkins.enabled=%t", false), "--install", "--wait") | 				"--set-string", fmt.Sprintf("operator.image=%s", *imageName), | ||||||
|  | 				"--set", fmt.Sprintf("webhook.enabled=%t", true), | ||||||
|  | 				"--set", fmt.Sprintf("jenkins.enabled=%t", false), "--install") | ||||||
| 			output, err := cmd.CombinedOutput() | 			output, err := cmd.CombinedOutput() | ||||||
| 			Expect(err).NotTo(HaveOccurred(), string(output)) | 			Expect(err).NotTo(HaveOccurred(), string(output)) | ||||||
| 
 | 
 | ||||||
|  | @ -106,129 +135,12 @@ var _ = Describe("Jenkins Controller with webhook", func() { | ||||||
| 			time.Sleep(time.Duration(200) * time.Second) | 			time.Sleep(time.Duration(200) * time.Second) | ||||||
| 
 | 
 | ||||||
| 			By("Creating a Jenkins custom resource with some plugins having security warnings but validation is turned off") | 			By("Creating a Jenkins custom resource with some plugins having security warnings but validation is turned off") | ||||||
| 			userplugins := []v1alpha2.Plugin{ | 			jenkins := e2e.RenderJenkinsCR(jenkinsCRName, namespace.Name, seedJobs, groovyScripts, casc, "") | ||||||
| 				{Name: "simple-theme-plugin", Version: "0.6"}, | 			jenkins.Spec.Master.Plugins = validPlugins | ||||||
| 				{Name: "audit-trail", Version: "3.5"}, | 			jenkins.Spec.ValidateSecurityWarnings = true | ||||||
| 				{Name: "github", Version: "1.29.0"}, |  | ||||||
| 			} |  | ||||||
| 			jenkins := CreateJenkinsCR("jenkins", namespace.Name, userplugins, false) |  | ||||||
| 			Expect(e2e.K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) | 			Expect(e2e.K8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) | ||||||
| 			e2e.WaitForJenkinsBaseConfigurationToComplete(jenkins) | 			e2e.WaitForJenkinsBaseConfigurationToComplete(jenkins) | ||||||
| 			e2e.WaitForJenkinsUserConfigurationToComplete(jenkins) | 			e2e.WaitForJenkinsUserConfigurationToComplete(jenkins) | ||||||
| 
 |  | ||||||
| 			By("Failing to update the Jenkins custom resource because some plugins have security warnings and validation is turned on") |  | ||||||
| 			userplugins = []v1alpha2.Plugin{ |  | ||||||
| 				{Name: "vncviewer", Version: "1.7"}, |  | ||||||
| 				{Name: "build-timestamp", Version: "1.0.3"}, |  | ||||||
| 				{Name: "deployit-plugin", Version: "7.5.5"}, |  | ||||||
| 				{Name: "github-branch-source", Version: "2.0.7"}, |  | ||||||
| 				{Name: "aws-lambda-cloud", Version: "0.4"}, |  | ||||||
| 				{Name: "groovy", Version: "1.31"}, |  | ||||||
| 				{Name: "google-login", Version: "1.2"}, |  | ||||||
| 			} |  | ||||||
| 			jenkins.Spec.Master.Plugins = userplugins |  | ||||||
| 			jenkins.Spec.ValidateSecurityWarnings = true |  | ||||||
| 			Expect(e2e.K8sClient.Update(context.TODO(), jenkins)).Should(MatchError("admission webhook \"vjenkins.kb.io\" denied the request: security vulnerabilities detected in the following user-defined plugins: \nvncviewer:1.7\ndeployit-plugin:7.5.5\ngithub-branch-source:2.0.7\ngroovy:1.31\ngoogle-login:1.2")) |  | ||||||
| 
 |  | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| 
 |  | ||||||
| }) | }) | ||||||
| 
 |  | ||||||
| func CreateJenkinsCR(name string, namespace string, userPlugins []v1alpha2.Plugin, validateSecurityWarnings bool) *v1alpha2.Jenkins { |  | ||||||
| 	jenkins := &v1alpha2.Jenkins{ |  | ||||||
| 		TypeMeta: v1alpha2.JenkinsTypeMeta(), |  | ||||||
| 		ObjectMeta: metav1.ObjectMeta{ |  | ||||||
| 			Name:      name, |  | ||||||
| 			Namespace: namespace, |  | ||||||
| 		}, |  | ||||||
| 		Spec: v1alpha2.JenkinsSpec{ |  | ||||||
| 			GroovyScripts: v1alpha2.GroovyScripts{ |  | ||||||
| 				Customization: v1alpha2.Customization{ |  | ||||||
| 					Configurations: []v1alpha2.ConfigMapRef{}, |  | ||||||
| 					Secret: v1alpha2.SecretRef{ |  | ||||||
| 						Name: "", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			ConfigurationAsCode: v1alpha2.ConfigurationAsCode{ |  | ||||||
| 				Customization: v1alpha2.Customization{ |  | ||||||
| 					Configurations: []v1alpha2.ConfigMapRef{}, |  | ||||||
| 					Secret: v1alpha2.SecretRef{ |  | ||||||
| 						Name: "", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			Master: v1alpha2.JenkinsMaster{ |  | ||||||
| 				Containers: []v1alpha2.Container{ |  | ||||||
| 					{ |  | ||||||
| 						Name: resources.JenkinsMasterContainerName, |  | ||||||
| 						Env: []corev1.EnvVar{ |  | ||||||
| 							{ |  | ||||||
| 								Name:  "TEST_ENV", |  | ||||||
| 								Value: "test_env_value", |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 						ReadinessProbe: &corev1.Probe{ |  | ||||||
| 							Handler: corev1.Handler{ |  | ||||||
| 								HTTPGet: &corev1.HTTPGetAction{ |  | ||||||
| 									Path:   "/login", |  | ||||||
| 									Port:   intstr.FromString("http"), |  | ||||||
| 									Scheme: corev1.URISchemeHTTP, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 							InitialDelaySeconds: int32(100), |  | ||||||
| 							TimeoutSeconds:      int32(4), |  | ||||||
| 							FailureThreshold:    int32(40), |  | ||||||
| 							SuccessThreshold:    int32(1), |  | ||||||
| 							PeriodSeconds:       int32(10), |  | ||||||
| 						}, |  | ||||||
| 						LivenessProbe: &corev1.Probe{ |  | ||||||
| 							Handler: corev1.Handler{ |  | ||||||
| 								HTTPGet: &corev1.HTTPGetAction{ |  | ||||||
| 									Path:   "/login", |  | ||||||
| 									Port:   intstr.FromString("http"), |  | ||||||
| 									Scheme: corev1.URISchemeHTTP, |  | ||||||
| 								}, |  | ||||||
| 							}, |  | ||||||
| 							InitialDelaySeconds: int32(80), |  | ||||||
| 							TimeoutSeconds:      int32(4), |  | ||||||
| 							FailureThreshold:    int32(30), |  | ||||||
| 							SuccessThreshold:    int32(1), |  | ||||||
| 							PeriodSeconds:       int32(5), |  | ||||||
| 						}, |  | ||||||
| 						VolumeMounts: []corev1.VolumeMount{ |  | ||||||
| 							{ |  | ||||||
| 								Name:      "plugins-cache", |  | ||||||
| 								MountPath: "/usr/share/jenkins/ref/plugins", |  | ||||||
| 							}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 					{ |  | ||||||
| 						Name:  "envoyproxy", |  | ||||||
| 						Image: "envoyproxy/envoy-alpine:v1.14.1", |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 				Plugins:               userPlugins, |  | ||||||
| 				DisableCSRFProtection: false, |  | ||||||
| 				NodeSelector:          map[string]string{"kubernetes.io/os": "linux"}, |  | ||||||
| 				Volumes: []corev1.Volume{ |  | ||||||
| 					{ |  | ||||||
| 						Name: "plugins-cache", |  | ||||||
| 						VolumeSource: corev1.VolumeSource{ |  | ||||||
| 							EmptyDir: &corev1.EmptyDirVolumeSource{}, |  | ||||||
| 						}, |  | ||||||
| 					}, |  | ||||||
| 				}, |  | ||||||
| 			}, |  | ||||||
| 			ValidateSecurityWarnings: validateSecurityWarnings, |  | ||||||
| 			Service: v1alpha2.Service{ |  | ||||||
| 				Type: corev1.ServiceTypeNodePort, |  | ||||||
| 				Port: constants.DefaultHTTPPortInt32, |  | ||||||
| 			}, |  | ||||||
| 			JenkinsAPISettings: v1alpha2.JenkinsAPISettings{AuthorizationStrategy: v1alpha2.CreateUserAuthorizationStrategy}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	return jenkins |  | ||||||
| } |  | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue