Merge f6acbfa749 into 08f0a74771
This commit is contained in:
commit
4d1ecf97ee
|
|
@ -73,6 +73,21 @@ type JenkinsSpec struct {
|
||||||
|
|
||||||
// JenkinsAPISettings defines configuration used by the operator to gain admin access to the Jenkins API
|
// JenkinsAPISettings defines configuration used by the operator to gain admin access to the Jenkins API
|
||||||
JenkinsAPISettings JenkinsAPISettings `json:"jenkinsAPISettings"`
|
JenkinsAPISettings JenkinsAPISettings `json:"jenkinsAPISettings"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
Lifecycle JenkinsLifecycle `json:"lifecycle,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JenkinsLifecycle struct {
|
||||||
|
// +optional
|
||||||
|
Ignore JenkinsLifecycleIgnore `json:"ignore,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JenkinsLifecycleIgnore struct {
|
||||||
|
IgnoredVolumes []string `json:"volumes,omitempty"`
|
||||||
|
IgnoredEnvs []string `json:"envs,omitempty"`
|
||||||
|
IgnoredAnnotations []string `json:"annotations,omitempty"`
|
||||||
|
IgnoredLabels []string `json:"labels,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizationStrategy defines authorization strategy of the operator for the Jenkins API
|
// AuthorizationStrategy defines authorization strategy of the operator for the Jenkins API
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
|
"github.com/jenkinsci/kubernetes-operator/api/v1alpha2"
|
||||||
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/backuprestore"
|
"github.com/jenkinsci/kubernetes-operator/pkg/configuration/backuprestore"
|
||||||
|
|
@ -82,7 +83,7 @@ func (r *JenkinsBaseConfigurationReconciler) checkForPodRecreation(currentJenkin
|
||||||
currentJenkinsMasterPod.Labels, r.Configuration.Jenkins.Spec.Master.Labels))
|
currentJenkinsMasterPod.Labels, r.Configuration.Jenkins.Spec.Master.Labels))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !compareMap(r.Configuration.Jenkins.Spec.Master.Annotations, currentJenkinsMasterPod.ObjectMeta.Annotations) {
|
if !r.compareAnnotations(currentJenkinsMasterPod) {
|
||||||
messages = append(messages, "Jenkins pod annotations have changed")
|
messages = append(messages, "Jenkins pod annotations have changed")
|
||||||
verbose = append(verbose, fmt.Sprintf("Jenkins pod annotations have changed, actual '%+v' required '%+v'",
|
verbose = append(verbose, fmt.Sprintf("Jenkins pod annotations have changed, actual '%+v' required '%+v'",
|
||||||
currentJenkinsMasterPod.ObjectMeta.Annotations, r.Configuration.Jenkins.Spec.Master.Annotations))
|
currentJenkinsMasterPod.ObjectMeta.Annotations, r.Configuration.Jenkins.Spec.Master.Annotations))
|
||||||
|
|
@ -146,6 +147,20 @@ func (r *JenkinsBaseConfigurationReconciler) checkForPodRecreation(currentJenkin
|
||||||
return reason.NewPodRestart(reason.OperatorSource, messages, verbose...)
|
return reason.NewPodRestart(reason.OperatorSource, messages, verbose...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *JenkinsBaseConfigurationReconciler) compareAnnotations(currentJenkinsMasterPod corev1.Pod) bool {
|
||||||
|
ignoredAnnotations := r.Jenkins.Spec.Lifecycle.Ignore.IgnoredAnnotations
|
||||||
|
annotations := r.Configuration.Jenkins.Spec.Master.Annotations
|
||||||
|
|
||||||
|
res := make(map[string]string)
|
||||||
|
for key, val := range annotations {
|
||||||
|
if slices.Contains(ignoredAnnotations, key) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res[key] = val
|
||||||
|
}
|
||||||
|
return compareMap(res, currentJenkinsMasterPod.ObjectMeta.Annotations)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *JenkinsBaseConfigurationReconciler) ensureJenkinsMasterPod(meta metav1.ObjectMeta) (reconcile.Result, error) {
|
func (r *JenkinsBaseConfigurationReconciler) ensureJenkinsMasterPod(meta metav1.ObjectMeta) (reconcile.Result, error) {
|
||||||
userAndPasswordHash, err := r.calculateUserAndPasswordHash()
|
userAndPasswordHash, err := r.calculateUserAndPasswordHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,114 @@ func TestCompareContainerVolumeMounts(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompareAnnotations(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
jenkinsAnnotations map[string]string
|
||||||
|
ignoredAnnotations []string
|
||||||
|
podAnnotations map[string]string
|
||||||
|
expectedShouldMatch bool
|
||||||
|
}
|
||||||
|
|
||||||
|
runTest := func(t *testing.T, tc testCase) {
|
||||||
|
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
jenkins := &v1alpha2.Jenkins{
|
||||||
|
Spec: v1alpha2.JenkinsSpec{
|
||||||
|
Master: v1alpha2.JenkinsMaster{
|
||||||
|
Annotations: tc.jenkinsAnnotations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tc.ignoredAnnotations) > 0 {
|
||||||
|
jenkins.Spec.Lifecycle = v1alpha2.JenkinsLifecycle{
|
||||||
|
Ignore: v1alpha2.JenkinsLifecycleIgnore{
|
||||||
|
IgnoredAnnotations: tc.ignoredAnnotations,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pod := corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: tc.podAnnotations,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
reconciler := New(configuration.Configuration{Jenkins: jenkins}, client.JenkinsAPIConnectionSettings{})
|
||||||
|
result := reconciler.compareAnnotations(pod)
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectedShouldMatch, result,
|
||||||
|
"Expected compareAnnotations to return %v but got %v", tc.expectedShouldMatch, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []testCase{
|
||||||
|
{
|
||||||
|
name: "no annotation - additional annotations - not different",
|
||||||
|
jenkinsAnnotations: map[string]string{},
|
||||||
|
ignoredAnnotations: nil,
|
||||||
|
podAnnotations: map[string]string{"one": "two"},
|
||||||
|
expectedShouldMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one additional annotation - change not ignored - not different",
|
||||||
|
jenkinsAnnotations: map[string]string{"one": "two"},
|
||||||
|
ignoredAnnotations: nil,
|
||||||
|
podAnnotations: map[string]string{"one": "two", "additional": "annotation"},
|
||||||
|
expectedShouldMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
name: "annotations different - different",
|
||||||
|
jenkinsAnnotations: map[string]string{"one": "two"},
|
||||||
|
ignoredAnnotations: nil,
|
||||||
|
podAnnotations: map[string]string{"two": "three"},
|
||||||
|
expectedShouldMatch: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "annotations different - ignored - not different",
|
||||||
|
jenkinsAnnotations: map[string]string{"one": "two"},
|
||||||
|
ignoredAnnotations: []string{"one"},
|
||||||
|
podAnnotations: map[string]string{"two": "three"},
|
||||||
|
expectedShouldMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one annotation different - change not ignored - different",
|
||||||
|
jenkinsAnnotations: map[string]string{"one": "two"},
|
||||||
|
ignoredAnnotations: nil,
|
||||||
|
podAnnotations: map[string]string{"one": "different"},
|
||||||
|
expectedShouldMatch: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one annotation different - change ignored - not different",
|
||||||
|
jenkinsAnnotations: map[string]string{"one": "two"},
|
||||||
|
ignoredAnnotations: []string{"one"},
|
||||||
|
podAnnotations: map[string]string{"one": "different"},
|
||||||
|
expectedShouldMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one additional annotation - different",
|
||||||
|
jenkinsAnnotations: map[string]string{"one": "two", "ignore": "me"},
|
||||||
|
ignoredAnnotations: nil,
|
||||||
|
podAnnotations: map[string]string{"one": "two", "ignore": "this"},
|
||||||
|
expectedShouldMatch: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one additional annotation - change ignored - not different",
|
||||||
|
jenkinsAnnotations: map[string]string{"one": "two", "ignore": "me"},
|
||||||
|
ignoredAnnotations: []string{"ignore"},
|
||||||
|
podAnnotations: map[string]string{"one": "two", "ignore": "this"},
|
||||||
|
expectedShouldMatch: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
runTest(t, tc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCompareVolumes(t *testing.T) {
|
func TestCompareVolumes(t *testing.T) {
|
||||||
t.Run("defaults", func(t *testing.T) {
|
t.Run("defaults", func(t *testing.T) {
|
||||||
jenkins := &v1alpha2.Jenkins{}
|
jenkins := &v1alpha2.Jenkins{}
|
||||||
|
|
@ -160,6 +268,74 @@ func TestCompareVolumes(t *testing.T) {
|
||||||
|
|
||||||
assert.True(t, got)
|
assert.True(t, got)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("different - additional workspace identity volume", func(t *testing.T) {
|
||||||
|
jenkins := &v1alpha2.Jenkins{
|
||||||
|
Spec: v1alpha2.JenkinsSpec{
|
||||||
|
Master: v1alpha2.JenkinsMaster{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod := corev1.Pod{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
ServiceAccountName: "service-account-name",
|
||||||
|
Volumes: append(resources.GetJenkinsMasterPodBaseVolumes(jenkins), corev1.Volume{Name: "azure-identity-token"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reconciler := New(configuration.Configuration{Jenkins: jenkins}, client.JenkinsAPIConnectionSettings{})
|
||||||
|
|
||||||
|
got := reconciler.compareVolumes(pod)
|
||||||
|
|
||||||
|
assert.False(t, got)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("additional workspace identity volume but ignored", func(t *testing.T) {
|
||||||
|
jenkins := &v1alpha2.Jenkins{
|
||||||
|
|
||||||
|
Spec: v1alpha2.JenkinsSpec{
|
||||||
|
Lifecycle: v1alpha2.JenkinsLifecycle{
|
||||||
|
Ignore: v1alpha2.JenkinsLifecycleIgnore{
|
||||||
|
IgnoredVolumes: []string{"azure-identity-token"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod := corev1.Pod{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
ServiceAccountName: "service-account-name",
|
||||||
|
Volumes: append(resources.GetJenkinsMasterPodBaseVolumes(jenkins), corev1.Volume{Name: "azure-identity-token"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reconciler := New(configuration.Configuration{Jenkins: jenkins}, client.JenkinsAPIConnectionSettings{})
|
||||||
|
|
||||||
|
got := reconciler.compareVolumes(pod)
|
||||||
|
|
||||||
|
assert.True(t, got)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("additional multiple volumes added but ignored", func(t *testing.T) {
|
||||||
|
jenkins := &v1alpha2.Jenkins{
|
||||||
|
|
||||||
|
Spec: v1alpha2.JenkinsSpec{
|
||||||
|
Lifecycle: v1alpha2.JenkinsLifecycle{
|
||||||
|
Ignore: v1alpha2.JenkinsLifecycleIgnore{
|
||||||
|
IgnoredVolumes: []string{"volume-present", "volume-absent"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pod := corev1.Pod{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
ServiceAccountName: "service-account-name",
|
||||||
|
Volumes: append(resources.GetJenkinsMasterPodBaseVolumes(jenkins), corev1.Volume{Name: "volume-present"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reconciler := New(configuration.Configuration{Jenkins: jenkins}, client.JenkinsAPIConnectionSettings{})
|
||||||
|
|
||||||
|
got := reconciler.compareVolumes(pod)
|
||||||
|
|
||||||
|
assert.True(t, got)
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJenkinsBaseConfigurationReconciler_verifyPlugins(t *testing.T) {
|
func TestJenkinsBaseConfigurationReconciler_verifyPlugins(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -294,6 +294,11 @@ func CompareContainerVolumeMounts(expected corev1.Container, actual corev1.Conta
|
||||||
func (r *JenkinsBaseConfigurationReconciler) compareVolumes(actualPod corev1.Pod) bool {
|
func (r *JenkinsBaseConfigurationReconciler) compareVolumes(actualPod corev1.Pod) bool {
|
||||||
var toCompare []corev1.Volume
|
var toCompare []corev1.Volume
|
||||||
for _, volume := range actualPod.Spec.Volumes {
|
for _, volume := range actualPod.Spec.Volumes {
|
||||||
|
|
||||||
|
if r.isVolumeIgnored(volume.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// filter out service account
|
// filter out service account
|
||||||
if strings.HasPrefix(volume.Name, actualPod.Spec.ServiceAccountName) {
|
if strings.HasPrefix(volume.Name, actualPod.Spec.ServiceAccountName) {
|
||||||
continue
|
continue
|
||||||
|
|
@ -421,3 +426,13 @@ func (r *JenkinsBaseConfigurationReconciler) ensureBaseConfiguration(jenkinsClie
|
||||||
})
|
})
|
||||||
return reconcile.Result{Requeue: requeue}, err
|
return reconcile.Result{Requeue: requeue}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isVolumeIgnored checks if the given volume name is in the list of ignored volumes
|
||||||
|
func (r *JenkinsBaseConfigurationReconciler) isVolumeIgnored(volumeName string) bool {
|
||||||
|
for _, ignoredVolume := range r.Jenkins.Spec.Lifecycle.Ignore.IgnoredVolumes {
|
||||||
|
if ignoredVolume == volumeName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue