Merge pull request #118 from jakalkhalili/notification-feature
Improved notification messages
This commit is contained in:
commit
d5177416c2
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis"
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/notifications"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/event"
|
"github.com/jenkinsci/kubernetes-operator/pkg/event"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
||||||
"github.com/jenkinsci/kubernetes-operator/version"
|
"github.com/jenkinsci/kubernetes-operator/version"
|
||||||
|
|
@ -118,8 +119,11 @@ func main() {
|
||||||
fatal(errors.Wrap(err, "failed to create Kubernetes client set"), *debug)
|
fatal(errors.Wrap(err, "failed to create Kubernetes client set"), *debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c := make(chan notifications.Event)
|
||||||
|
go notifications.Listen(c, events, mgr.GetClient())
|
||||||
|
|
||||||
// setup Jenkins controller
|
// setup Jenkins controller
|
||||||
if err := jenkins.Add(mgr, *local, *minikube, events, *clientSet, *cfg); err != nil {
|
if err := jenkins.Add(mgr, *local, *minikube, *clientSet, *cfg, &c); err != nil {
|
||||||
fatal(errors.Wrap(err, "failed to setup controllers"), *debug)
|
fatal(errors.Wrap(err, "failed to setup controllers"), *debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ type JenkinsSpec struct {
|
||||||
// +optional
|
// +optional
|
||||||
SeedJobs []SeedJob `json:"seedJobs,omitempty"`
|
SeedJobs []SeedJob `json:"seedJobs,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 MicrosoftTeams or Mailgun
|
// Can be used to integrate chat services like Slack, Microsoft Teams or Mailgun
|
||||||
Notifications []Notification `json:"notifications,omitempty"`*/
|
Notifications []Notification `json:"notifications,omitempty"`
|
||||||
|
|
||||||
// Service is Kubernetes service of Jenkins master HTTP pod
|
// Service is Kubernetes service of Jenkins master HTTP pod
|
||||||
// Defaults to :
|
// Defaults to :
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,13 @@ func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) {
|
||||||
*out = make([]SeedJob, len(*in))
|
*out = make([]SeedJob, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.Notifications != nil {
|
||||||
|
in, out := &in.Notifications, &out.Notifications
|
||||||
|
*out = make([]Notification, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
in.Service.DeepCopyInto(&out.Service)
|
in.Service.DeepCopyInto(&out.Service)
|
||||||
in.SlaveService.DeepCopyInto(&out.SlaveService)
|
in.SlaveService.DeepCopyInto(&out.SlaveService)
|
||||||
in.Backup.DeepCopyInto(&out.Backup)
|
in.Backup.DeepCopyInto(&out.Backup)
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ func New(k8sClient k8s.Client, clientSet kubernetes.Clientset,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates backup and restore configuration
|
// Validate validates backup and restore configuration
|
||||||
func (bar *BackupAndRestore) Validate() bool {
|
func (bar *BackupAndRestore) Validate() []string {
|
||||||
valid := true
|
var messages []string
|
||||||
allContainers := map[string]v1alpha2.Container{}
|
allContainers := map[string]v1alpha2.Container{}
|
||||||
for _, container := range bar.jenkins.Spec.Master.Containers {
|
for _, container := range bar.jenkins.Spec.Master.Containers {
|
||||||
allContainers[container.Name] = container
|
allContainers[container.Name] = container
|
||||||
|
|
@ -57,12 +57,10 @@ func (bar *BackupAndRestore) Validate() bool {
|
||||||
if len(restore.ContainerName) > 0 {
|
if len(restore.ContainerName) > 0 {
|
||||||
_, found := allContainers[restore.ContainerName]
|
_, found := allContainers[restore.ContainerName]
|
||||||
if !found {
|
if !found {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("restore container '%s' not found in CR spec.master.containers", restore.ContainerName))
|
||||||
bar.logger.V(log.VWarn).Info(fmt.Sprintf("restore container '%s' not found in CR spec.master.containers", restore.ContainerName))
|
|
||||||
}
|
}
|
||||||
if restore.Action.Exec == nil {
|
if restore.Action.Exec == nil {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("spec.restore.action.exec is not configured"))
|
||||||
bar.logger.V(log.VWarn).Info(fmt.Sprintf("spec.restore.action.exec is not configured"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,29 +68,24 @@ func (bar *BackupAndRestore) Validate() bool {
|
||||||
if len(backup.ContainerName) > 0 {
|
if len(backup.ContainerName) > 0 {
|
||||||
_, found := allContainers[backup.ContainerName]
|
_, found := allContainers[backup.ContainerName]
|
||||||
if !found {
|
if !found {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("backup container '%s' not found in CR spec.master.containers", backup.ContainerName))
|
||||||
bar.logger.V(log.VWarn).Info(fmt.Sprintf("backup container '%s' not found in CR spec.master.containers", backup.ContainerName))
|
|
||||||
}
|
}
|
||||||
if backup.Action.Exec == nil {
|
if backup.Action.Exec == nil {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("spec.backup.action.exec is not configured"))
|
||||||
bar.logger.V(log.VWarn).Info(fmt.Sprintf("spec.backup.action.exec is not configured"))
|
|
||||||
}
|
}
|
||||||
if backup.Interval == 0 {
|
if backup.Interval == 0 {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("spec.backup.interval is not configured"))
|
||||||
bar.logger.V(log.VWarn).Info(fmt.Sprintf("spec.backup.interval is not configured"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(restore.ContainerName) > 0 && len(backup.ContainerName) == 0 {
|
if len(restore.ContainerName) > 0 && len(backup.ContainerName) == 0 {
|
||||||
valid = false
|
messages = append(messages, "spec.backup.containerName is not configured")
|
||||||
bar.logger.V(log.VWarn).Info("spec.backup.containerName is not configured")
|
|
||||||
}
|
}
|
||||||
if len(backup.ContainerName) > 0 && len(restore.ContainerName) == 0 {
|
if len(backup.ContainerName) > 0 && len(restore.ContainerName) == 0 {
|
||||||
valid = false
|
messages = append(messages, "spec.restore.containerName is not configured")
|
||||||
bar.logger.V(log.VWarn).Info("spec.restore.containerName is not configured")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore performs Jenkins restore backup operation
|
// Restore performs Jenkins restore backup operation
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (reconcile.Result, jenki
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err = r.ensureBaseConfiguration(jenkinsClient)
|
result, err = r.ensureBaseConfiguration(jenkinsClient)
|
||||||
|
|
||||||
return result, jenkinsClient, err
|
return result, jenkinsClient, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,11 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
docker "github.com/docker/distribution/reference"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
|
||||||
|
|
||||||
docker "github.com/docker/distribution/reference"
|
|
||||||
stackerr "github.com/pkg/errors"
|
stackerr "github.com/pkg/errors"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
|
@ -24,213 +22,207 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate validates Jenkins CR Spec.master section
|
// Validate validates Jenkins CR Spec.master section
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha2.Jenkins) (bool, error) {
|
func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha2.Jenkins) ([]string, error) {
|
||||||
if !r.validateReservedVolumes() {
|
var messages []string
|
||||||
return false, nil
|
|
||||||
|
if msg := r.validateReservedVolumes(); len(msg) > 0 {
|
||||||
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
if valid, err := r.validateVolumes(); err != nil {
|
|
||||||
return false, err
|
if msg, err := r.validateVolumes(); err != nil {
|
||||||
} else if !valid {
|
return nil, err
|
||||||
return false, nil
|
} else if len(msg) > 0 {
|
||||||
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, container := range jenkins.Spec.Master.Containers {
|
for _, container := range jenkins.Spec.Master.Containers {
|
||||||
if !r.validateContainer(container) {
|
if msg := r.validateContainer(container); len(msg) > 0 {
|
||||||
return false, nil
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.validatePlugins(plugins.BasePlugins(), jenkins.Spec.Master.BasePlugins, jenkins.Spec.Master.Plugins) {
|
if msg := r.validatePlugins(plugins.BasePlugins(), jenkins.Spec.Master.BasePlugins, jenkins.Spec.Master.Plugins); len(msg) > 0 {
|
||||||
return false, nil
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.validateJenkinsMasterPodEnvs() {
|
if msg := r.validateJenkinsMasterPodEnvs(); len(msg) > 0 {
|
||||||
return false, nil
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid, err := r.validateCustomization(r.jenkins.Spec.GroovyScripts.Customization, "spec.groovyScripts"); err != nil {
|
if msg, err := r.validateCustomization(r.jenkins.Spec.GroovyScripts.Customization, "spec.groovyScripts"); err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
} else if !valid {
|
} else if len(msg) > 0 {
|
||||||
return false, nil
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
if valid, err := r.validateCustomization(r.jenkins.Spec.ConfigurationAsCode.Customization, "spec.configurationAsCode"); err != nil {
|
if msg, err := r.validateCustomization(r.jenkins.Spec.ConfigurationAsCode.Customization, "spec.configurationAsCode"); err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
} else if !valid {
|
} else if len(msg) > 0 {
|
||||||
return false, nil
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecrets() (bool, error) {
|
func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecrets() ([]string, error) {
|
||||||
|
var messages []string
|
||||||
for _, sr := range r.jenkins.Spec.Master.ImagePullSecrets {
|
for _, sr := range r.jenkins.Spec.Master.ImagePullSecrets {
|
||||||
valid, err := r.validateImagePullSecret(sr.Name)
|
msg, err := r.validateImagePullSecret(sr.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !valid {
|
if len(msg) > 0 {
|
||||||
return false, nil
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecret(secretName string) (bool, error) {
|
func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecret(secretName string) ([]string, error) {
|
||||||
|
var messages []string
|
||||||
secret := &corev1.Secret{}
|
secret := &corev1.Secret{}
|
||||||
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: r.jenkins.ObjectMeta.Namespace}, secret)
|
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: r.jenkins.ObjectMeta.Namespace}, secret)
|
||||||
if err != nil && apierrors.IsNotFound(err) {
|
if err != nil && apierrors.IsNotFound(err) {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret %s not found defined in spec.master.imagePullSecrets", secretName))
|
messages = append(messages, fmt.Sprintf("Secret %s not found defined in spec.master.imagePullSecrets", secretName))
|
||||||
return false, nil
|
|
||||||
} else if err != nil && !apierrors.IsNotFound(err) {
|
} else if err != nil && !apierrors.IsNotFound(err) {
|
||||||
return false, stackerr.WithStack(err)
|
return nil, stackerr.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if secret.Data["docker-server"] == nil {
|
if secret.Data["docker-server"] == nil {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-server' key.", secretName))
|
messages = append(messages, fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-server' key.", secretName))
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
if secret.Data["docker-username"] == nil {
|
if secret.Data["docker-username"] == nil {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-username' key.", secretName))
|
messages = append(messages, fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-username' key.", secretName))
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
if secret.Data["docker-password"] == nil {
|
if secret.Data["docker-password"] == nil {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-password' key.", secretName))
|
messages = append(messages, fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-password' key.", secretName))
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
if secret.Data["docker-email"] == nil {
|
if secret.Data["docker-email"] == nil {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-email' key.", secretName))
|
messages = append(messages, fmt.Sprintf("Secret '%s' defined in spec.master.imagePullSecrets doesn't have 'docker-email' key.", secretName))
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateVolumes() (bool, error) {
|
func (r *ReconcileJenkinsBaseConfiguration) validateVolumes() ([]string, error) {
|
||||||
valid := true
|
var messages []string
|
||||||
for _, volume := range r.jenkins.Spec.Master.Volumes {
|
for _, volume := range r.jenkins.Spec.Master.Volumes {
|
||||||
switch {
|
switch {
|
||||||
case volume.ConfigMap != nil:
|
case volume.ConfigMap != nil:
|
||||||
if ok, err := r.validateConfigMapVolume(volume); err != nil {
|
if msg, err := r.validateConfigMapVolume(volume); err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
} else if !ok {
|
} else if len(msg) > 0 {
|
||||||
valid = false
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
case volume.Secret != nil:
|
case volume.Secret != nil:
|
||||||
if ok, err := r.validateSecretVolume(volume); err != nil {
|
if msg, err := r.validateSecretVolume(volume); err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
} else if !ok {
|
} else if len(msg) > 0 {
|
||||||
valid = false
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
case volume.PersistentVolumeClaim != nil:
|
case volume.PersistentVolumeClaim != nil:
|
||||||
if ok, err := r.validatePersistentVolumeClaim(volume); err != nil {
|
if msg, err := r.validatePersistentVolumeClaim(volume); err != nil {
|
||||||
return false, err
|
return nil, err
|
||||||
} else if !ok {
|
} else if len(msg) > 0 {
|
||||||
valid = false
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
default: //TODO add support for rest of volumes
|
default: //TODO add support for rest of volumes
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("Unsupported volume '%v'", volume))
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Unsupported volume '%v'", volume))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validatePersistentVolumeClaim(volume corev1.Volume) (bool, error) {
|
func (r *ReconcileJenkinsBaseConfiguration) validatePersistentVolumeClaim(volume corev1.Volume) ([]string, error) {
|
||||||
|
var messages []string
|
||||||
|
|
||||||
pvc := &corev1.PersistentVolumeClaim{}
|
pvc := &corev1.PersistentVolumeClaim{}
|
||||||
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.PersistentVolumeClaim.ClaimName, Namespace: r.jenkins.ObjectMeta.Namespace}, pvc)
|
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.PersistentVolumeClaim.ClaimName, Namespace: r.jenkins.ObjectMeta.Namespace}, pvc)
|
||||||
if err != nil && apierrors.IsNotFound(err) {
|
if err != nil && apierrors.IsNotFound(err) {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("PersistentVolumeClaim '%s' not found for volume '%v'", volume.PersistentVolumeClaim.ClaimName, volume))
|
messages = append(messages, fmt.Sprintf("PersistentVolumeClaim '%s' not found for volume '%v'", volume.PersistentVolumeClaim.ClaimName, volume))
|
||||||
return false, nil
|
|
||||||
} else if err != nil && !apierrors.IsNotFound(err) {
|
} else if err != nil && !apierrors.IsNotFound(err) {
|
||||||
return false, stackerr.WithStack(err)
|
return nil, stackerr.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateConfigMapVolume(volume corev1.Volume) (bool, error) {
|
func (r *ReconcileJenkinsBaseConfiguration) validateConfigMapVolume(volume corev1.Volume) ([]string, error) {
|
||||||
|
var messages []string
|
||||||
if volume.ConfigMap.Optional != nil && *volume.ConfigMap.Optional {
|
if volume.ConfigMap.Optional != nil && *volume.ConfigMap.Optional {
|
||||||
return true, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
configMap := &corev1.ConfigMap{}
|
configMap := &corev1.ConfigMap{}
|
||||||
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.ConfigMap.Name, Namespace: r.jenkins.ObjectMeta.Namespace}, configMap)
|
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.ConfigMap.Name, Namespace: r.jenkins.ObjectMeta.Namespace}, configMap)
|
||||||
if err != nil && apierrors.IsNotFound(err) {
|
if err != nil && apierrors.IsNotFound(err) {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("ConfigMap '%s' not found for volume '%v'", volume.ConfigMap.Name, volume))
|
messages = append(messages, fmt.Sprintf("ConfigMap '%s' not found for volume '%v'", volume.ConfigMap.Name, volume))
|
||||||
return false, nil
|
|
||||||
} else if err != nil && !apierrors.IsNotFound(err) {
|
} else if err != nil && !apierrors.IsNotFound(err) {
|
||||||
return false, stackerr.WithStack(err)
|
return nil, stackerr.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateSecretVolume(volume corev1.Volume) (bool, error) {
|
func (r *ReconcileJenkinsBaseConfiguration) validateSecretVolume(volume corev1.Volume) ([]string, error) {
|
||||||
|
var messages []string
|
||||||
if volume.Secret.Optional != nil && *volume.Secret.Optional {
|
if volume.Secret.Optional != nil && *volume.Secret.Optional {
|
||||||
return true, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
secret := &corev1.Secret{}
|
secret := &corev1.Secret{}
|
||||||
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.Secret.SecretName, Namespace: r.jenkins.ObjectMeta.Namespace}, secret)
|
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: volume.Secret.SecretName, Namespace: r.jenkins.ObjectMeta.Namespace}, secret)
|
||||||
if err != nil && apierrors.IsNotFound(err) {
|
if err != nil && apierrors.IsNotFound(err) {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' not found for volume '%v'", volume.Secret.SecretName, volume))
|
messages = append(messages, fmt.Sprintf("Secret '%s' not found for volume '%v'", volume.Secret.SecretName, volume))
|
||||||
return false, nil
|
|
||||||
} else if err != nil && !apierrors.IsNotFound(err) {
|
} else if err != nil && !apierrors.IsNotFound(err) {
|
||||||
return false, stackerr.WithStack(err)
|
return nil, stackerr.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateReservedVolumes() bool {
|
func (r *ReconcileJenkinsBaseConfiguration) validateReservedVolumes() []string {
|
||||||
valid := true
|
var messages []string
|
||||||
|
|
||||||
for _, baseVolume := range resources.GetJenkinsMasterPodBaseVolumes(r.jenkins) {
|
for _, baseVolume := range resources.GetJenkinsMasterPodBaseVolumes(r.jenkins) {
|
||||||
for _, volume := range r.jenkins.Spec.Master.Volumes {
|
for _, volume := range r.jenkins.Spec.Master.Volumes {
|
||||||
if baseVolume.Name == volume.Name {
|
if baseVolume.Name == volume.Name {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Jenkins Master pod volume '%s' is reserved please choose different one", volume.Name))
|
messages = append(messages, fmt.Sprintf("Jenkins Master pod volume '%s' is reserved please choose different one", volume.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha2.Container) bool {
|
func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha2.Container) []string {
|
||||||
logger := r.logger.WithValues("container", container.Name)
|
var messages []string
|
||||||
if container.Image == "" {
|
if container.Image == "" {
|
||||||
logger.V(log.VWarn).Info("Image not set")
|
messages = append(messages, "Image not set")
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dockerImageRegexp.MatchString(container.Image) && !docker.ReferenceRegexp.MatchString(container.Image) {
|
if !dockerImageRegexp.MatchString(container.Image) && !docker.ReferenceRegexp.MatchString(container.Image) {
|
||||||
logger.V(log.VWarn).Info("Invalid image")
|
messages = append(messages, "Invalid image")
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.ImagePullPolicy == "" {
|
if container.ImagePullPolicy == "" {
|
||||||
logger.V(log.VWarn).Info("Image pull policy not set")
|
messages = append(messages, "Image pull policy not set")
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.validateContainerVolumeMounts(container) {
|
if msg := r.validateContainerVolumeMounts(container); len(msg) > 0 {
|
||||||
return false
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateContainerVolumeMounts(container v1alpha2.Container) bool {
|
func (r *ReconcileJenkinsBaseConfiguration) validateContainerVolumeMounts(container v1alpha2.Container) []string {
|
||||||
logger := r.logger.WithValues("container", container.Name)
|
var messages []string
|
||||||
allVolumes := append(resources.GetJenkinsMasterPodBaseVolumes(r.jenkins), r.jenkins.Spec.Master.Volumes...)
|
allVolumes := append(resources.GetJenkinsMasterPodBaseVolumes(r.jenkins), r.jenkins.Spec.Master.Volumes...)
|
||||||
valid := true
|
|
||||||
|
|
||||||
for _, volumeMount := range container.VolumeMounts {
|
for _, volumeMount := range container.VolumeMounts {
|
||||||
if len(volumeMount.MountPath) == 0 {
|
if len(volumeMount.MountPath) == 0 {
|
||||||
logger.V(log.VWarn).Info(fmt.Sprintf("mountPath not set for '%s' volume mount in container '%s'", volumeMount.Name, container.Name))
|
messages = append(messages, fmt.Sprintf("mountPath not set for '%s' volume mount in container '%s'", volumeMount.Name, container.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foundVolume := false
|
foundVolume := false
|
||||||
|
|
@ -241,15 +233,15 @@ func (r *ReconcileJenkinsBaseConfiguration) validateContainerVolumeMounts(contai
|
||||||
}
|
}
|
||||||
|
|
||||||
if !foundVolume {
|
if !foundVolume {
|
||||||
logger.V(log.VWarn).Info(fmt.Sprintf("Not found volume for '%s' volume mount in container '%s'", volumeMount.Name, container.Name))
|
messages = append(messages, fmt.Sprintf("Not found volume for '%s' volume mount in container '%s'", volumeMount.Name, container.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool {
|
func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() []string {
|
||||||
|
var messages []string
|
||||||
baseEnvs := resources.GetJenkinsMasterContainerBaseEnvs(r.jenkins)
|
baseEnvs := resources.GetJenkinsMasterContainerBaseEnvs(r.jenkins)
|
||||||
baseEnvNames := map[string]string{}
|
baseEnvNames := map[string]string{}
|
||||||
for _, env := range baseEnvs {
|
for _, env := range baseEnvs {
|
||||||
|
|
@ -257,14 +249,12 @@ func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
javaOpts := corev1.EnvVar{}
|
javaOpts := corev1.EnvVar{}
|
||||||
valid := true
|
|
||||||
for _, userEnv := range r.jenkins.Spec.Master.Containers[0].Env {
|
for _, userEnv := range r.jenkins.Spec.Master.Containers[0].Env {
|
||||||
if userEnv.Name == constants.JavaOpsVariableName {
|
if userEnv.Name == constants.JavaOpsVariableName {
|
||||||
javaOpts = userEnv
|
javaOpts = userEnv
|
||||||
}
|
}
|
||||||
if _, overriding := baseEnvNames[userEnv.Name]; overriding {
|
if _, overriding := baseEnvNames[userEnv.Name]; overriding {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Jenkins Master container env '%s' cannot be overridden", userEnv.Name))
|
messages = append(messages, fmt.Sprintf("Jenkins Master container env '%s' cannot be overridden", userEnv.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,23 +272,21 @@ func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool
|
||||||
}
|
}
|
||||||
for requiredFlag, set := range requiredFlags {
|
for requiredFlag, set := range requiredFlags {
|
||||||
if !set {
|
if !set {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("Jenkins Master container env '%s' doesn't have required flag '%s'", constants.JavaOpsVariableName, requiredFlag))
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Jenkins Master container env '%s' doesn't have required flag '%s'", constants.JavaOpsVariableName, requiredFlag))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(requiredBasePlugins []plugins.Plugin, basePlugins, userPlugins []v1alpha2.Plugin) bool {
|
func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(requiredBasePlugins []plugins.Plugin, basePlugins, userPlugins []v1alpha2.Plugin) []string {
|
||||||
valid := true
|
var messages []string
|
||||||
allPlugins := map[plugins.Plugin][]plugins.Plugin{}
|
allPlugins := map[plugins.Plugin][]plugins.Plugin{}
|
||||||
|
|
||||||
for _, jenkinsPlugin := range basePlugins {
|
for _, jenkinsPlugin := range basePlugins {
|
||||||
plugin, err := plugins.NewPlugin(jenkinsPlugin.Name, jenkinsPlugin.Version)
|
plugin, err := plugins.NewPlugin(jenkinsPlugin.Name, jenkinsPlugin.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.V(log.VWarn).Info(err.Error())
|
messages = append(messages, err.Error())
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if plugin != nil {
|
if plugin != nil {
|
||||||
|
|
@ -309,8 +297,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(requiredBasePlugins
|
||||||
for _, jenkinsPlugin := range userPlugins {
|
for _, jenkinsPlugin := range userPlugins {
|
||||||
plugin, err := plugins.NewPlugin(jenkinsPlugin.Name, jenkinsPlugin.Version)
|
plugin, err := plugins.NewPlugin(jenkinsPlugin.Name, jenkinsPlugin.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.V(log.VWarn).Info(err.Error())
|
messages = append(messages, err.Error())
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if plugin != nil {
|
if plugin != nil {
|
||||||
|
|
@ -318,19 +305,19 @@ func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(requiredBasePlugins
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !plugins.VerifyDependencies(allPlugins) {
|
if msg := plugins.VerifyDependencies(allPlugins); len(msg) > 0 {
|
||||||
valid = false
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.verifyBasePlugins(requiredBasePlugins, basePlugins) {
|
if msg := r.verifyBasePlugins(requiredBasePlugins, basePlugins); len(msg) > 0 {
|
||||||
valid = false
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) verifyBasePlugins(requiredBasePlugins []plugins.Plugin, basePlugins []v1alpha2.Plugin) bool {
|
func (r *ReconcileJenkinsBaseConfiguration) verifyBasePlugins(requiredBasePlugins []plugins.Plugin, basePlugins []v1alpha2.Plugin) []string {
|
||||||
valid := true
|
var messages []string
|
||||||
|
|
||||||
for _, requiredBasePlugin := range requiredBasePlugins {
|
for _, requiredBasePlugin := range requiredBasePlugins {
|
||||||
found := false
|
found := false
|
||||||
|
|
@ -341,52 +328,46 @@ func (r *ReconcileJenkinsBaseConfiguration) verifyBasePlugins(requiredBasePlugin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("Missing plugin '%s' in spec.master.basePlugins", requiredBasePlugin.Name))
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Missing plugin '%s' in spec.master.basePlugins", requiredBasePlugin.Name))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkinsBaseConfiguration) validateCustomization(customization v1alpha2.Customization, name string) (bool, error) {
|
func (r *ReconcileJenkinsBaseConfiguration) validateCustomization(customization v1alpha2.Customization, name string) ([]string, error) {
|
||||||
valid := true
|
var messages []string
|
||||||
if len(customization.Secret.Name) == 0 && len(customization.Configurations) == 0 {
|
if len(customization.Secret.Name) == 0 && len(customization.Configurations) == 0 {
|
||||||
return true, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if len(customization.Secret.Name) > 0 && len(customization.Configurations) == 0 {
|
if len(customization.Secret.Name) > 0 && len(customization.Configurations) == 0 {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("%s.secret.name is set but %s.configurations is empty", name, name))
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("%s.secret.name is set but %s.configurations is empty", name, name))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(customization.Secret.Name) > 0 {
|
if len(customization.Secret.Name) > 0 {
|
||||||
secret := &corev1.Secret{}
|
secret := &corev1.Secret{}
|
||||||
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: customization.Secret.Name, Namespace: r.jenkins.ObjectMeta.Namespace}, secret)
|
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: customization.Secret.Name, Namespace: r.jenkins.ObjectMeta.Namespace}, secret)
|
||||||
if err != nil && apierrors.IsNotFound(err) {
|
if err != nil && apierrors.IsNotFound(err) {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("Secret '%s' configured in %s.secret.name not found", customization.Secret.Name, name))
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("Secret '%s' configured in %s.secret.name not found", customization.Secret.Name, name))
|
|
||||||
} else if err != nil && !apierrors.IsNotFound(err) {
|
} else if err != nil && !apierrors.IsNotFound(err) {
|
||||||
return false, stackerr.WithStack(err)
|
return nil, stackerr.WithStack(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for index, configMapRef := range customization.Configurations {
|
for index, configMapRef := range customization.Configurations {
|
||||||
if len(configMapRef.Name) == 0 {
|
if len(configMapRef.Name) == 0 {
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("%s.configurations[%d] name is empty", name, index))
|
messages = append(messages, fmt.Sprintf("%s.configurations[%d] name is empty", name, index))
|
||||||
valid = false
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
configMap := &corev1.ConfigMap{}
|
configMap := &corev1.ConfigMap{}
|
||||||
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: configMapRef.Name, Namespace: r.jenkins.ObjectMeta.Namespace}, configMap)
|
err := r.k8sClient.Get(context.TODO(), types.NamespacedName{Name: configMapRef.Name, Namespace: r.jenkins.ObjectMeta.Namespace}, configMap)
|
||||||
if err != nil && apierrors.IsNotFound(err) {
|
if err != nil && apierrors.IsNotFound(err) {
|
||||||
valid = false
|
messages = append(messages, fmt.Sprintf("ConfigMap '%s' configured in %s.configurations[%d] not found", configMapRef.Name, name, index))
|
||||||
r.logger.V(log.VWarn).Info(fmt.Sprintf("ConfigMap '%s' configured in %s.configurations[%d] not found", configMapRef.Name, name, index))
|
|
||||||
return false, nil
|
|
||||||
} else if err != nil && !apierrors.IsNotFound(err) {
|
} else if err != nil && !apierrors.IsNotFound(err) {
|
||||||
return false, stackerr.WithStack(err)
|
return nil, stackerr.WithStack(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("valid user plugin", func(t *testing.T) {
|
t.Run("valid user plugin", func(t *testing.T) {
|
||||||
var requiredBasePlugins []plugins.Plugin
|
var requiredBasePlugins []plugins.Plugin
|
||||||
|
|
@ -39,7 +39,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("invalid user plugin name", func(t *testing.T) {
|
t.Run("invalid user plugin name", func(t *testing.T) {
|
||||||
var requiredBasePlugins []plugins.Plugin
|
var requiredBasePlugins []plugins.Plugin
|
||||||
|
|
@ -48,7 +48,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.False(t, got)
|
assert.Equal(t, got, []string{"invalid plugin name 'INVALID?:0.0.1', must follow pattern '(?i)^[0-9a-z-_]+$'"})
|
||||||
})
|
})
|
||||||
t.Run("invalid user plugin version", func(t *testing.T) {
|
t.Run("invalid user plugin version", func(t *testing.T) {
|
||||||
var requiredBasePlugins []plugins.Plugin
|
var requiredBasePlugins []plugins.Plugin
|
||||||
|
|
@ -57,7 +57,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.False(t, got)
|
assert.Equal(t, got, []string{"invalid plugin version 'simple-plugin:invalid', must follow pattern '^[0-9\\\\.]+$'"})
|
||||||
})
|
})
|
||||||
t.Run("valid base plugin", func(t *testing.T) {
|
t.Run("valid base plugin", func(t *testing.T) {
|
||||||
var requiredBasePlugins []plugins.Plugin
|
var requiredBasePlugins []plugins.Plugin
|
||||||
|
|
@ -66,7 +66,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("invalid base plugin name", func(t *testing.T) {
|
t.Run("invalid base plugin name", func(t *testing.T) {
|
||||||
var requiredBasePlugins []plugins.Plugin
|
var requiredBasePlugins []plugins.Plugin
|
||||||
|
|
@ -75,7 +75,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.False(t, got)
|
assert.Equal(t, got, []string{"invalid plugin name 'INVALID?:0.0.1', must follow pattern '(?i)^[0-9a-z-_]+$'"})
|
||||||
})
|
})
|
||||||
t.Run("invalid base plugin version", func(t *testing.T) {
|
t.Run("invalid base plugin version", func(t *testing.T) {
|
||||||
var requiredBasePlugins []plugins.Plugin
|
var requiredBasePlugins []plugins.Plugin
|
||||||
|
|
@ -84,7 +84,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.False(t, got)
|
assert.Equal(t, got, []string{"invalid plugin version 'simple-plugin:invalid', must follow pattern '^[0-9\\\\.]+$'"})
|
||||||
})
|
})
|
||||||
t.Run("valid user and base plugin version", func(t *testing.T) {
|
t.Run("valid user and base plugin version", func(t *testing.T) {
|
||||||
var requiredBasePlugins []plugins.Plugin
|
var requiredBasePlugins []plugins.Plugin
|
||||||
|
|
@ -93,7 +93,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("invalid user and base plugin version", func(t *testing.T) {
|
t.Run("invalid user and base plugin version", func(t *testing.T) {
|
||||||
var requiredBasePlugins []plugins.Plugin
|
var requiredBasePlugins []plugins.Plugin
|
||||||
|
|
@ -102,7 +102,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.False(t, got)
|
assert.NotNil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("required base plugin set with the same version", func(t *testing.T) {
|
t.Run("required base plugin set with the same version", func(t *testing.T) {
|
||||||
requiredBasePlugins := []plugins.Plugin{{Name: "simple-plugin", Version: "0.0.1"}}
|
requiredBasePlugins := []plugins.Plugin{{Name: "simple-plugin", Version: "0.0.1"}}
|
||||||
|
|
@ -111,7 +111,7 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("required base plugin set with different version", func(t *testing.T) {
|
t.Run("required base plugin set with different version", func(t *testing.T) {
|
||||||
requiredBasePlugins := []plugins.Plugin{{Name: "simple-plugin", Version: "0.0.1"}}
|
requiredBasePlugins := []plugins.Plugin{{Name: "simple-plugin", Version: "0.0.1"}}
|
||||||
|
|
@ -120,16 +120,16 @@ func TestValidatePlugins(t *testing.T) {
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("missign required base plugin", func(t *testing.T) {
|
t.Run("missing required base plugin", func(t *testing.T) {
|
||||||
requiredBasePlugins := []plugins.Plugin{{Name: "simple-plugin", Version: "0.0.1"}}
|
requiredBasePlugins := []plugins.Plugin{{Name: "simple-plugin", Version: "0.0.1"}}
|
||||||
var basePlugins []v1alpha2.Plugin
|
var basePlugins []v1alpha2.Plugin
|
||||||
var userPlugins []v1alpha2.Plugin
|
var userPlugins []v1alpha2.Plugin
|
||||||
|
|
||||||
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
got := baseReconcileLoop.validatePlugins(requiredBasePlugins, basePlugins, userPlugins)
|
||||||
|
|
||||||
assert.False(t, got)
|
assert.Equal(t, got, []string{"Missing plugin 'simple-plugin' in spec.master.basePlugins"})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,7 +165,7 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
|
|
||||||
got, err := baseReconcileLoop.validateImagePullSecrets()
|
got, err := baseReconcileLoop.validateImagePullSecrets()
|
||||||
assert.Equal(t, got, true)
|
assert.Nil(t, got)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -186,7 +186,8 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
|
|
||||||
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
||||||
assert.Equal(t, got, false)
|
|
||||||
|
assert.Equal(t, got, []string{"Secret test-ref not found defined in spec.master.imagePullSecrets", "Secret 'test-ref' defined in spec.master.imagePullSecrets doesn't have 'docker-server' key.", "Secret 'test-ref' defined in spec.master.imagePullSecrets doesn't have 'docker-username' key.", "Secret 'test-ref' defined in spec.master.imagePullSecrets doesn't have 'docker-password' key.", "Secret 'test-ref' defined in spec.master.imagePullSecrets doesn't have 'docker-email' key."})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no docker email", func(t *testing.T) {
|
t.Run("no docker email", func(t *testing.T) {
|
||||||
|
|
@ -219,7 +220,8 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
|
|
||||||
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
||||||
assert.Equal(t, got, false)
|
|
||||||
|
assert.Equal(t, got, []string{"Secret 'test-ref' defined in spec.master.imagePullSecrets doesn't have 'docker-email' key."})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no docker password", func(t *testing.T) {
|
t.Run("no docker password", func(t *testing.T) {
|
||||||
|
|
@ -252,7 +254,8 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
|
|
||||||
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
||||||
assert.Equal(t, got, false)
|
|
||||||
|
assert.Equal(t, got, []string{"Secret 'test-ref' defined in spec.master.imagePullSecrets doesn't have 'docker-password' key."})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no docker username", func(t *testing.T) {
|
t.Run("no docker username", func(t *testing.T) {
|
||||||
|
|
@ -285,7 +288,8 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
|
|
||||||
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
||||||
assert.Equal(t, got, false)
|
|
||||||
|
assert.Equal(t, got, []string{"Secret 'test-ref' defined in spec.master.imagePullSecrets doesn't have 'docker-username' key."})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("no docker server", func(t *testing.T) {
|
t.Run("no docker server", func(t *testing.T) {
|
||||||
|
|
@ -318,7 +322,8 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
|
|
||||||
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
got, _ := baseReconcileLoop.validateImagePullSecrets()
|
||||||
assert.Equal(t, got, false)
|
|
||||||
|
assert.Equal(t, got, []string{"Secret 'test-ref' defined in spec.master.imagePullSecrets doesn't have 'docker-server' key."})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,7 +353,7 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateJenkinsMasterPodEnvs()
|
got := baseReconcileLoop.validateJenkinsMasterPodEnvs()
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("override JENKINS_HOME env", func(t *testing.T) {
|
t.Run("override JENKINS_HOME env", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -374,7 +379,8 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateJenkinsMasterPodEnvs()
|
got := baseReconcileLoop.validateJenkinsMasterPodEnvs()
|
||||||
assert.Equal(t, false, got)
|
|
||||||
|
assert.Equal(t, got, []string{"Jenkins Master container env 'JENKINS_HOME' cannot be overridden"})
|
||||||
})
|
})
|
||||||
t.Run("missing -Djava.awt.headless=true in JAVA_OPTS env", func(t *testing.T) {
|
t.Run("missing -Djava.awt.headless=true in JAVA_OPTS env", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -396,7 +402,8 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateJenkinsMasterPodEnvs()
|
got := baseReconcileLoop.validateJenkinsMasterPodEnvs()
|
||||||
assert.Equal(t, false, got)
|
|
||||||
|
assert.Equal(t, got, []string{"Jenkins Master container env 'JAVA_OPTS' doesn't have required flag '-Djava.awt.headless=true'"})
|
||||||
})
|
})
|
||||||
t.Run("missing -Djenkins.install.runSetupWizard=false in JAVA_OPTS env", func(t *testing.T) {
|
t.Run("missing -Djenkins.install.runSetupWizard=false in JAVA_OPTS env", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -418,7 +425,8 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateJenkinsMasterPodEnvs()
|
got := baseReconcileLoop.validateJenkinsMasterPodEnvs()
|
||||||
assert.Equal(t, false, got)
|
|
||||||
|
assert.Equal(t, got, []string{"Jenkins Master container env 'JAVA_OPTS' doesn't have required flag '-Djenkins.install.runSetupWizard=false'"})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -438,7 +446,7 @@ func TestValidateReservedVolumes(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateReservedVolumes()
|
got := baseReconcileLoop.validateReservedVolumes()
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("used reserved name", func(t *testing.T) {
|
t.Run("used reserved name", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -455,7 +463,8 @@ func TestValidateReservedVolumes(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateReservedVolumes()
|
got := baseReconcileLoop.validateReservedVolumes()
|
||||||
assert.Equal(t, false, got)
|
|
||||||
|
assert.Equal(t, got, []string{"Jenkins Master pod volume 'jenkins-home' is reserved please choose different one"})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -469,7 +478,7 @@ func TestValidateContainerVolumeMounts(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateContainerVolumeMounts(v1alpha2.Container{})
|
got := baseReconcileLoop.validateContainerVolumeMounts(v1alpha2.Container{})
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("one extra volume", func(t *testing.T) {
|
t.Run("one extra volume", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -496,7 +505,7 @@ func TestValidateContainerVolumeMounts(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Containers[0])
|
got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Containers[0])
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("empty mountPath", func(t *testing.T) {
|
t.Run("empty mountPath", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -523,7 +532,7 @@ func TestValidateContainerVolumeMounts(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Containers[0])
|
got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Containers[0])
|
||||||
assert.Equal(t, false, got)
|
assert.NotNil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("missing volume", func(t *testing.T) {
|
t.Run("missing volume", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -545,7 +554,8 @@ func TestValidateContainerVolumeMounts(t *testing.T) {
|
||||||
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
baseReconcileLoop := New(nil, nil, logf.ZapLogger(false),
|
||||||
&jenkins, false, false, nil, nil)
|
&jenkins, false, false, nil, nil)
|
||||||
got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Containers[0])
|
got := baseReconcileLoop.validateContainerVolumeMounts(jenkins.Spec.Master.Containers[0])
|
||||||
assert.Equal(t, false, got)
|
|
||||||
|
assert.Equal(t, got, []string{"Not found volume for 'missing-volume' volume mount in container ''"})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -568,7 +578,7 @@ func TestValidateConfigMapVolume(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateConfigMapVolume(volume)
|
got, err := baseReconcileLoop.validateConfigMapVolume(volume)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("happy, required", func(t *testing.T) {
|
t.Run("happy, required", func(t *testing.T) {
|
||||||
optional := false
|
optional := false
|
||||||
|
|
@ -594,7 +604,7 @@ func TestValidateConfigMapVolume(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateConfigMapVolume(volume)
|
got, err := baseReconcileLoop.validateConfigMapVolume(volume)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("missing configmap", func(t *testing.T) {
|
t.Run("missing configmap", func(t *testing.T) {
|
||||||
optional := false
|
optional := false
|
||||||
|
|
@ -618,7 +628,8 @@ func TestValidateConfigMapVolume(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateConfigMapVolume(volume)
|
got, err := baseReconcileLoop.validateConfigMapVolume(volume)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, got)
|
|
||||||
|
assert.Equal(t, got, []string{"ConfigMap 'configmap-name' not found for volume '{volume-name {nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil &ConfigMapVolumeSource{LocalObjectReference:LocalObjectReference{Name:configmap-name,},Items:[],DefaultMode:nil,Optional:*false,} nil nil nil nil nil nil nil nil}}'"})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -641,7 +652,7 @@ func TestValidateSecretVolume(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateSecretVolume(volume)
|
got, err := baseReconcileLoop.validateSecretVolume(volume)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("happy, required", func(t *testing.T) {
|
t.Run("happy, required", func(t *testing.T) {
|
||||||
optional := false
|
optional := false
|
||||||
|
|
@ -665,7 +676,7 @@ func TestValidateSecretVolume(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateSecretVolume(volume)
|
got, err := baseReconcileLoop.validateSecretVolume(volume)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("missing secret", func(t *testing.T) {
|
t.Run("missing secret", func(t *testing.T) {
|
||||||
optional := false
|
optional := false
|
||||||
|
|
@ -687,7 +698,8 @@ func TestValidateSecretVolume(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateSecretVolume(volume)
|
got, err := baseReconcileLoop.validateSecretVolume(volume)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, got)
|
|
||||||
|
assert.Equal(t, got, []string{"Secret 'secret-name' not found for volume '{volume-name {nil nil nil nil nil &SecretVolumeSource{SecretName:secret-name,Items:[],DefaultMode:nil,Optional:*false,} nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil}}'"})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -709,7 +721,7 @@ func TestValidateCustomization(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("secret set but configurations is empty", func(t *testing.T) {
|
t.Run("secret set but configurations is empty", func(t *testing.T) {
|
||||||
customization := v1alpha2.Customization{
|
customization := v1alpha2.Customization{
|
||||||
|
|
@ -731,7 +743,8 @@ func TestValidateCustomization(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, got)
|
|
||||||
|
assert.Equal(t, got, []string{"spec.groovyScripts.secret.name is set but spec.groovyScripts.configurations is empty"})
|
||||||
})
|
})
|
||||||
t.Run("secret and configmap exists", func(t *testing.T) {
|
t.Run("secret and configmap exists", func(t *testing.T) {
|
||||||
customization := v1alpha2.Customization{
|
customization := v1alpha2.Customization{
|
||||||
|
|
@ -761,7 +774,7 @@ func TestValidateCustomization(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("secret not exists and configmap exists", func(t *testing.T) {
|
t.Run("secret not exists and configmap exists", func(t *testing.T) {
|
||||||
configMapName := "configmap-name"
|
configMapName := "configmap-name"
|
||||||
|
|
@ -784,7 +797,8 @@ func TestValidateCustomization(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, got)
|
|
||||||
|
assert.Equal(t, got, []string{"Secret 'secretName' configured in spec.groovyScripts.secret.name not found"})
|
||||||
})
|
})
|
||||||
t.Run("secret exists and configmap not exists", func(t *testing.T) {
|
t.Run("secret exists and configmap not exists", func(t *testing.T) {
|
||||||
customization := v1alpha2.Customization{
|
customization := v1alpha2.Customization{
|
||||||
|
|
@ -806,6 +820,7 @@ func TestValidateCustomization(t *testing.T) {
|
||||||
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
got, err := baseReconcileLoop.validateCustomization(customization, "spec.groovyScripts")
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, got)
|
|
||||||
|
assert.Equal(t, got, []string{"ConfigMap 'configmap-name' configured in spec.groovyScripts.configurations[0] not found"})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
|
||||||
|
|
||||||
"github.com/go-logr/logr"
|
|
||||||
stackerr "github.com/pkg/errors"
|
stackerr "github.com/pkg/errors"
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
|
|
@ -19,51 +16,42 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateSeedJobs verify seed jobs configuration
|
// ValidateSeedJobs verify seed jobs configuration
|
||||||
func (s *SeedJobs) ValidateSeedJobs(jenkins v1alpha2.Jenkins) (bool, error) {
|
func (s *SeedJobs) ValidateSeedJobs(jenkins v1alpha2.Jenkins) ([]string, error) {
|
||||||
valid := true
|
var messages []string
|
||||||
|
|
||||||
if !s.validateIfIDIsUnique(jenkins.Spec.SeedJobs) {
|
if msg := s.validateIfIDIsUnique(jenkins.Spec.SeedJobs); len(msg) > 0 {
|
||||||
valid = false
|
messages = append(messages, msg...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, seedJob := range jenkins.Spec.SeedJobs {
|
for _, seedJob := range jenkins.Spec.SeedJobs {
|
||||||
logger := s.logger.WithValues("seedJob", seedJob.ID).V(log.VWarn)
|
|
||||||
|
|
||||||
if len(seedJob.ID) == 0 {
|
if len(seedJob.ID) == 0 {
|
||||||
logger.Info("id can't be empty")
|
messages = append(messages, fmt.Sprintf("seedJob `%s` id can't be empty", seedJob.ID))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(seedJob.RepositoryBranch) == 0 {
|
if len(seedJob.RepositoryBranch) == 0 {
|
||||||
logger.Info("repository branch can't be empty")
|
messages = append(messages, fmt.Sprintf("seedJob `%s` repository branch can't be empty", seedJob.ID))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(seedJob.RepositoryURL) == 0 {
|
if len(seedJob.RepositoryURL) == 0 {
|
||||||
logger.Info("repository URL branch can't be empty")
|
messages = append(messages, fmt.Sprintf("seedJob `%s` repository URL branch can't be empty", seedJob.ID))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(seedJob.Targets) == 0 {
|
if len(seedJob.Targets) == 0 {
|
||||||
logger.Info("targets can't be empty")
|
messages = append(messages, fmt.Sprintf("seedJob `%s` targets can't be empty", seedJob.ID))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := v1alpha2.AllowedJenkinsCredentialMap[string(seedJob.JenkinsCredentialType)]; !ok {
|
if _, ok := v1alpha2.AllowedJenkinsCredentialMap[string(seedJob.JenkinsCredentialType)]; !ok {
|
||||||
logger.Info("unknown credential type")
|
messages = append(messages, fmt.Sprintf("seedJob `%s` unknown credential type", seedJob.ID))
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType ||
|
if (seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType ||
|
||||||
seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType) && len(seedJob.CredentialID) == 0 {
|
seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType) && len(seedJob.CredentialID) == 0 {
|
||||||
logger.Info("credential ID can't be empty")
|
messages = append(messages, fmt.Sprintf("seedJob `%s` credential ID can't be empty", seedJob.ID))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate repository url match private key
|
// validate repository url match private key
|
||||||
if strings.Contains(seedJob.RepositoryURL, "git@") && seedJob.JenkinsCredentialType == v1alpha2.NoJenkinsCredentialCredentialType {
|
if strings.Contains(seedJob.RepositoryURL, "git@") && seedJob.JenkinsCredentialType == v1alpha2.NoJenkinsCredentialCredentialType {
|
||||||
logger.Info("Jenkins credential must be set while using ssh repository url")
|
messages = append(messages, fmt.Sprintf("seedJob `%s` Jenkins credential must be set while using ssh repository url", seedJob.ID))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType {
|
if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType {
|
||||||
|
|
@ -71,56 +59,66 @@ func (s *SeedJobs) ValidateSeedJobs(jenkins v1alpha2.Jenkins) (bool, error) {
|
||||||
namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: seedJob.CredentialID}
|
namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: seedJob.CredentialID}
|
||||||
err := s.k8sClient.Get(context.TODO(), namespaceName, secret)
|
err := s.k8sClient.Get(context.TODO(), namespaceName, secret)
|
||||||
if err != nil && apierrors.IsNotFound(err) {
|
if err != nil && apierrors.IsNotFound(err) {
|
||||||
logger.Info(fmt.Sprintf("required secret '%s' with Jenkins credential not found", seedJob.CredentialID))
|
messages = append(messages, fmt.Sprintf("seedJob `%s` required secret '%s' with Jenkins credential not found", seedJob.ID, seedJob.CredentialID))
|
||||||
return false, nil
|
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return false, stackerr.WithStack(err)
|
return nil, stackerr.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType {
|
if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType {
|
||||||
if ok := validateBasicSSHSecret(logger, *secret); !ok {
|
if msg := validateBasicSSHSecret(*secret); len(msg) > 0 {
|
||||||
valid = false
|
for _, m := range msg {
|
||||||
|
messages = append(messages, fmt.Sprintf("seedJob `%s` %s", seedJob.ID, m))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType {
|
if seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType {
|
||||||
if ok := validateUsernamePasswordSecret(logger, *secret); !ok {
|
if msg := validateUsernamePasswordSecret(*secret); len(msg) > 0 {
|
||||||
valid = false
|
for _, m := range msg {
|
||||||
|
messages = append(messages, fmt.Sprintf("seedJob `%s` %s", seedJob.ID, m))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(seedJob.BuildPeriodically) > 0 {
|
if len(seedJob.BuildPeriodically) > 0 {
|
||||||
if !s.validateSchedule(seedJob, seedJob.BuildPeriodically, "buildPeriodically") {
|
if msg := s.validateSchedule(seedJob, seedJob.BuildPeriodically, "buildPeriodically"); len(msg) > 0 {
|
||||||
valid = false
|
for _, m := range msg {
|
||||||
|
messages = append(messages, fmt.Sprintf("seedJob `%s` %s", seedJob.ID, m))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(seedJob.PollSCM) > 0 {
|
if len(seedJob.PollSCM) > 0 {
|
||||||
if !s.validateSchedule(seedJob, seedJob.PollSCM, "pollSCM") {
|
if msg := s.validateSchedule(seedJob, seedJob.PollSCM, "pollSCM"); len(msg) > 0 {
|
||||||
valid = false
|
for _, m := range msg {
|
||||||
|
messages = append(messages, fmt.Sprintf("seedJob `%s` %s", seedJob.ID, m))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if seedJob.GitHubPushTrigger {
|
if seedJob.GitHubPushTrigger {
|
||||||
if !s.validateGitHubPushTrigger(jenkins) {
|
if msg := s.validateGitHubPushTrigger(jenkins); len(msg) > 0 {
|
||||||
valid = false
|
for _, m := range msg {
|
||||||
|
messages = append(messages, fmt.Sprintf("seedJob `%s` %s", seedJob.ID, m))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeedJobs) validateSchedule(job v1alpha2.SeedJob, str string, key string) bool {
|
func (s *SeedJobs) validateSchedule(job v1alpha2.SeedJob, str string, key string) []string {
|
||||||
|
var messages []string
|
||||||
_, err := cron.Parse(str)
|
_, err := cron.Parse(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.V(log.VWarn).Info(fmt.Sprintf("`%s` schedule '%s' is invalid cron spec in `%s`", key, str, job.ID))
|
messages = append(messages, fmt.Sprintf("`%s` schedule '%s' is invalid cron spec in `%s`", key, str, job.ID))
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeedJobs) validateGitHubPushTrigger(jenkins v1alpha2.Jenkins) bool {
|
func (s *SeedJobs) validateGitHubPushTrigger(jenkins v1alpha2.Jenkins) []string {
|
||||||
|
var messages []string
|
||||||
exists := false
|
exists := false
|
||||||
for _, plugin := range jenkins.Spec.Master.BasePlugins {
|
for _, plugin := range jenkins.Spec.Master.BasePlugins {
|
||||||
if plugin.Name == "github" {
|
if plugin.Name == "github" {
|
||||||
|
|
@ -136,75 +134,65 @@ func (s *SeedJobs) validateGitHubPushTrigger(jenkins v1alpha2.Jenkins) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists && !userExists {
|
if !exists && !userExists {
|
||||||
s.logger.V(log.VWarn).Info("githubPushTrigger is set. This function requires `github` plugin installed in .Spec.Master.Plugins because seed jobs Push Trigger function needs it")
|
messages = append(messages, "githubPushTrigger is set. This function requires `github` plugin installed in .Spec.Master.Plugins because seed jobs Push Trigger function needs it")
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SeedJobs) validateIfIDIsUnique(seedJobs []v1alpha2.SeedJob) bool {
|
func (s *SeedJobs) validateIfIDIsUnique(seedJobs []v1alpha2.SeedJob) []string {
|
||||||
|
var messages []string
|
||||||
ids := map[string]bool{}
|
ids := map[string]bool{}
|
||||||
for _, seedJob := range seedJobs {
|
for _, seedJob := range seedJobs {
|
||||||
if _, found := ids[seedJob.ID]; found {
|
if _, found := ids[seedJob.ID]; found {
|
||||||
s.logger.V(log.VWarn).Info(fmt.Sprintf("'%s' seed job ID is not unique", seedJob.ID))
|
messages = append(messages, fmt.Sprintf("'%s' seed job ID is not unique", seedJob.ID))
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
ids[seedJob.ID] = true
|
ids[seedJob.ID] = true
|
||||||
}
|
}
|
||||||
return true
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateBasicSSHSecret(logger logr.InfoLogger, secret v1.Secret) bool {
|
func validateBasicSSHSecret(secret v1.Secret) []string {
|
||||||
valid := true
|
var messages []string
|
||||||
username, exists := secret.Data[UsernameSecretKey]
|
username, exists := secret.Data[UsernameSecretKey]
|
||||||
if !exists {
|
if !exists {
|
||||||
logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", UsernameSecretKey, secret.ObjectMeta.Name))
|
messages = append(messages, fmt.Sprintf("required data '%s' not found in secret '%s'", UsernameSecretKey, secret.ObjectMeta.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
if len(username) == 0 {
|
if len(username) == 0 {
|
||||||
logger.Info(fmt.Sprintf("required data '%s' is empty in secret '%s'", UsernameSecretKey, secret.ObjectMeta.Name))
|
messages = append(messages, fmt.Sprintf("required data '%s' is empty in secret '%s'", UsernameSecretKey, secret.ObjectMeta.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKey, exists := secret.Data[PrivateKeySecretKey]
|
privateKey, exists := secret.Data[PrivateKeySecretKey]
|
||||||
if !exists {
|
if !exists {
|
||||||
logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", PrivateKeySecretKey, secret.ObjectMeta.Name))
|
messages = append(messages, fmt.Sprintf("required data '%s' not found in secret '%s'", PrivateKeySecretKey, secret.ObjectMeta.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
if len(string(privateKey)) == 0 {
|
if len(string(privateKey)) == 0 {
|
||||||
logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", PrivateKeySecretKey, secret.ObjectMeta.Name))
|
messages = append(messages, fmt.Sprintf("required data '%s' not found in secret '%s'", PrivateKeySecretKey, secret.ObjectMeta.Name))
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
if err := validatePrivateKey(string(privateKey)); err != nil {
|
if err := validatePrivateKey(string(privateKey)); err != nil {
|
||||||
logger.Info(fmt.Sprintf("private key '%s' invalid in secret '%s': %s", PrivateKeySecretKey, secret.ObjectMeta.Name, err))
|
messages = append(messages, fmt.Sprintf("private key '%s' invalid in secret '%s': %s", PrivateKeySecretKey, secret.ObjectMeta.Name, err))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateUsernamePasswordSecret(logger logr.InfoLogger, secret v1.Secret) bool {
|
func validateUsernamePasswordSecret(secret v1.Secret) []string {
|
||||||
valid := true
|
var messages []string
|
||||||
username, exists := secret.Data[UsernameSecretKey]
|
username, exists := secret.Data[UsernameSecretKey]
|
||||||
if !exists {
|
if !exists {
|
||||||
logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", UsernameSecretKey, secret.ObjectMeta.Name))
|
messages = append(messages, fmt.Sprintf("required data '%s' not found in secret '%s'", UsernameSecretKey, secret.ObjectMeta.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
if len(username) == 0 {
|
if len(username) == 0 {
|
||||||
logger.Info(fmt.Sprintf("required data '%s' is empty in secret '%s'", UsernameSecretKey, secret.ObjectMeta.Name))
|
messages = append(messages, fmt.Sprintf("required data '%s' is empty in secret '%s'", UsernameSecretKey, secret.ObjectMeta.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
password, exists := secret.Data[PasswordSecretKey]
|
password, exists := secret.Data[PasswordSecretKey]
|
||||||
if !exists {
|
if !exists {
|
||||||
logger.Info(fmt.Sprintf("required data '%s' not found in secret '%s'", PasswordSecretKey, secret.ObjectMeta.Name))
|
messages = append(messages, fmt.Sprintf("required data '%s' not found in secret '%s'", PasswordSecretKey, secret.ObjectMeta.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
if len(password) == 0 {
|
if len(password) == 0 {
|
||||||
logger.Info(fmt.Sprintf("required data '%s' is empty in secret '%s'", PasswordSecretKey, secret.ObjectMeta.Name))
|
messages = append(messages, fmt.Sprintf("required data '%s' is empty in secret '%s'", PasswordSecretKey, secret.ObjectMeta.Name))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid
|
return messages
|
||||||
}
|
}
|
||||||
|
|
||||||
func validatePrivateKey(privateKey string) error {
|
func validatePrivateKey(privateKey string) error {
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, true, result)
|
assert.Nil(t, result)
|
||||||
})
|
})
|
||||||
t.Run("Invalid without id", func(t *testing.T) {
|
t.Run("Invalid without id", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -96,7 +96,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `` id can't be empty"})
|
||||||
})
|
})
|
||||||
t.Run("Valid with private key and secret", func(t *testing.T) {
|
t.Run("Valid with private key and secret", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -129,7 +130,7 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, true, result)
|
assert.Nil(t, result)
|
||||||
})
|
})
|
||||||
t.Run("Invalid private key in secret", func(t *testing.T) {
|
t.Run("Invalid private key in secret", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -162,7 +163,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` private key 'privateKey' invalid in secret 'deploy-keys': failed to decode PEM block"})
|
||||||
})
|
})
|
||||||
t.Run("Invalid with PrivateKey and empty Secret data", func(t *testing.T) {
|
t.Run("Invalid with PrivateKey and empty Secret data", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -195,7 +197,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` required data 'privateKey' not found in secret 'deploy-keys'", "seedJob `example` private key 'privateKey' invalid in secret 'deploy-keys': failed to decode PEM block"})
|
||||||
})
|
})
|
||||||
t.Run("Invalid with ssh RepositoryURL and empty PrivateKey", func(t *testing.T) {
|
t.Run("Invalid with ssh RepositoryURL and empty PrivateKey", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -217,7 +220,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string([]string{"seedJob `example` required secret 'jenkins-operator-e2e' with Jenkins credential not found", "seedJob `example` required data 'username' not found in secret ''", "seedJob `example` required data 'username' is empty in secret ''", "seedJob `example` required data 'privateKey' not found in secret ''", "seedJob `example` required data 'privateKey' not found in secret ''", "seedJob `example` private key 'privateKey' invalid in secret '': failed to decode PEM block"}))
|
||||||
})
|
})
|
||||||
t.Run("Invalid without targets", func(t *testing.T) {
|
t.Run("Invalid without targets", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -237,7 +241,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` targets can't be empty"})
|
||||||
})
|
})
|
||||||
t.Run("Invalid without repository URL", func(t *testing.T) {
|
t.Run("Invalid without repository URL", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -257,7 +262,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` repository URL branch can't be empty"})
|
||||||
})
|
})
|
||||||
t.Run("Invalid without repository branch", func(t *testing.T) {
|
t.Run("Invalid without repository branch", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -277,7 +283,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` repository branch can't be empty"})
|
||||||
})
|
})
|
||||||
t.Run("Valid with username and password", func(t *testing.T) {
|
t.Run("Valid with username and password", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -310,7 +317,7 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, true, result)
|
assert.Nil(t, result)
|
||||||
})
|
})
|
||||||
t.Run("Invalid with empty username", func(t *testing.T) {
|
t.Run("Invalid with empty username", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -343,7 +350,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` required data 'username' is empty in secret 'deploy-keys'"})
|
||||||
})
|
})
|
||||||
t.Run("Invalid with empty password", func(t *testing.T) {
|
t.Run("Invalid with empty password", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -376,7 +384,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` required data 'password' is empty in secret 'deploy-keys'"})
|
||||||
})
|
})
|
||||||
t.Run("Invalid without username", func(t *testing.T) {
|
t.Run("Invalid without username", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -408,7 +417,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` required data 'username' not found in secret 'deploy-keys'", "seedJob `example` required data 'username' is empty in secret 'deploy-keys'"})
|
||||||
})
|
})
|
||||||
t.Run("Invalid without password", func(t *testing.T) {
|
t.Run("Invalid without password", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -440,7 +450,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, false, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` required data 'password' not found in secret 'deploy-keys'", "seedJob `example` required data 'password' is empty in secret 'deploy-keys'"})
|
||||||
})
|
})
|
||||||
t.Run("Invalid with wrong cron spec", func(t *testing.T) {
|
t.Run("Invalid with wrong cron spec", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -463,7 +474,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` `buildPeriodically` schedule 'invalid-cron-spec' is invalid cron spec in `example`"})
|
||||||
})
|
})
|
||||||
t.Run("Valid with good cron spec", func(t *testing.T) {
|
t.Run("Valid with good cron spec", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -487,7 +499,7 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, result)
|
assert.Nil(t, result)
|
||||||
})
|
})
|
||||||
t.Run("Invalid with set githubPushTrigger and not installed github plugin", func(t *testing.T) {
|
t.Run("Invalid with set githubPushTrigger and not installed github plugin", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -510,7 +522,8 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, result)
|
|
||||||
|
assert.Equal(t, result, []string{"seedJob `example` githubPushTrigger is set. This function requires `github` plugin installed in .Spec.Master.Plugins because seed jobs Push Trigger function needs it"})
|
||||||
})
|
})
|
||||||
t.Run("Invalid with set githubPushTrigger and not installed github plugin", func(t *testing.T) {
|
t.Run("Invalid with set githubPushTrigger and not installed github plugin", func(t *testing.T) {
|
||||||
jenkins := v1alpha2.Jenkins{
|
jenkins := v1alpha2.Jenkins{
|
||||||
|
|
@ -538,7 +551,7 @@ func TestValidateSeedJobs(t *testing.T) {
|
||||||
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
result, err := seedJobs.ValidateSeedJobs(jenkins)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, result)
|
assert.Nil(t, result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -549,7 +562,7 @@ func TestValidateIfIDIsUnique(t *testing.T) {
|
||||||
}
|
}
|
||||||
ctrl := New(nil, nil, logf.ZapLogger(false))
|
ctrl := New(nil, nil, logf.ZapLogger(false))
|
||||||
got := ctrl.validateIfIDIsUnique(seedJobs)
|
got := ctrl.validateIfIDIsUnique(seedJobs)
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("duplicated ids", func(t *testing.T) {
|
t.Run("duplicated ids", func(t *testing.T) {
|
||||||
seedJobs := []v1alpha2.SeedJob{
|
seedJobs := []v1alpha2.SeedJob{
|
||||||
|
|
@ -557,6 +570,7 @@ func TestValidateIfIDIsUnique(t *testing.T) {
|
||||||
}
|
}
|
||||||
ctrl := New(nil, nil, logf.ZapLogger(false))
|
ctrl := New(nil, nil, logf.ZapLogger(false))
|
||||||
got := ctrl.validateIfIDIsUnique(seedJobs)
|
got := ctrl.validateIfIDIsUnique(seedJobs)
|
||||||
assert.Equal(t, false, got)
|
|
||||||
|
assert.Equal(t, got, []string{"'first' seed job ID is not unique"})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate validates Jenkins CR Spec section
|
// Validate validates Jenkins CR Spec section
|
||||||
func (r *ReconcileUserConfiguration) Validate(jenkins *v1alpha2.Jenkins) (bool, error) {
|
func (r *ReconcileUserConfiguration) Validate(jenkins *v1alpha2.Jenkins) ([]string, error) {
|
||||||
backupAndRestore := backuprestore.New(r.k8sClient, r.clientSet, r.logger, r.jenkins, r.config)
|
backupAndRestore := backuprestore.New(r.k8sClient, r.clientSet, r.logger, r.jenkins, r.config)
|
||||||
if ok := backupAndRestore.Validate(); !ok {
|
if msg := backupAndRestore.Validate(); msg != nil {
|
||||||
return false, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
seedJobs := seedjobs.New(r.jenkinsClient, r.k8sClient, r.logger)
|
seedJobs := seedjobs.New(r.jenkinsClient, r.k8sClient, r.logger)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package jenkins
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||||
|
|
@ -11,8 +12,8 @@ import (
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/base/resources"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/configuration/user"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/constants"
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/notifications"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins"
|
"github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins/plugins"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/event"
|
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
||||||
"github.com/jenkinsci/kubernetes-operator/version"
|
"github.com/jenkinsci/kubernetes-operator/version"
|
||||||
|
|
||||||
|
|
@ -34,15 +35,6 @@ import (
|
||||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// reasonBaseConfigurationSuccess is the event which informs base configuration has been completed successfully
|
|
||||||
reasonBaseConfigurationSuccess event.Reason = "BaseConfigurationSuccess"
|
|
||||||
// reasonUserConfigurationSuccess is the event which informs user configuration has been completed successfully
|
|
||||||
reasonUserConfigurationSuccess event.Reason = "BaseConfigurationFailure"
|
|
||||||
// reasonCRValidationFailure is the event which informs user has provided invalid configuration in Jenkins CR
|
|
||||||
reasonCRValidationFailure event.Reason = "CRValidationFailure"
|
|
||||||
)
|
|
||||||
|
|
||||||
type reconcileError struct {
|
type reconcileError struct {
|
||||||
err error
|
err error
|
||||||
counter uint64
|
counter uint64
|
||||||
|
|
@ -52,20 +44,20 @@ var reconcileErrors = map[string]reconcileError{}
|
||||||
|
|
||||||
// Add creates a new Jenkins Controller and adds it to the Manager. The Manager will set fields on the Controller
|
// Add creates a new Jenkins Controller and adds it to the Manager. The Manager will set fields on the Controller
|
||||||
// and Start it when the Manager is Started.
|
// and Start it when the Manager is Started.
|
||||||
func Add(mgr manager.Manager, local, minikube bool, events event.Recorder, clientSet kubernetes.Clientset, config rest.Config) error {
|
func Add(mgr manager.Manager, local, minikube bool, clientSet kubernetes.Clientset, config rest.Config, notificationEvents *chan notifications.Event) error {
|
||||||
return add(mgr, newReconciler(mgr, local, minikube, events, clientSet, config))
|
return add(mgr, newReconciler(mgr, local, minikube, clientSet, config, notificationEvents))
|
||||||
}
|
}
|
||||||
|
|
||||||
// newReconciler returns a new reconcile.Reconciler
|
// newReconciler returns a new reconcile.Reconciler
|
||||||
func newReconciler(mgr manager.Manager, local, minikube bool, events event.Recorder, clientSet kubernetes.Clientset, config rest.Config) reconcile.Reconciler {
|
func newReconciler(mgr manager.Manager, local, minikube bool, clientSet kubernetes.Clientset, config rest.Config, notificationEvents *chan notifications.Event) reconcile.Reconciler {
|
||||||
return &ReconcileJenkins{
|
return &ReconcileJenkins{
|
||||||
client: mgr.GetClient(),
|
client: mgr.GetClient(),
|
||||||
scheme: mgr.GetScheme(),
|
scheme: mgr.GetScheme(),
|
||||||
local: local,
|
local: local,
|
||||||
minikube: minikube,
|
minikube: minikube,
|
||||||
events: events,
|
|
||||||
clientSet: clientSet,
|
clientSet: clientSet,
|
||||||
config: config,
|
config: config,
|
||||||
|
notificationEvents: notificationEvents,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,17 +114,18 @@ type ReconcileJenkins struct {
|
||||||
client client.Client
|
client client.Client
|
||||||
scheme *runtime.Scheme
|
scheme *runtime.Scheme
|
||||||
local, minikube bool
|
local, minikube bool
|
||||||
events event.Recorder
|
|
||||||
clientSet kubernetes.Clientset
|
clientSet kubernetes.Clientset
|
||||||
config rest.Config
|
config rest.Config
|
||||||
|
notificationEvents *chan notifications.Event
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconcile it's a main reconciliation loop which maintain desired state based on Jenkins.Spec
|
// Reconcile it's a main reconciliation loop which maintain desired state based on Jenkins.Spec
|
||||||
func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Result, error) {
|
func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Result, error) {
|
||||||
|
reconcileFailLimit := uint64(10)
|
||||||
logger := r.buildLogger(request.Name)
|
logger := r.buildLogger(request.Name)
|
||||||
logger.V(log.VDebug).Info("Reconciling Jenkins")
|
logger.V(log.VDebug).Info("Reconciling Jenkins")
|
||||||
|
|
||||||
result, err := r.reconcile(request, logger)
|
result, jenkins, err := r.reconcile(request, logger)
|
||||||
if err != nil && apierrors.IsConflict(err) {
|
if err != nil && apierrors.IsConflict(err) {
|
||||||
return reconcile.Result{Requeue: true}, nil
|
return reconcile.Result{Requeue: true}, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|
@ -151,11 +144,19 @@ func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Resul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reconcileErrors[request.Name] = lastErrors
|
reconcileErrors[request.Name] = lastErrors
|
||||||
if lastErrors.counter >= 15 {
|
if lastErrors.counter >= reconcileFailLimit {
|
||||||
if log.Debug {
|
if log.Debug {
|
||||||
logger.V(log.VWarn).Info(fmt.Sprintf("Reconcile loop failed ten times with the same error, giving up: %+v", err))
|
logger.V(log.VWarn).Info(fmt.Sprintf("Reconcile loop failed %d times with the same error, giving up: %+v", reconcileFailLimit, err))
|
||||||
} else {
|
} else {
|
||||||
logger.V(log.VWarn).Info(fmt.Sprintf("Reconcile loop failed ten times with the same error, giving up: %s", err))
|
logger.V(log.VWarn).Info(fmt.Sprintf("Reconcile loop failed %d times with the same error, giving up: %s", reconcileFailLimit, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
*r.notificationEvents <- notifications.Event{
|
||||||
|
Jenkins: *jenkins,
|
||||||
|
Phase: notifications.PhaseBase,
|
||||||
|
LogLevel: v1alpha2.NotificationLogLevelWarning,
|
||||||
|
Message: fmt.Sprintf("Reconcile loop failed %d times with the same error, giving up: %s", reconcileFailLimit, err),
|
||||||
|
MessagesVerbose: []string{fmt.Sprintf("Reconcile loop failed %d times with the same error, giving up: %+v", reconcileFailLimit, err)},
|
||||||
}
|
}
|
||||||
return reconcile.Result{Requeue: false}, nil
|
return reconcile.Result{Requeue: false}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +177,7 @@ func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Resul
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logger) (reconcile.Result, error) {
|
func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logger) (reconcile.Result, *v1alpha2.Jenkins, error) {
|
||||||
// Fetch the Jenkins instance
|
// Fetch the Jenkins instance
|
||||||
jenkins := &v1alpha2.Jenkins{}
|
jenkins := &v1alpha2.Jenkins{}
|
||||||
err := r.client.Get(context.TODO(), request.NamespacedName, jenkins)
|
err := r.client.Get(context.TODO(), request.NamespacedName, jenkins)
|
||||||
|
|
@ -185,38 +186,47 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logg
|
||||||
// Request object not found, could have been deleted after reconcile request.
|
// Request object not found, could have been deleted after reconcile request.
|
||||||
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
|
// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.
|
||||||
// Return and don't requeue
|
// Return and don't requeue
|
||||||
return reconcile.Result{}, nil
|
return reconcile.Result{}, nil, nil
|
||||||
}
|
}
|
||||||
// Error reading the object - requeue the request.
|
// Error reading the object - requeue the request.
|
||||||
return reconcile.Result{}, errors.WithStack(err)
|
return reconcile.Result{}, nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
err = r.setDefaults(jenkins, logger)
|
err = r.setDefaults(jenkins, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reconcile.Result{}, err
|
return reconcile.Result{}, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reconcile base configuration
|
// Reconcile base configuration
|
||||||
baseConfiguration := base.New(r.client, r.scheme, logger, jenkins, r.local, r.minikube, &r.clientSet, &r.config)
|
baseConfiguration := base.New(r.client, r.scheme, logger, jenkins, r.local, r.minikube, &r.clientSet, &r.config)
|
||||||
|
|
||||||
valid, err := baseConfiguration.Validate(jenkins)
|
messages, err := baseConfiguration.Validate(jenkins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reconcile.Result{}, err
|
return reconcile.Result{}, nil, err
|
||||||
}
|
}
|
||||||
if !valid {
|
if len(messages) > 0 {
|
||||||
r.events.Emit(jenkins, event.TypeWarning, reasonCRValidationFailure, "Base CR validation failed")
|
message := "Validation of base configuration failed, please correct Jenkins CR."
|
||||||
logger.V(log.VWarn).Info("Validation of base configuration failed, please correct Jenkins CR")
|
*r.notificationEvents <- notifications.Event{
|
||||||
return reconcile.Result{}, nil // don't requeue
|
Jenkins: *jenkins,
|
||||||
|
Phase: notifications.PhaseBase,
|
||||||
|
LogLevel: v1alpha2.NotificationLogLevelWarning,
|
||||||
|
Message: message,
|
||||||
|
MessagesVerbose: messages,
|
||||||
|
}
|
||||||
|
logger.V(log.VWarn).Info(message)
|
||||||
|
for _, msg := range messages {
|
||||||
|
logger.V(log.VWarn).Info(msg)
|
||||||
|
}
|
||||||
|
return reconcile.Result{}, nil, nil // don't requeue
|
||||||
}
|
}
|
||||||
|
|
||||||
result, jenkinsClient, err := baseConfiguration.Reconcile()
|
result, jenkinsClient, err := baseConfiguration.Reconcile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reconcile.Result{}, err
|
return reconcile.Result{}, nil, err
|
||||||
}
|
}
|
||||||
if result.Requeue {
|
if result.Requeue {
|
||||||
return result, nil
|
return result, nil, nil
|
||||||
}
|
}
|
||||||
if jenkinsClient == nil {
|
if jenkinsClient == nil {
|
||||||
return reconcile.Result{Requeue: false}, nil
|
return reconcile.Result{Requeue: false}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if jenkins.Status.BaseConfigurationCompletedTime == nil {
|
if jenkins.Status.BaseConfigurationCompletedTime == nil {
|
||||||
|
|
@ -224,31 +234,50 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logg
|
||||||
jenkins.Status.BaseConfigurationCompletedTime = &now
|
jenkins.Status.BaseConfigurationCompletedTime = &now
|
||||||
err = r.client.Update(context.TODO(), jenkins)
|
err = r.client.Update(context.TODO(), jenkins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reconcile.Result{}, errors.WithStack(err)
|
return reconcile.Result{}, nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
logger.Info(fmt.Sprintf("Base configuration phase is complete, took %s",
|
|
||||||
jenkins.Status.BaseConfigurationCompletedTime.Sub(jenkins.Status.ProvisionStartTime.Time)))
|
message := fmt.Sprintf("Base configuration phase is complete, took %s",
|
||||||
r.events.Emit(jenkins, event.TypeNormal, reasonBaseConfigurationSuccess, "Base configuration completed")
|
jenkins.Status.BaseConfigurationCompletedTime.Sub(jenkins.Status.ProvisionStartTime.Time))
|
||||||
|
*r.notificationEvents <- notifications.Event{
|
||||||
|
Jenkins: *jenkins,
|
||||||
|
Phase: notifications.PhaseBase,
|
||||||
|
LogLevel: v1alpha2.NotificationLogLevelInfo,
|
||||||
|
Message: message,
|
||||||
|
MessagesVerbose: messages,
|
||||||
|
}
|
||||||
|
logger.Info(message)
|
||||||
}
|
}
|
||||||
// Reconcile user configuration
|
// Reconcile user configuration
|
||||||
userConfiguration := user.New(r.client, jenkinsClient, logger, jenkins, r.clientSet, r.config)
|
userConfiguration := user.New(r.client, jenkinsClient, logger, jenkins, r.clientSet, r.config)
|
||||||
|
|
||||||
valid, err = userConfiguration.Validate(jenkins)
|
messages, err = userConfiguration.Validate(jenkins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reconcile.Result{}, err
|
return reconcile.Result{}, nil, err
|
||||||
}
|
}
|
||||||
if !valid {
|
if len(messages) > 0 {
|
||||||
logger.V(log.VWarn).Info("Validation of user configuration failed, please correct Jenkins CR")
|
message := fmt.Sprintf("Validation of user configuration failed, please correct Jenkins CR")
|
||||||
r.events.Emit(jenkins, event.TypeWarning, reasonCRValidationFailure, "User CR validation failed")
|
*r.notificationEvents <- notifications.Event{
|
||||||
return reconcile.Result{}, nil // don't requeue
|
Jenkins: *jenkins,
|
||||||
|
Phase: notifications.PhaseUser,
|
||||||
|
LogLevel: v1alpha2.NotificationLogLevelWarning,
|
||||||
|
Message: message,
|
||||||
|
MessagesVerbose: messages,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.V(log.VWarn).Info(message)
|
||||||
|
for _, msg := range messages {
|
||||||
|
logger.V(log.VWarn).Info(msg)
|
||||||
|
}
|
||||||
|
return reconcile.Result{}, nil, nil // don't requeue
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err = userConfiguration.Reconcile()
|
result, err = userConfiguration.Reconcile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reconcile.Result{}, err
|
return reconcile.Result{}, nil, err
|
||||||
}
|
}
|
||||||
if result.Requeue {
|
if result.Requeue {
|
||||||
return result, nil
|
return result, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if jenkins.Status.UserConfigurationCompletedTime == nil {
|
if jenkins.Status.UserConfigurationCompletedTime == nil {
|
||||||
|
|
@ -256,14 +285,21 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request, logger logr.Logg
|
||||||
jenkins.Status.UserConfigurationCompletedTime = &now
|
jenkins.Status.UserConfigurationCompletedTime = &now
|
||||||
err = r.client.Update(context.TODO(), jenkins)
|
err = r.client.Update(context.TODO(), jenkins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return reconcile.Result{}, errors.WithStack(err)
|
return reconcile.Result{}, nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
logger.Info(fmt.Sprintf("User configuration phase is complete, took %s",
|
message := fmt.Sprintf("User configuration phase is complete, took %s",
|
||||||
jenkins.Status.UserConfigurationCompletedTime.Sub(jenkins.Status.ProvisionStartTime.Time)))
|
jenkins.Status.UserConfigurationCompletedTime.Sub(jenkins.Status.ProvisionStartTime.Time))
|
||||||
r.events.Emit(jenkins, event.TypeNormal, reasonUserConfigurationSuccess, "User configuration completed")
|
*r.notificationEvents <- notifications.Event{
|
||||||
|
Jenkins: *jenkins,
|
||||||
|
Phase: notifications.PhaseUser,
|
||||||
|
LogLevel: v1alpha2.NotificationLogLevelInfo,
|
||||||
|
Message: message,
|
||||||
|
MessagesVerbose: messages,
|
||||||
|
}
|
||||||
|
logger.Info(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return reconcile.Result{}, nil
|
return reconcile.Result{}, jenkins, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReconcileJenkins) buildLogger(jenkinsName string) logr.Logger {
|
func (r *ReconcileJenkins) buildLogger(jenkinsName string) logr.Logger {
|
||||||
|
|
|
||||||
|
|
@ -20,21 +20,17 @@ const content = `
|
||||||
<html>
|
<html>
|
||||||
<head></head>
|
<head></head>
|
||||||
<body>
|
<body>
|
||||||
<h1 style="background-color: %s; color: white; padding: 3px 10px;">Jenkins Operator Reconciled</h1>
|
<h1 style="background-color: %s; color: white; padding: 3px 10px;">%s</h1>
|
||||||
<h3>Failed to do something</h3>
|
<h3>%s</h3>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>CR name:</b></td>
|
<td><b>CR name:</b></td>
|
||||||
<td>%s</td>
|
<td>%s</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><b>Configuration type:</b></td>
|
<td><b>Phase:</b></td>
|
||||||
<td>%s</td>
|
<td>%s</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td><b>Status:</b></td>
|
|
||||||
<td><b style="color: %s;">%s</b></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
<h6 style="font-size: 11px; color: grey; margin-top: 15px;">Powered by Jenkins Operator <3</h6>
|
<h6 style="font-size: 11px; color: grey; margin-top: 15px;">Powered by Jenkins Operator <3</h6>
|
||||||
</body>
|
</body>
|
||||||
|
|
@ -74,9 +70,22 @@ func (m MailGun) Send(event Event, config v1alpha2.Notification) error {
|
||||||
|
|
||||||
mg := mailgun.NewMailgun(config.Mailgun.Domain, secretValue)
|
mg := mailgun.NewMailgun(config.Mailgun.Domain, secretValue)
|
||||||
|
|
||||||
htmlMessage := fmt.Sprintf(content, m.getStatusColor(event.LogLevel), event.Jenkins.Name, event.ConfigurationType, m.getStatusColor(event.LogLevel), string(event.LogLevel))
|
var statusMessage string
|
||||||
|
|
||||||
msg := mg.NewMessage(fmt.Sprintf("Jenkins Operator Notifier <%s>", config.Mailgun.From), "Jenkins Operator Status", "", config.Mailgun.Recipient)
|
if config.Verbose {
|
||||||
|
message := event.Message + "<ul>"
|
||||||
|
for _, msg := range event.MessagesVerbose {
|
||||||
|
message = message + "<li>" + msg + "</li>"
|
||||||
|
}
|
||||||
|
message = message + "</ul>"
|
||||||
|
statusMessage = message
|
||||||
|
} else {
|
||||||
|
statusMessage = event.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlMessage := fmt.Sprintf(content, m.getStatusColor(event.LogLevel), notificationTitle(event), statusMessage, event.Jenkins.Name, event.Phase)
|
||||||
|
|
||||||
|
msg := mg.NewMessage(fmt.Sprintf("Jenkins Operator Notifier <%s>", config.Mailgun.From), notificationTitle(event), "", config.Mailgun.Recipient)
|
||||||
msg.SetHtml(htmlMessage)
|
msg.SetHtml(htmlMessage)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||||
|
|
@ -26,6 +27,7 @@ type TeamsMessage struct {
|
||||||
ThemeColor StatusColor `json:"themeColor"`
|
ThemeColor StatusColor `json:"themeColor"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Sections []TeamsSection `json:"sections"`
|
Sections []TeamsSection `json:"sections"`
|
||||||
|
Summary string `json:"summary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TeamsSection is MS Teams message section
|
// TeamsSection is MS Teams message section
|
||||||
|
|
@ -67,11 +69,10 @@ func (t Teams) Send(event Event, config v1alpha2.Notification) error {
|
||||||
return errors.Errorf("Microsoft Teams webhook URL is empty in secret '%s/%s[%s]", event.Jenkins.Namespace, selector.Name, selector.Key)
|
return errors.Errorf("Microsoft Teams webhook URL is empty in secret '%s/%s[%s]", event.Jenkins.Namespace, selector.Name, selector.Key)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := json.Marshal(TeamsMessage{
|
tm := &TeamsMessage{
|
||||||
Type: "MessageCard",
|
Type: "MessageCard",
|
||||||
Context: "https://schema.org/extensions",
|
Context: "https://schema.org/extensions",
|
||||||
ThemeColor: t.getStatusColor(event.LogLevel),
|
ThemeColor: t.getStatusColor(event.LogLevel),
|
||||||
Title: titleText,
|
|
||||||
Sections: []TeamsSection{
|
Sections: []TeamsSection{
|
||||||
{
|
{
|
||||||
Facts: []TeamsFact{
|
Facts: []TeamsFact{
|
||||||
|
|
@ -79,14 +80,6 @@ func (t Teams) Send(event Event, config v1alpha2.Notification) error {
|
||||||
Name: crNameFieldName,
|
Name: crNameFieldName,
|
||||||
Value: event.Jenkins.Name,
|
Value: event.Jenkins.Name,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: configurationTypeFieldName,
|
|
||||||
Value: event.ConfigurationType,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: loggingLevelFieldName,
|
|
||||||
Value: string(event.LogLevel),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: namespaceFieldName,
|
Name: namespaceFieldName,
|
||||||
Value: event.Jenkins.Namespace,
|
Value: event.Jenkins.Namespace,
|
||||||
|
|
@ -95,7 +88,28 @@ func (t Teams) Send(event Event, config v1alpha2.Notification) error {
|
||||||
Text: event.Message,
|
Text: event.Message,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Summary: event.Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
tm.Title = notificationTitle(event)
|
||||||
|
|
||||||
|
if config.Verbose {
|
||||||
|
message := event.Message
|
||||||
|
for _, msg := range event.MessagesVerbose {
|
||||||
|
message = message + "\n\n - " + msg
|
||||||
|
}
|
||||||
|
tm.Sections[0].Text += message
|
||||||
|
tm.Summary = message
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Phase != PhaseUnknown {
|
||||||
|
tm.Sections[0].Facts = append(tm.Sections[0].Facts, TeamsFact{
|
||||||
|
Name: phaseFieldName,
|
||||||
|
Value: string(event.Phase),
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := json.Marshal(tm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
@ -110,6 +124,9 @@ func (t Teams) Send(event Event, config v1alpha2.Notification) error {
|
||||||
return errors.WithStack(err)
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return errors.New(fmt.Sprintf("Invalid response from server: %s", resp.Status))
|
||||||
|
}
|
||||||
defer func() { _ = resp.Body.Close() }()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ func TestTeams_Send(t *testing.T) {
|
||||||
Namespace: testNamespace,
|
Namespace: testNamespace,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ConfigurationType: testConfigurationType,
|
Phase: testPhase,
|
||||||
Message: testMessage,
|
Message: testMessage,
|
||||||
MessageVerbose: testMessageVerbose,
|
MessagesVerbose: testMessageVerbose,
|
||||||
LogLevel: testLoggingLevel,
|
LogLevel: testLoggingLevel,
|
||||||
}
|
}
|
||||||
teams := Teams{k8sClient: fakeClient}
|
teams := Teams{k8sClient: fakeClient}
|
||||||
|
|
@ -42,7 +42,7 @@ func TestTeams_Send(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, message.Title, titleText)
|
assert.Equal(t, message.Title, notificationTitle(event))
|
||||||
assert.Equal(t, message.ThemeColor, teams.getStatusColor(event.LogLevel))
|
assert.Equal(t, message.ThemeColor, teams.getStatusColor(event.LogLevel))
|
||||||
|
|
||||||
mainSection := message.Sections[0]
|
mainSection := message.Sections[0]
|
||||||
|
|
@ -51,8 +51,8 @@ func TestTeams_Send(t *testing.T) {
|
||||||
|
|
||||||
for _, fact := range mainSection.Facts {
|
for _, fact := range mainSection.Facts {
|
||||||
switch fact.Name {
|
switch fact.Name {
|
||||||
case configurationTypeFieldName:
|
case phaseFieldName:
|
||||||
assert.Equal(t, fact.Value, event.ConfigurationType)
|
assert.Equal(t, fact.Value, string(event.Phase))
|
||||||
case crNameFieldName:
|
case crNameFieldName:
|
||||||
assert.Equal(t, fact.Value, event.Jenkins.Name)
|
assert.Equal(t, fact.Value, event.Jenkins.Name)
|
||||||
case messageFieldName:
|
case messageFieldName:
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,52 @@
|
||||||
package notifications
|
package notifications
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
"github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2"
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/event"
|
||||||
|
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
titleText = "Operator reconciled."
|
infoTitleText = "Jenkins Operator reconciliation info"
|
||||||
|
warnTitleText = "Jenkins Operator reconciliation warning"
|
||||||
messageFieldName = "Message"
|
messageFieldName = "Message"
|
||||||
loggingLevelFieldName = "Logging Level"
|
loggingLevelFieldName = "Logging Level"
|
||||||
crNameFieldName = "CR Name"
|
crNameFieldName = "CR Name"
|
||||||
configurationTypeFieldName = "Configuration Type"
|
phaseFieldName = "Phase"
|
||||||
namespaceFieldName = "Namespace"
|
namespaceFieldName = "Namespace"
|
||||||
footerContent = "Powered by Jenkins Operator"
|
footerContent = "Powered by Jenkins Operator"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// PhaseBase is core configuration of Jenkins provided by the Operator
|
||||||
|
PhaseBase Phase = "base"
|
||||||
|
|
||||||
|
// PhaseUser is user-defined configuration of Jenkins
|
||||||
|
PhaseUser Phase = "user"
|
||||||
|
|
||||||
|
// PhaseUnknown is untraceable type of configuration
|
||||||
|
PhaseUnknown Phase = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
testConfigurationType = "test-configuration"
|
testPhase = PhaseUser
|
||||||
testCrName = "test-cr"
|
testCrName = "test-cr"
|
||||||
testNamespace = "default"
|
testNamespace = "default"
|
||||||
testMessage = "test-message"
|
testMessage = "test-message"
|
||||||
testMessageVerbose = "detail-test-message"
|
testMessageVerbose = []string{"detail-test-message"}
|
||||||
testLoggingLevel = v1alpha2.NotificationLogLevelWarning
|
testLoggingLevel = v1alpha2.NotificationLogLevelWarning
|
||||||
|
|
||||||
client = http.Client{}
|
client = http.Client{}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Phase defines the type of configuration
|
||||||
|
type Phase string
|
||||||
|
|
||||||
// StatusColor is useful for better UX
|
// StatusColor is useful for better UX
|
||||||
type StatusColor string
|
type StatusColor string
|
||||||
|
|
||||||
|
|
@ -36,18 +56,18 @@ type LoggingLevel string
|
||||||
// Event contains event details which will be sent as a notification
|
// Event contains event details which will be sent as a notification
|
||||||
type Event struct {
|
type Event struct {
|
||||||
Jenkins v1alpha2.Jenkins
|
Jenkins v1alpha2.Jenkins
|
||||||
ConfigurationType string
|
Phase Phase
|
||||||
LogLevel v1alpha2.NotificationLogLevel
|
LogLevel v1alpha2.NotificationLogLevel
|
||||||
Message string
|
Message string
|
||||||
MessageVerbose string
|
MessagesVerbose []string
|
||||||
}
|
}
|
||||||
|
|
||||||
/*type service interface {
|
type service interface {
|
||||||
Send(event Event, notificationConfig v1alpha2.Notification) error
|
Send(event Event, notificationConfig v1alpha2.Notification) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen listens for incoming events and send it as notifications
|
// Listen listens for incoming events and send it as notifications
|
||||||
func Listen(events chan Event, k8sClient k8sclient.Client) {
|
func Listen(events chan Event, k8sEvent event.Recorder, k8sClient k8sclient.Client) {
|
||||||
for event := range events {
|
for event := range events {
|
||||||
logger := log.Log.WithValues("cr", event.Jenkins.Name)
|
logger := log.Log.WithValues("cr", event.Jenkins.Name)
|
||||||
for _, notificationConfig := range event.Jenkins.Spec.Notifications {
|
for _, notificationConfig := range event.Jenkins.Spec.Notifications {
|
||||||
|
|
@ -61,7 +81,7 @@ func Listen(events chan Event, k8sClient k8sclient.Client) {
|
||||||
} else if notificationConfig.Mailgun != nil {
|
} else if notificationConfig.Mailgun != nil {
|
||||||
svc = MailGun{k8sClient: k8sClient}
|
svc = MailGun{k8sClient: k8sClient}
|
||||||
} else {
|
} else {
|
||||||
logger.V(log.VWarn).Info(fmt.Sprintf("Unexpected notification `%+v`", notificationConfig))
|
logger.V(log.VWarn).Info(fmt.Sprintf("Unknown notification service `%+v`", notificationConfig))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,7 +97,18 @@ func Listen(events chan Event, k8sClient k8sclient.Client) {
|
||||||
}
|
}
|
||||||
}(notificationConfig)
|
}(notificationConfig)
|
||||||
}
|
}
|
||||||
|
k8sEvent.Emit(&event.Jenkins, logLevelEventType(event.LogLevel), "NotificationSent", event.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logLevelEventType(level v1alpha2.NotificationLogLevel) event.Type {
|
||||||
|
switch level {
|
||||||
|
case v1alpha2.NotificationLogLevelWarning:
|
||||||
|
return event.TypeWarning
|
||||||
|
case v1alpha2.NotificationLogLevelInfo:
|
||||||
|
return event.TypeNormal
|
||||||
|
default:
|
||||||
|
return event.TypeNormal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,6 +116,15 @@ func notify(svc service, event Event, manifest v1alpha2.Notification) error {
|
||||||
if event.LogLevel == v1alpha2.NotificationLogLevelInfo && manifest.LoggingLevel == v1alpha2.NotificationLogLevelWarning {
|
if event.LogLevel == v1alpha2.NotificationLogLevelInfo && manifest.LoggingLevel == v1alpha2.NotificationLogLevelWarning {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.Send(event, manifest)
|
return svc.Send(event, manifest)
|
||||||
}*/
|
}
|
||||||
|
|
||||||
|
func notificationTitle(event Event) string {
|
||||||
|
if event.LogLevel == v1alpha2.NotificationLogLevelInfo {
|
||||||
|
return infoTitleText
|
||||||
|
} else if event.LogLevel == v1alpha2.NotificationLogLevelWarning {
|
||||||
|
return warnTitleText
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,11 @@ func (s Slack) Send(event Event, config v1alpha2.Notification) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
slackMessage, err := json.Marshal(SlackMessage{
|
sm := &SlackMessage{
|
||||||
Attachments: []SlackAttachment{
|
Attachments: []SlackAttachment{
|
||||||
{
|
{
|
||||||
Fallback: "",
|
Fallback: "",
|
||||||
Color: s.getStatusColor(event.LogLevel),
|
Color: s.getStatusColor(event.LogLevel),
|
||||||
Text: titleText,
|
|
||||||
Fields: []SlackField{
|
Fields: []SlackField{
|
||||||
{
|
{
|
||||||
Title: messageFieldName,
|
Title: messageFieldName,
|
||||||
|
|
@ -81,16 +80,6 @@ func (s Slack) Send(event Event, config v1alpha2.Notification) error {
|
||||||
Value: event.Jenkins.Name,
|
Value: event.Jenkins.Name,
|
||||||
Short: true,
|
Short: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Title: configurationTypeFieldName,
|
|
||||||
Value: event.ConfigurationType,
|
|
||||||
Short: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Title: loggingLevelFieldName,
|
|
||||||
Value: string(event.LogLevel),
|
|
||||||
Short: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Title: namespaceFieldName,
|
Title: namespaceFieldName,
|
||||||
Value: event.Jenkins.Namespace,
|
Value: event.Jenkins.Namespace,
|
||||||
|
|
@ -100,15 +89,37 @@ func (s Slack) Send(event Event, config v1alpha2.Notification) error {
|
||||||
Footer: footerContent,
|
Footer: footerContent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
mainAttachment := &sm.Attachments[0]
|
||||||
|
|
||||||
|
mainAttachment.Title = notificationTitle(event)
|
||||||
|
|
||||||
|
if config.Verbose {
|
||||||
|
// TODO: or for title == message
|
||||||
|
message := event.Message
|
||||||
|
for _, msg := range event.MessagesVerbose {
|
||||||
|
message = message + "\n - " + msg
|
||||||
|
}
|
||||||
|
mainAttachment.Fields[0].Value = message
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Phase != PhaseUnknown {
|
||||||
|
mainAttachment.Fields = append(mainAttachment.Fields, SlackField{
|
||||||
|
Title: phaseFieldName,
|
||||||
|
Value: string(event.Phase),
|
||||||
|
Short: true,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
slackMessage, err := json.Marshal(sm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
secretValue := string(secret.Data[selector.Key])
|
secretValue := string(secret.Data[selector.Key])
|
||||||
if secretValue == "" {
|
if secretValue == "" {
|
||||||
return errors.Errorf("SecretValue %s is empty", selector.Name)
|
return errors.Errorf("Secret with given name `%s` and selector name `%s` is empty", secret.Name, selector.Name)
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
request, err := http.NewRequest("POST", secretValue, bytes.NewBuffer(slackMessage))
|
request, err := http.NewRequest("POST", secretValue, bytes.NewBuffer(slackMessage))
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ func TestSlack_Send(t *testing.T) {
|
||||||
Namespace: testNamespace,
|
Namespace: testNamespace,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ConfigurationType: testConfigurationType,
|
Phase: testPhase,
|
||||||
Message: testMessage,
|
Message: testMessage,
|
||||||
MessageVerbose: testMessageVerbose,
|
MessagesVerbose: testMessageVerbose,
|
||||||
LogLevel: testLoggingLevel,
|
LogLevel: testLoggingLevel,
|
||||||
}
|
}
|
||||||
slack := Slack{k8sClient: fakeClient}
|
slack := Slack{k8sClient: fakeClient}
|
||||||
|
|
@ -45,11 +45,11 @@ func TestSlack_Send(t *testing.T) {
|
||||||
|
|
||||||
mainAttachment := message.Attachments[0]
|
mainAttachment := message.Attachments[0]
|
||||||
|
|
||||||
assert.Equal(t, mainAttachment.Text, titleText)
|
assert.Equal(t, mainAttachment.Title, notificationTitle(event))
|
||||||
for _, field := range mainAttachment.Fields {
|
for _, field := range mainAttachment.Fields {
|
||||||
switch field.Title {
|
switch field.Title {
|
||||||
case configurationTypeFieldName:
|
case phaseFieldName:
|
||||||
assert.Equal(t, field.Value, event.ConfigurationType)
|
assert.Equal(t, field.Value, string(event.Phase))
|
||||||
case crNameFieldName:
|
case crNameFieldName:
|
||||||
assert.Equal(t, field.Value, event.Jenkins.Name)
|
assert.Equal(t, field.Value, event.Jenkins.Name)
|
||||||
case messageFieldName:
|
case messageFieldName:
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/log"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -77,10 +75,10 @@ func Must(plugin *Plugin, err error) Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyDependencies checks if all plugins have compatible versions
|
// VerifyDependencies checks if all plugins have compatible versions
|
||||||
func VerifyDependencies(values ...map[Plugin][]Plugin) bool {
|
func VerifyDependencies(values ...map[Plugin][]Plugin) []string {
|
||||||
|
var messages []string
|
||||||
// key - plugin name, value array of versions
|
// key - plugin name, value array of versions
|
||||||
allPlugins := make(map[string][]Plugin)
|
allPlugins := make(map[string][]Plugin)
|
||||||
valid := true
|
|
||||||
|
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
for rootPlugin, plugins := range value {
|
for rootPlugin, plugins := range value {
|
||||||
|
|
@ -105,18 +103,17 @@ func VerifyDependencies(values ...map[Plugin][]Plugin) bool {
|
||||||
for _, firstVersion := range versions {
|
for _, firstVersion := range versions {
|
||||||
for _, secondVersion := range versions {
|
for _, secondVersion := range versions {
|
||||||
if firstVersion.Version != secondVersion.Version {
|
if firstVersion.Version != secondVersion.Version {
|
||||||
log.Log.V(log.VWarn).Info(fmt.Sprintf("Plugin '%s' requires version '%s' but plugin '%s' requires '%s' for plugin '%s'",
|
messages = append(messages, fmt.Sprintf("Plugin '%s' requires version '%s' but plugin '%s' requires '%s' for plugin '%s'",
|
||||||
firstVersion.rootPluginNameAndVersion,
|
firstVersion.rootPluginNameAndVersion,
|
||||||
firstVersion.Version,
|
firstVersion.Version,
|
||||||
secondVersion.rootPluginNameAndVersion,
|
secondVersion.rootPluginNameAndVersion,
|
||||||
secondVersion.Version,
|
secondVersion.Version,
|
||||||
pluginName,
|
pluginName,
|
||||||
))
|
))
|
||||||
valid = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return valid
|
return messages
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ func TestVerifyDependencies(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := VerifyDependencies(basePlugins)
|
got := VerifyDependencies(basePlugins)
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("happy, two root plugins with one depended plugin with the same version", func(t *testing.T) {
|
t.Run("happy, two root plugins with one depended plugin with the same version", func(t *testing.T) {
|
||||||
basePlugins := map[Plugin][]Plugin{
|
basePlugins := map[Plugin][]Plugin{
|
||||||
|
|
@ -30,7 +30,7 @@ func TestVerifyDependencies(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := VerifyDependencies(basePlugins)
|
got := VerifyDependencies(basePlugins)
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("happy, two plugin names with names with underscores", func(t *testing.T) {
|
t.Run("happy, two plugin names with names with underscores", func(t *testing.T) {
|
||||||
basePlugins := map[Plugin][]Plugin{
|
basePlugins := map[Plugin][]Plugin{
|
||||||
|
|
@ -42,7 +42,7 @@ func TestVerifyDependencies(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := VerifyDependencies(basePlugins)
|
got := VerifyDependencies(basePlugins)
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("happy, two plugin names with uppercase names", func(t *testing.T) {
|
t.Run("happy, two plugin names with uppercase names", func(t *testing.T) {
|
||||||
basePlugins := map[Plugin][]Plugin{
|
basePlugins := map[Plugin][]Plugin{
|
||||||
|
|
@ -54,7 +54,7 @@ func TestVerifyDependencies(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := VerifyDependencies(basePlugins)
|
got := VerifyDependencies(basePlugins)
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("fail, two root plugins have different versions", func(t *testing.T) {
|
t.Run("fail, two root plugins have different versions", func(t *testing.T) {
|
||||||
basePlugins := map[Plugin][]Plugin{
|
basePlugins := map[Plugin][]Plugin{
|
||||||
|
|
@ -66,7 +66,7 @@ func TestVerifyDependencies(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := VerifyDependencies(basePlugins)
|
got := VerifyDependencies(basePlugins)
|
||||||
assert.Equal(t, false, got)
|
assert.NotNil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("happy, no version collision with two sperate plugins lists", func(t *testing.T) {
|
t.Run("happy, no version collision with two sperate plugins lists", func(t *testing.T) {
|
||||||
basePlugins := map[Plugin][]Plugin{
|
basePlugins := map[Plugin][]Plugin{
|
||||||
|
|
@ -80,7 +80,7 @@ func TestVerifyDependencies(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := VerifyDependencies(basePlugins, extraPlugins)
|
got := VerifyDependencies(basePlugins, extraPlugins)
|
||||||
assert.Equal(t, true, got)
|
assert.Nil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("fail, dependent plugins have different versions", func(t *testing.T) {
|
t.Run("fail, dependent plugins have different versions", func(t *testing.T) {
|
||||||
basePlugins := map[Plugin][]Plugin{
|
basePlugins := map[Plugin][]Plugin{
|
||||||
|
|
@ -92,7 +92,7 @@ func TestVerifyDependencies(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := VerifyDependencies(basePlugins)
|
got := VerifyDependencies(basePlugins)
|
||||||
assert.Equal(t, false, got)
|
assert.NotNil(t, got)
|
||||||
})
|
})
|
||||||
t.Run("fail, root and dependent plugins have different versions", func(t *testing.T) {
|
t.Run("fail, root and dependent plugins have different versions", func(t *testing.T) {
|
||||||
basePlugins := map[Plugin][]Plugin{
|
basePlugins := map[Plugin][]Plugin{
|
||||||
|
|
@ -106,6 +106,6 @@ func TestVerifyDependencies(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
got := VerifyDependencies(basePlugins, extraPlugins)
|
got := VerifyDependencies(basePlugins, extraPlugins)
|
||||||
assert.Equal(t, false, got)
|
assert.NotNil(t, got)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue