Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ go 1.25.0
require (
github.com/fluxcd/pkg/runtime v0.80.0
github.com/go-logr/logr v1.4.3
github.com/goccy/go-yaml v1.19.2
github.com/kylelemons/godebug v1.1.0
github.com/onsi/ginkgo/v2 v2.25.3
github.com/onsi/gomega v1.38.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.34.1
k8s.io/apimachinery v0.34.1
k8s.io/client-go v0.34.1
Expand Down Expand Up @@ -90,6 +90,7 @@ require (
google.golang.org/protobuf v1.36.7 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.34.0 // indirect
k8s.io/cli-runtime v0.33.2 // indirect
k8s.io/component-base v0.34.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZ
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
Expand Down
190 changes: 61 additions & 129 deletions internal/controllers/ratelimitservice_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import (
"crypto/sha256"
"fmt"
"slices"
"strings"

"github.com/go-logr/logr"
"gopkg.in/yaml.v3"
"github.com/goccy/go-yaml"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -318,7 +317,6 @@ func (r *RateLimitServiceReconciler) reconcile(ctx context.Context, service infr
gid int64 = 10000
uid int64 = 10000
runAsNonRoot = true
replicas int32 = 1
controllerOwner = true
labels = map[string]string{
"app.kubernetes.io/instance": "ratelimit",
Expand Down Expand Up @@ -363,33 +361,11 @@ func (r *RateLimitServiceReconciler) reconcile(ctx context.Context, service infr
}

checksum := fmt.Sprintf("%x", checksumSha.Sum(nil))

var cm corev1.ConfigMap
err = r.Get(ctx, client.ObjectKey{
Namespace: cmTemplate.Namespace,
Name: cmTemplate.Name,
}, &cm)

if err != nil && !apierrors.IsNotFound(err) {
if err := r.createOrUpdateWithOwnershipValidation(ctx, &service, cmTemplate); err != nil {
return service, ctrl.Result{}, err
}

if apierrors.IsNotFound(err) {
if err := r.Create(ctx, cmTemplate); err != nil {
return service, ctrl.Result{}, err
}
} else {
if !isOwner(&service, &cm) {
return service, ctrl.Result{}, fmt.Errorf("can not take ownership of existing configmap: %s", cm.Name)
}

mergeMetadata(&cmTemplate.ObjectMeta, cm.ObjectMeta)
if err := r.Update(ctx, cmTemplate); err != nil {
return service, ctrl.Result{}, err
}
}

template := &appsv1.Deployment{
deploymentTemplate := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("ratelimit-%s", service.Name),
Namespace: service.Namespace,
Expand Down Expand Up @@ -417,49 +393,44 @@ func (r *RateLimitServiceReconciler) reconcile(ctx context.Context, service infr
}

if service.Spec.DeploymentTemplate != nil {
template.Labels = service.Spec.DeploymentTemplate.Labels
template.Annotations = service.Spec.DeploymentTemplate.Annotations
service.Spec.DeploymentTemplate.Spec.Template.DeepCopyInto(&template.Spec.Template)
template.Spec.MinReadySeconds = service.Spec.DeploymentTemplate.Spec.MinReadySeconds
template.Spec.Paused = service.Spec.DeploymentTemplate.Spec.Paused
template.Spec.ProgressDeadlineSeconds = service.Spec.DeploymentTemplate.Spec.ProgressDeadlineSeconds
template.Spec.Replicas = service.Spec.DeploymentTemplate.Spec.Replicas
template.Spec.RevisionHistoryLimit = service.Spec.DeploymentTemplate.Spec.RevisionHistoryLimit
template.Spec.Strategy = service.Spec.DeploymentTemplate.Spec.Strategy
deploymentTemplate.Labels = service.Spec.DeploymentTemplate.Labels
deploymentTemplate.Annotations = service.Spec.DeploymentTemplate.Annotations
service.Spec.DeploymentTemplate.Spec.Template.DeepCopyInto(&deploymentTemplate.Spec.Template)
deploymentTemplate.Spec.MinReadySeconds = service.Spec.DeploymentTemplate.Spec.MinReadySeconds
deploymentTemplate.Spec.Paused = service.Spec.DeploymentTemplate.Spec.Paused
deploymentTemplate.Spec.ProgressDeadlineSeconds = service.Spec.DeploymentTemplate.Spec.ProgressDeadlineSeconds
deploymentTemplate.Spec.Replicas = service.Spec.DeploymentTemplate.Spec.Replicas
deploymentTemplate.Spec.RevisionHistoryLimit = service.Spec.DeploymentTemplate.Spec.RevisionHistoryLimit
deploymentTemplate.Spec.Strategy = service.Spec.DeploymentTemplate.Spec.Strategy
}

if template.Labels == nil {
template.Labels = make(map[string]string)
if deploymentTemplate.Labels == nil {
deploymentTemplate.Labels = make(map[string]string)
}

if template.Spec.Template.Labels == nil {
template.Spec.Template.Labels = make(map[string]string)
if deploymentTemplate.Spec.Template.Labels == nil {
deploymentTemplate.Spec.Template.Labels = make(map[string]string)
}

template.Spec.Selector = &metav1.LabelSelector{
MatchLabels: labels,
if deploymentTemplate.Annotations == nil {
deploymentTemplate.Annotations = make(map[string]string)
}

if template.Spec.Replicas == nil {
template.Spec.Replicas = &replicas
if deploymentTemplate.Spec.Template.Annotations == nil {
deploymentTemplate.Spec.Template.Annotations = make(map[string]string)
}

template.Spec.Template.Labels["app.kubernetes.io/instance"] = "ratelimit"
template.Spec.Template.Labels["app.kubernetes.io/name"] = "ratelimit"
template.Spec.Template.Labels["ratelimit-controller/service"] = service.Name
template.Labels["app.kubernetes.io/instance"] = "ratelimit"
template.Labels["app.kubernetes.io/name"] = "ratelimit"
template.Labels["ratelimit-controller/service"] = service.Name

if template.Annotations == nil {
template.Annotations = make(map[string]string)
}

if template.Spec.Template.Annotations == nil {
template.Spec.Template.Annotations = make(map[string]string)
deploymentTemplate.Spec.Selector = &metav1.LabelSelector{
MatchLabels: labels,
}

template.Spec.Template.Annotations["ratelimit-controller/sha256-checksum"] = checksum
deploymentTemplate.Spec.Template.Labels["app.kubernetes.io/instance"] = "ratelimit"
deploymentTemplate.Spec.Template.Labels["app.kubernetes.io/name"] = "ratelimit"
deploymentTemplate.Spec.Template.Labels["ratelimit-controller/service"] = service.Name
deploymentTemplate.Labels["app.kubernetes.io/instance"] = "ratelimit"
deploymentTemplate.Labels["app.kubernetes.io/name"] = "ratelimit"
deploymentTemplate.Labels["ratelimit-controller/service"] = service.Name
deploymentTemplate.Spec.Template.Annotations["ratelimit-controller/sha256-checksum"] = checksum

containers := []corev1.Container{
{
Expand Down Expand Up @@ -521,12 +492,12 @@ func (r *RateLimitServiceReconciler) reconcile(ctx context.Context, service infr
},
}

containers, err = merge.MergePatchContainers(containers, template.Spec.Template.Spec.Containers)
containers, err = merge.MergePatchContainers(containers, deploymentTemplate.Spec.Template.Spec.Containers)
if err != nil {
return service, ctrl.Result{}, err
}

template.Spec.Template.Spec.Volumes = append(template.Spec.Template.Spec.Volumes, corev1.Volume{
deploymentTemplate.Spec.Template.Spec.Volumes = append(deploymentTemplate.Spec.Template.Spec.Volumes, corev1.Volume{
Name: "config",
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
Expand All @@ -537,7 +508,7 @@ func (r *RateLimitServiceReconciler) reconcile(ctx context.Context, service infr
},
})

template.Spec.Template.Spec.Containers = containers
deploymentTemplate.Spec.Template.Spec.Containers = containers

svcTemplate := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -571,92 +542,53 @@ func (r *RateLimitServiceReconciler) reconcile(ctx context.Context, service infr
},
}

var svc corev1.Service
err = r.Get(ctx, client.ObjectKey{
Namespace: svcTemplate.Namespace,
Name: svcTemplate.Name,
}, &svc)

if err != nil && !apierrors.IsNotFound(err) {
if err := r.createOrUpdateWithOwnershipValidation(ctx, &service, svcTemplate); err != nil {
return service, ctrl.Result{}, err
}

if apierrors.IsNotFound(err) {
if err := r.Create(ctx, svcTemplate); err != nil {
return service, ctrl.Result{}, err
}
} else {
if !isOwner(&service, &cm) {
return service, ctrl.Result{}, fmt.Errorf("can not take ownership of existing service: %s", svc.Name)
}

mergeMetadata(&svcTemplate.ObjectMeta, svc.ObjectMeta)
if err := r.Update(ctx, svcTemplate); err != nil {
return service, ctrl.Result{}, err
}
if err := r.createOrUpdateWithOwnershipValidation(ctx, &service, deploymentTemplate); err != nil {
return service, ctrl.Result{}, err
}

var deployment appsv1.Deployment
err = r.Get(ctx, client.ObjectKey{
Namespace: template.Namespace,
Name: template.Name,
}, &deployment)
service = infrav1beta1.RateLimitServiceReady(service, metav1.ConditionTrue, "ReconciliationSuccessful", fmt.Sprintf("deployment/%s created", deploymentTemplate.Name))
return service, ctrl.Result{}, nil
}

func (r *RateLimitServiceReconciler) createOrUpdateWithOwnershipValidation(ctx context.Context, owner client.Object, obj client.Object) error {
existing := obj.DeepCopyObject().(client.Object)
err := r.Get(ctx, client.ObjectKey{
Namespace: obj.GetNamespace(),
Name: obj.GetName(),
}, existing)

if err != nil && !apierrors.IsNotFound(err) {
return service, ctrl.Result{}, err
return err
}

if apierrors.IsNotFound(err) {
if err := r.Create(ctx, template); err != nil {
return service, ctrl.Result{}, err
if err := r.Create(ctx, obj); err != nil {
return err
}

} else {
if !isOwner(&service, &deployment) {
return service, ctrl.Result{}, fmt.Errorf("can not take ownership of existing deployment: %s", deployment.Name)
}

mergeMetadata(&template.ObjectMeta, deployment.ObjectMeta)
if err := r.Update(ctx, template); err != nil {
return service, ctrl.Result{}, err
if !isOwner(owner, existing) {
return fmt.Errorf("can not take ownership of existing resource: %s", obj.GetName())
}
}

service = infrav1beta1.RateLimitServiceReady(service, metav1.ConditionTrue, "ReconciliationSuccessful", fmt.Sprintf("deployment/%s created", template.Name))
return service, ctrl.Result{}, nil
}

// mergeMetadata takes labels and annotations from the old resource and merges
// them into the new resource. If a key is present in both resources, the new
// resource wins. It also copies the ResourceVersion from the old resource to
// the new resource to prevent update conflicts.
func mergeMetadata(new *metav1.ObjectMeta, old metav1.ObjectMeta) {
new.ResourceVersion = old.ResourceVersion

new.SetLabels(mergeMaps(new.Labels, old.Labels))
new.SetAnnotations(mergeMaps(new.Annotations, old.Annotations))
}

func mergeMaps(new map[string]string, old map[string]string) map[string]string {
return mergeMapsByPrefix(new, old, "")
}

func mergeMapsByPrefix(from map[string]string, to map[string]string, prefix string) map[string]string {
if to == nil {
to = make(map[string]string)
}

if from == nil {
from = make(map[string]string)
}
obj.GetObjectKind().SetGroupVersionKind(existing.GetObjectKind().GroupVersionKind())
err := r.Patch(
ctx,
obj,
client.Apply,
client.FieldOwner("ratelimit-controller"),
client.ForceOwnership,
)

for k, v := range from {
if strings.HasPrefix(k, prefix) {
to[k] = v
if err != nil {
return fmt.Errorf("can not patch resource: %w", err)
}
}

return to
return nil
}

func (r *RateLimitServiceReconciler) extendserviceWithRateLimitRules(ctx context.Context, service infrav1beta1.RateLimitService) (infrav1beta1.RateLimitService, []infrav1beta1.RateLimitRule, error) {
Expand Down
Loading
Loading