Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/v1alpha1/lavinmq_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type LavinMQSpec struct {
// +optional
Image string `json:"image,omitempty"`

// +optional
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`

// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=3
// +kubebuilder:default=1
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions config/crd/bases/cloudamqp.com_lavinmqs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,25 @@ spec:
image:
default: cloudamqp/lavinmq:2.4.1
type: string
imagePullSecrets:
items:
description: |-
LocalObjectReference contains enough information to let you locate the
referenced object inside the same namespace.
properties:
name:
default: ""
description: |-
Name of the referent.
This field is effectively required, but due to backwards compatibility is
allowed to be empty. Instances of this type with an empty value here are
almost certainly wrong.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
type: string
type: object
x-kubernetes-map-type: atomic
type: array
Comment thread
erkki marked this conversation as resolved.
x-kubernetes-list-type: atomic
nodeSelector:
additionalProperties:
type: string
Expand Down
9 changes: 9 additions & 0 deletions internal/reconciler/statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,10 @@ func (b *StatefulSetReconciler) appendSpec(sts *appsv1.StatefulSet) *appsv1.Stat
},
}

if b.Instance.Spec.ImagePullSecrets != nil {
sts.Spec.Template.Spec.ImagePullSecrets = b.Instance.Spec.ImagePullSecrets
}
Comment thread
erkki marked this conversation as resolved.
Comment thread
erkki marked this conversation as resolved.

return sts
}
func (b *StatefulSetReconciler) portsFromSpec() []corev1.ContainerPort {
Expand Down Expand Up @@ -356,6 +360,11 @@ func (b *StatefulSetReconciler) diffTemplate(old *corev1.PodSpec) {
old.NodeSelector = b.Instance.Spec.NodeSelector
}

if !reflect.DeepEqual(old.ImagePullSecrets, b.Instance.Spec.ImagePullSecrets) {
b.Logger.Info("imagePullSecrets changed, updating")
old.ImagePullSecrets = b.Instance.Spec.ImagePullSecrets
}

index := slices.IndexFunc(old.Volumes, func(v corev1.Volume) bool {
return v.Name == "tls"
})
Expand Down
58 changes: 58 additions & 0 deletions internal/reconciler/statefulset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,61 @@ func deleteConfigMap(t *testing.T, configMap *corev1.ConfigMap) {
err := k8sClient.Delete(t.Context(), configMap)
assert.NoErrorf(t, err, "Failed to delete ConfigMap")
}

func TestImagePullSecrets(t *testing.T) {
t.Parallel()
instance := testutils.GetDefaultInstance(&testutils.DefaultInstanceSettings{})
instance.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: "registry-creds"}}

err := testutils.CreateNamespace(t.Context(), k8sClient, instance.Namespace)
assert.NoErrorf(t, err, "Failed to create namespace")
defer testutils.DeleteNamespace(t.Context(), k8sClient, instance.Namespace)

Comment thread
erkki marked this conversation as resolved.
Outdated
configMap := createConfigMap(t, instance)
defer deleteConfigMap(t, configMap)

rc := &reconciler.StatefulSetReconciler{
ResourceReconciler: &reconciler.ResourceReconciler{
Instance: instance,
Scheme: scheme.Scheme,
Client: k8sClient,
},
}

err = k8sClient.Create(t.Context(), instance)
assert.NoErrorf(t, err, "Failed to create instance")

// Created StatefulSet carries the pull secrets
_, err = rc.Reconcile(t.Context())
assert.NoErrorf(t, err, "Failed to reconcile instance")

sts := &appsv1.StatefulSet{}
stsName := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}
err = k8sClient.Get(t.Context(), stsName, sts)
assert.NoErrorf(t, err, "Failed to get statefulset")
assert.Equal(t, []corev1.LocalObjectReference{{Name: "registry-creds"}}, sts.Spec.Template.Spec.ImagePullSecrets)

// Changing the secret propagates to the existing StatefulSet
instance.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: "other-creds"}}
err = k8sClient.Update(t.Context(), instance)
assert.NoErrorf(t, err, "Failed to update instance")

_, err = rc.Reconcile(t.Context())
assert.NoErrorf(t, err, "Failed to reconcile instance")

err = k8sClient.Get(t.Context(), stsName, sts)
assert.NoErrorf(t, err, "Failed to get statefulset")
assert.Equal(t, []corev1.LocalObjectReference{{Name: "other-creds"}}, sts.Spec.Template.Spec.ImagePullSecrets)

// Removing the secrets clears them from the StatefulSet
instance.Spec.ImagePullSecrets = nil
err = k8sClient.Update(t.Context(), instance)
assert.NoErrorf(t, err, "Failed to update instance")

_, err = rc.Reconcile(t.Context())
assert.NoErrorf(t, err, "Failed to reconcile instance")

err = k8sClient.Get(t.Context(), stsName, sts)
assert.NoErrorf(t, err, "Failed to get statefulset")
assert.Empty(t, sts.Spec.Template.Spec.ImagePullSecrets)
}