Skip to content

Commit

Permalink
imagepullSecrets to access private repos
Browse files Browse the repository at this point in the history
Signed-off-by: Eguzki Astiz Lezaun <[email protected]>
  • Loading branch information
eguzki committed Sep 27, 2024
1 parent 997b476 commit 42236a8
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 30 deletions.
11 changes: 8 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -234,14 +234,16 @@ clean-cov: ## Remove coverage reports
.PHONY: test
test: test-unit test-integration ## Run all tests

ifdef VERBOSE
test-integration: VERBOSE_FLAG = -v
endif
test-integration: clean-cov generate fmt vet ginkgo ## Run Integration tests.
mkdir -p $(PROJECT_PATH)/coverage/integration
# Check `ginkgo help run` for command line options. For example to filtering tests.
$(GINKGO) \
$(GINKGO) $(VERBOSE_FLAG) \
--coverpkg $(INTEGRATION_COVER_PKGS) \
--output-dir $(PROJECT_PATH)/coverage/integration \
--coverprofile cover.out \
-v \
--compilers=$(INTEGRATION_TEST_NUM_CORES) \
--procs=$(INTEGRATION_TEST_NUM_PROCESSES) \
--randomize-all \
Expand All @@ -255,9 +257,12 @@ test-integration: clean-cov generate fmt vet ginkgo ## Run Integration tests.
ifdef TEST_NAME
test-unit: TEST_PATTERN := --run $(TEST_NAME)
endif
ifdef VERBOSE
test-unit: VERBOSE_FLAG = -v
endif
test-unit: clean-cov generate fmt vet ## Run Unit tests.
mkdir -p $(PROJECT_PATH)/coverage/unit
go test $(UNIT_DIRS) -coverprofile $(PROJECT_PATH)/coverage/unit/cover.out -v -timeout 0 $(TEST_PATTERN)
go test $(UNIT_DIRS) -coverprofile $(PROJECT_PATH)/coverage/unit/cover.out $(VERBOSE_FLAG) -timeout 0 $(TEST_PATTERN)

##@ Build
build: GIT_SHA=$(shell git rev-parse HEAD || echo "unknown")
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.

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ metadata:
capabilities: Basic Install
categories: Integration & Delivery
containerImage: quay.io/kuadrant/limitador-operator:latest
createdAt: "2024-09-24T13:57:26Z"
createdAt: "2024-09-27T10:13:26Z"
operators.operatorframework.io/builder: operator-sdk-v1.32.0
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
repository: https://github.com/Kuadrant/limitador-operator
Expand Down
15 changes: 15 additions & 0 deletions bundle/manifests/limitador.kuadrant.io_limitadors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,21 @@ spec:
type: object
image:
type: string
imagePullSecrets:
items:
description: |-
LocalObjectReference contains enough information to let you locate the
referenced object inside the same namespace.
properties:
name:
description: |-
Name of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?
type: string
type: object
x-kubernetes-map-type: atomic
type: array
limits:
items:
description: RateLimit defines the desired Limitador limit
Expand Down
15 changes: 15 additions & 0 deletions config/crd/bases/limitador.kuadrant.io_limitadors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,21 @@ spec:
type: object
image:
type: string
imagePullSecrets:
items:
description: |-
LocalObjectReference contains enough information to let you locate the
referenced object inside the same namespace.
properties:
name:
description: |-
Name of the referent.
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
TODO: Add other useful fields. apiVersion, kind, uid?
type: string
type: object
x-kubernetes-map-type: atomic
type: array
limits:
items:
description: RateLimit defines the desired Limitador limit
Expand Down
30 changes: 21 additions & 9 deletions controllers/limitador_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (

"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
Expand Down Expand Up @@ -137,7 +137,7 @@ func (r *LimitadorReconciler) reconcileSpec(ctx context.Context, limitadorObj *l
}

func (r *LimitadorReconciler) reconcilePodLimitsHashAnnotation(ctx context.Context, limitadorObj *limitadorv1alpha1.Limitador) (ctrl.Result, error) {
podList := &v1.PodList{}
podList := &corev1.PodList{}
options := &client.ListOptions{
LabelSelector: labels.SelectorFromSet(limitador.Labels(limitadorObj)),
Namespace: limitadorObj.Namespace,
Expand All @@ -156,7 +156,7 @@ func (r *LimitadorReconciler) reconcilePodLimitsHashAnnotation(ctx context.Conte
}

// Use CM resource version to track limits changes
cm := &v1.ConfigMap{}
cm := &corev1.ConfigMap{}
if err := r.Client().Get(ctx, types.NamespacedName{Name: limitador.LimitsConfigMapName(limitadorObj), Namespace: limitadorObj.Namespace}, cm); err != nil {
if apierrors.IsNotFound(err) {
return ctrl.Result{Requeue: true}, nil
Expand Down Expand Up @@ -236,6 +236,13 @@ func (r *LimitadorReconciler) reconcileDeployment(ctx context.Context, limitador
reconcilers.DeploymentReadinessProbeMutator,
)

// reconcile imagepullsecrets only when set in limitador CR
// if not set in limitador CR, the user will be able to add them manually and the operator
// will not revert the changes.
if len(deploymentOptions.ImagePullSecrets) > 0 {
deploymentMutators = append(deploymentMutators, reconcilers.DeploymentImagePullSecretsMutator)
}

deployment := limitador.Deployment(limitadorObj, deploymentOptions)
// controller reference
if err := r.SetOwnerReference(limitadorObj, deployment); err != nil {
Expand Down Expand Up @@ -327,13 +334,13 @@ func (r *LimitadorReconciler) reconcileLimitsConfigMap(ctx context.Context, limi
}

func mutateLimitsConfigMap(existingObj, desiredObj client.Object) (bool, error) {
existing, ok := existingObj.(*v1.ConfigMap)
existing, ok := existingObj.(*corev1.ConfigMap)
if !ok {
return false, fmt.Errorf("%T is not a *v1.ConfigMap", existingObj)
return false, fmt.Errorf("%T is not a *corev1.ConfigMap", existingObj)
}
desired, ok := desiredObj.(*v1.ConfigMap)
desired, ok := desiredObj.(*corev1.ConfigMap)
if !ok {
return false, fmt.Errorf("%T is not a *v1.ConfigMap", desiredObj)
return false, fmt.Errorf("%T is not a *corev1.ConfigMap", desiredObj)
}

updated := false
Expand Down Expand Up @@ -378,6 +385,7 @@ func (r *LimitadorReconciler) getDeploymentOptions(ctx context.Context, limObj *
if err != nil {
return deploymentOptions, err
}
deploymentOptions.ImagePullSecrets = r.getDeploymentImagePullSecrets(limObj)

return deploymentOptions, nil
}
Expand All @@ -402,7 +410,7 @@ func (r *LimitadorReconciler) getDeploymentStorageOptions(ctx context.Context, l
return limitador.InMemoryDeploymentOptions()
}

func (r *LimitadorReconciler) getDeploymentEnvVar(limObj *limitadorv1alpha1.Limitador) ([]v1.EnvVar, error) {
func (r *LimitadorReconciler) getDeploymentEnvVar(limObj *limitadorv1alpha1.Limitador) ([]corev1.EnvVar, error) {
if limObj.Spec.Storage != nil {
if limObj.Spec.Storage.Redis != nil {
return limitador.DeploymentEnvVar(limObj.Spec.Storage.Redis.ConfigSecretRef)
Expand All @@ -416,12 +424,16 @@ func (r *LimitadorReconciler) getDeploymentEnvVar(limObj *limitadorv1alpha1.Limi
return nil, nil
}

func (r *LimitadorReconciler) getDeploymentImagePullSecrets(limObj *limitadorv1alpha1.Limitador) []corev1.LocalObjectReference {
return limObj.Spec.ImagePullSecrets
}

// SetupWithManager sets up the controller with the Manager.
func (r *LimitadorReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&limitadorv1alpha1.Limitador{}).
Owns(&appsv1.Deployment{}).
Owns(&v1.ConfigMap{}).
Owns(&corev1.ConfigMap{}).
Owns(&policyv1.PodDisruptionBudget{}).
Complete(r)
}
27 changes: 27 additions & 0 deletions controllers/limitador_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,33 @@ var _ = Describe("Limitador controller", func() {
Expect(pvc.GetOwnerReferences()).To(HaveLen(1))
}, specTimeOut)
})

Context("Creating a new Limitador object with imagePullSecrets", func() {
var limitadorObj *limitadorv1alpha1.Limitador

BeforeEach(func(ctx SpecContext) {
limitadorObj = basicLimitador(testNamespace)
limitadorObj.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: "regcred"}}

Expect(k8sClient.Create(ctx, limitadorObj)).Should(Succeed())
Eventually(testLimitadorIsReady(ctx, limitadorObj)).WithContext(ctx).Should(Succeed())
})

It("Should create a new deployment with imagepullsecrets", func(ctx SpecContext) {
deployment := &appsv1.Deployment{}
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(ctx,
types.NamespacedName{
Namespace: testNamespace,
Name: limitador.DeploymentName(limitadorObj),
}, deployment)).To(Succeed())
}).WithContext(ctx).Should(Succeed())

Expect(deployment.Spec.Template.Spec.ImagePullSecrets).To(
HaveExactElements(corev1.LocalObjectReference{Name: "regcred"}),
)
}, specTimeOut)
})
})

func basicLimitador(ns string) *limitadorv1alpha1.Limitador {
Expand Down
10 changes: 7 additions & 3 deletions doc/custom-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ EOF

To pull an image from a private container image registry or repository, you need to provide credentials.

Create a Secret by providing credentials on the command line
Create a Secret of type `kubernetes.io/dockerconfigjson` by providing credentials.
For example, using `kubectl` tool with the following command line:

```
kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword> --docker-email=<your-email>
kubectl create secret docker-registry regcred --docker-server=<your-registry-server> --docker-username=<your-name> --docker-password=<your-pword>
```

Deploy limitador instance that uses your secret
That will create a secret named `regcred`.

Deploy limitador instance with the `imagePullSecrets` field having a reference to the `regcred`.

```yaml
---
Expand All @@ -47,3 +49,5 @@ spec:
imagePullSecrets:
- name: regcred
```
> **NOTE**: It is mandatory that the secret and limitador CR are created in the same namespace.
27 changes: 14 additions & 13 deletions pkg/limitador/deployment_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@ import (
"strings"

appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"

limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1"
)

type DeploymentOptions struct {
Command []string
VolumeMounts []v1.VolumeMount
Volumes []v1.Volume
VolumeMounts []corev1.VolumeMount
Volumes []corev1.Volume
DeploymentStrategy appsv1.DeploymentStrategy
EnvVar []v1.EnvVar
EnvVar []corev1.EnvVar
ImagePullSecrets []corev1.LocalObjectReference
}

type DeploymentStorageOptions struct {
Command []string
VolumeMounts []v1.VolumeMount
Volumes []v1.Volume
VolumeMounts []corev1.VolumeMount
Volumes []corev1.Volume
DeploymentStrategy appsv1.DeploymentStrategy
}

Expand Down Expand Up @@ -67,8 +68,8 @@ func DeploymentCommand(limObj *limitadorv1alpha1.Limitador, storageOptions Deplo
return command
}

func DeploymentVolumeMounts(storageOptions DeploymentStorageOptions) []v1.VolumeMount {
volumeMounts := []v1.VolumeMount{
func DeploymentVolumeMounts(storageOptions DeploymentStorageOptions) []corev1.VolumeMount {
volumeMounts := []corev1.VolumeMount{
{
Name: LimitsCMVolumeName,
MountPath: LimitadorCMMountPath,
Expand All @@ -78,13 +79,13 @@ func DeploymentVolumeMounts(storageOptions DeploymentStorageOptions) []v1.Volume
return volumeMounts
}

func DeploymentVolumes(limObj *limitadorv1alpha1.Limitador, storageOptions DeploymentStorageOptions) []v1.Volume {
volumes := []v1.Volume{
func DeploymentVolumes(limObj *limitadorv1alpha1.Limitador, storageOptions DeploymentStorageOptions) []corev1.Volume {
volumes := []corev1.Volume{
{
Name: LimitsCMVolumeName,
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: LimitsConfigMapName(limObj),
},
},
Expand Down
3 changes: 2 additions & 1 deletion pkg/limitador/k8s_objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ func Deployment(limitador *limitadorv1alpha1.Limitador, deploymentOptions Deploy
Labels: Labels(limitador),
},
Spec: v1.PodSpec{
Affinity: limitador.Spec.Affinity,
Affinity: limitador.Spec.Affinity,
ImagePullSecrets: deploymentOptions.ImagePullSecrets,
Containers: []v1.Container{
{
Name: "limitador",
Expand Down
12 changes: 12 additions & 0 deletions pkg/limitador/k8s_objects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"gotest.tools/assert"
corev1 "k8s.io/api/core/v1"

Check failure on line 7 in pkg/limitador/k8s_objects_test.go

View workflow job for this annotation

GitHub Actions / Lint

ST1019: package "k8s.io/api/core/v1" is being imported more than once (stylecheck)
v1 "k8s.io/api/core/v1"

Check failure on line 8 in pkg/limitador/k8s_objects_test.go

View workflow job for this annotation

GitHub Actions / Lint

ST1019(related information): other import of "k8s.io/api/core/v1" (stylecheck)
policyv1 "k8s.io/api/policy/v1"
"k8s.io/apimachinery/pkg/api/resource"
Expand Down Expand Up @@ -192,6 +193,17 @@ func TestDeployment(t *testing.T) {
},
)
})

t.Run("imagePullSecrets", func(subT *testing.T) {
limObj := newTestLimitadorObj("some-name", "some-ns", nil)
deployment := Deployment(limObj, DeploymentOptions{
ImagePullSecrets: []corev1.LocalObjectReference{{Name: "regcred"}},
})

assert.DeepEqual(subT, deployment.Spec.Template.Spec.ImagePullSecrets,
[]corev1.LocalObjectReference{{Name: "regcred"}},
)
})
}

func TestPodDisruptionBudgetName(t *testing.T) {
Expand Down
20 changes: 20 additions & 0 deletions pkg/reconcilers/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import (
"fmt"
"reflect"

"github.com/google/go-cmp/cmp"
appsv1 "k8s.io/api/apps/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/kuadrant/limitador-operator/pkg/log"
)

// DeploymentMutateFn is a function which mutates the existing Deployment into it's desired state.
Expand Down Expand Up @@ -189,3 +192,20 @@ func DeploymentReadinessProbeMutator(desired, existing *appsv1.Deployment) bool

return update
}

func DeploymentImagePullSecretsMutator(desired, existing *appsv1.Deployment) bool {
logger := log.Log

if cmp.Equal(desired.Spec.Template.Spec.ImagePullSecrets, existing.Spec.Template.Spec.ImagePullSecrets) {
return false
}

if logger.V(1).Enabled() {
diff := cmp.Diff(desired.Spec.Template.Spec.ImagePullSecrets, existing.Spec.Template.Spec.ImagePullSecrets)
logger.V(1).Info("imagepullsecrets not equal", "difference", diff)
}

// indeed, update the deployment
existing.Spec.Template.Spec.ImagePullSecrets = desired.Spec.Template.Spec.ImagePullSecrets
return true
}

0 comments on commit 42236a8

Please sign in to comment.