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