Skip to content

Commit

Permalink
Merge pull request #160 from Kuadrant/78-production-ready-configure-p…
Browse files Browse the repository at this point in the history
…ossible-different-image

pull an image from a private registry
  • Loading branch information
eguzki authored Oct 1, 2024
2 parents de60fb3 + 347991e commit 41baa87
Show file tree
Hide file tree
Showing 14 changed files with 217 additions and 48 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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ spec:
* [Rate Limit Headers](./doc/rate-limit-headers.md)
* [Logging](./doc/logging.md)
* [Tracing](./doc/tracing.md)
* [Custom Image](./doc/custom-image.md)
## Contributing
Expand All @@ -60,4 +61,4 @@ This software is licensed under the [Apache 2.0 license](https://www.apache.org/
See the LICENSE and NOTICE files that should have been provided along with this software for details.
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FKuadrant%2Flimitador-operator.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FKuadrant%2Flimitador-operator?ref=badge_large)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FKuadrant%2Flimitador-operator.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2FKuadrant%2Flimitador-operator?ref=badge_large)
3 changes: 3 additions & 0 deletions api/v1alpha1/limitador_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ type LimitadorSpec struct {

// +optional
Image *string `json:"image,omitempty"`

// +optional
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,15,rep,name=imagePullSecrets"`
}

//+kubebuilder:object:root=true
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
53 changes: 53 additions & 0 deletions doc/custom-image.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Custom Image

Currently, the limitador image being used in the deployment is read from different sources with some order of precedence:
* If Limtador CR's `spec.image` is set -> image = `${spec.image}`
* If Limtador CR's `spec.version` is set -> image = `quay.io/kuadrant/limitador:${spec.version}` (note the repo is hardcoded)
* if `RELATED_IMAGE_LIMITADOR` env var is set -> image = `$RELATED_IMAGE_LIMITADOR`
* else: hardcoded to `quay.io/kuadrant/limitador:latest`

The `spec.image` field is not meant to be used in production environments.
It is meant to be used for dev/testing purposes.
The main drawback of the `spec.image` usage is that upgrades cannot be supported as the
limitador operator cannot ensure the operation to be safe.


```yaml
---
apiVersion: limitador.kuadrant.io/v1alpha1
kind: Limitador
metadata:
name: limitador-instance-1
spec:
image: example.com/myorg/limitador-repo:custom-image-v1
EOF
```

## Pull an Image from a Private Registry

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

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>
```

That will create a secret named `regcred`.

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

```yaml
---
apiVersion: limitador.kuadrant.io/v1alpha1
kind: Limitador
metadata:
name: limitador-instance-1
spec:
image: example.com/myorg/limitador-repo:custom-image-v1
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
Loading

0 comments on commit 41baa87

Please sign in to comment.