Skip to content

Commit

Permalink
Proper reconciliation
Browse files Browse the repository at this point in the history
  • Loading branch information
programmer04 committed Jul 29, 2024
1 parent 23ac0a7 commit b7c5456
Show file tree
Hide file tree
Showing 16 changed files with 287 additions and 2,397 deletions.
8 changes: 7 additions & 1 deletion api/v1alpha1/kongplugin_installation_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func init() {
//+kubebuilder:subresource:status
//+kubebuilder:resource:shortName=kpi,categories=kong;all
//+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
//+kubebuilder:printcolumn:name="Accepted",description="The Resource is ready",type=string,JSONPath=`.status.conditions[?(@.type=='Accepted')].status`

// KongPluginInstallation allows using a custom Kong Plugin distributed as a container image available in a registry.
// Such a plugin can be associated with GatewayConfiguration or DataPlane to be available for particular Kong Gateway
Expand Down Expand Up @@ -77,6 +77,12 @@ type KongPluginInstallationStatus struct {
//+listMapKey=type
//+kubebuilder:validation:MaxItems=8
Conditions []metav1.Condition `json:"conditions,omitempty"`

// UnderlyingConfigMapName is the name of the ConfigMap that contains the plugin's content.
// It is set when the plugin is successfully fetched and unpacked.
//
//+optional
UnderlyingConfigMapName string `json:"underlyingConfigMapName,omitempty"`
}

// The following are KongPluginInstallation specific types for
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ spec:
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
- description: The Resource is ready
jsonPath: .status.conditions[?(@.type=='Accepted')].status
name: Accepted
type: string
name: v1alpha1
schema:
Expand Down Expand Up @@ -153,6 +154,11 @@ spec:
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
underlyingConfigMapName:
description: |-
UnderlyingConfigMapName is the name of the ConfigMap that contains the plugin's content.
It is set when the plugin is successfully fetched and unpacked.
type: string
type: object
type: object
served: true
Expand Down
1 change: 1 addition & 0 deletions config/crd/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ resources:
- bases/gateway-operator.konghq.com_dataplanes.yaml
- bases/gateway-operator.konghq.com_gatewayconfigurations.yaml
- bases/gateway-operator.konghq.com_dataplanemetricsextensions.yaml
- bases/gateway-operator.konghq.com_kongplugininstallations.yaml
#+kubebuilder:scaffold:crdkustomizeresource

# patches:
Expand Down
24 changes: 24 additions & 0 deletions config/rbac/role/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resources:
- configmaps/status
verbs:
- get
- apiGroups:
- ""
resources:
Expand Down Expand Up @@ -451,6 +457,24 @@ rules:
- get
- list
- watch
- apiGroups:
- gateway-operator.konghq.com
resources:
- kongplugininstallations
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- gateway-operator.konghq.com
resources:
- kongplugininstallations/status
verbs:
- get
- patch
- update
- apiGroups:
- gateway.networking.k8s.io
resources:
Expand Down
168 changes: 168 additions & 0 deletions controller/kongplugininstallation/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package kongplugininstallation

import (
"context"
"errors"
"fmt"

"github.com/samber/lo"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"oras.land/oras-go/v2/registry/remote/credentials"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"

"github.com/kong/gateway-operator/api/v1alpha1"
"github.com/kong/gateway-operator/controller/kongplugininstallation/image"
"github.com/kong/gateway-operator/controller/pkg/log"
"github.com/kong/gateway-operator/pkg/utils/kubernetes"
)

// Reconciler reconciles a KongPluginInstallation object.
type Reconciler struct {
client.Client
Scheme *runtime.Scheme
DevelopmentMode bool
}

// SetupWithManager sets up the controller with the Manager.
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha1.KongPluginInstallation{}).
WithEventFilter(predicate.GenerationChangedPredicate{}).
Owns(&corev1.ConfigMap{}, builder.WithPredicates(
predicate.Funcs{
DeleteFunc: func(e event.DeleteEvent) bool {
return true
},
CreateFunc: func(e event.CreateEvent) bool {
return false
},
UpdateFunc: func(e event.UpdateEvent) bool {
return false
},
},
)).
Complete(r)
}

// Reconcile moves the current state of an object to the intended state.
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.GetLogger(ctx, "kongplugininstallation", r.DevelopmentMode)
var kpi v1alpha1.KongPluginInstallation
log.Info(logger, "HIT", kpi, "name", req.Name, "namespace", req.Namespace)
// Fetch the KongPluginInstallation instance
if err := r.Client.Get(ctx, req.NamespacedName, &kpi); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.Info(logger, "reconcile in progress", kpi, "name", req.Name, "namespace", req.Namespace)

var credentialsStore credentials.Store
if kpi.Spec.ImagePullSecretRef != nil {
secretNN := client.ObjectKey{
Namespace: kpi.Spec.ImagePullSecretRef.Namespace,
Name: kpi.Spec.ImagePullSecretRef.Name,
}
if secretNN.Namespace == "" {
secretNN.Namespace = req.Namespace
}

var secret corev1.Secret
if err := r.Client.Get(
ctx,
secretNN,
&secret,
); err != nil {
return ctrl.Result{}, setStatusConditionFailed(ctx, r.Client, &kpi, fmt.Sprintf("can't retrieve secret %q, because: %s", secretNN, err))
}

const requiredKey = ".dockerconfigjson"
secretData, ok := secret.Data[requiredKey]
if !ok {
return ctrl.Result{}, setStatusConditionFailed(
ctx, r.Client, &kpi, fmt.Sprintf("can't parse secret %q - unexpected type, it should follow 'kubernetes.io/dockerconfigjson'", secretNN),
)
}
var err error
credentialsStore, err = image.CredentialsStoreFromString(string(secretData))
if err != nil {
return ctrl.Result{}, setStatusConditionFailed(ctx, r.Client, &kpi, fmt.Sprintf("can't parse secret: %q data: %s", secretNN, err))
}
}

plugin, err := image.FetchPluginContent(ctx, kpi.Spec.Image, credentialsStore)
if err != nil {
return ctrl.Result{}, setStatusConditionFailed(ctx, r.Client, &kpi, fmt.Sprintf("can't fetch image: %q error: %s", kpi.Spec.Image, err))
}

cms, err := kubernetes.ListConfigMapsForOwner(ctx, r.Client, kpi.GetUID())
if err != nil {
return ctrl.Result{}, err
}
var cm corev1.ConfigMap
switch len(cms) {
case 0:
if cmName := kpi.Status.UnderlyingConfigMapName; cmName != "" {
cm.Name = cmName
} else {
cm.GenerateName = kpi.Name
}
cm.Namespace = kpi.Namespace
cm.Immutable = lo.ToPtr(true)
cm.Data = map[string]string{
fmt.Sprintf("%s.lua", kpi.Name): string(plugin),
}
if err := ctrl.SetControllerReference(&kpi, &cm, r.Scheme); err != nil {
return ctrl.Result{}, err
}
if err := r.Client.Create(ctx, &cm); err != nil {
return ctrl.Result{}, err
}
kpi.Status.UnderlyingConfigMapName = cm.Name
case 1:
cm = cms[0]
cm.Name = kpi.Status.UnderlyingConfigMapName
cm.Data = map[string]string{
fmt.Sprintf("%s.lua", kpi.Name): string(plugin),
}
if err := r.Client.Update(ctx, &cm); err != nil {
return ctrl.Result{}, err
}
default:
return ctrl.Result{}, errors.New("it should never happen!!!???")
}

return ctrl.Result{}, setStatusCondition(
ctx, r.Client, &kpi, metav1.ConditionTrue, v1alpha1.KongPluginInstallationReasonReady, "plugin successfully saved in cluster as ConfigMap",
)
}

func setStatusConditionFailed(ctx context.Context, client client.Client, kpi *v1alpha1.KongPluginInstallation, msg string) error {
return setStatusCondition(ctx, client, kpi, metav1.ConditionFalse, v1alpha1.KongPluginInstallationReasonFailed, msg)
}

func setStatusCondition(
ctx context.Context, client client.Client, kpi *v1alpha1.KongPluginInstallation, conditionStatus metav1.ConditionStatus, reason v1alpha1.KongPluginInstallationConditionReason, msg string,
) error {
status := metav1.Condition{
Type: string(v1alpha1.KongPluginInstallationConditionStatusAccepted),
Status: conditionStatus,
ObservedGeneration: kpi.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(reason),
Message: msg,
}
_, index, found := lo.FindIndexOf(kpi.Status.Conditions, func(c metav1.Condition) bool {
return c.Type == string(v1alpha1.KongPluginInstallationConditionStatusAccepted)
})
if found {
kpi.Status.Conditions[index] = status
} else {
kpi.Status.Conditions = append(kpi.Status.Conditions, status)
}
return client.Status().Update(ctx, kpi)
}
7 changes: 7 additions & 0 deletions controller/kongplugininstallation/controller_rbac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package kongplugininstallation

//+kubebuilder:rbac:groups=gateway-operator.konghq.com,resources=kongplugininstallations,verbs=get;list;watch;update;patch
//+kubebuilder:rbac:groups=gateway-operator.konghq.com,resources=kongplugininstallations/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;
//+kubebuilder:rbac:groups=core,resources=configmaps/status,verbs=get
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ func FetchPluginContent(ctx context.Context, imageURL string, credentialsStore c
return nil, fmt.Errorf("unexpected format of image url: %w", err)
}
registryName, repositoryName, imageTag := ref.Context().RegistryStr(), ref.Context().RepositoryStr(), ref.Identifier()
fmt.Printf("fetching image %s from registry %s with tag %s\n",
registryName, repositoryName, imageTag,
)
// Errors for NewRegistry(..) and Repository(..) should never happen because the image URL has been already validated above.
registry, err := remote.NewRegistry(registryName)
if err != nil {
Expand Down Expand Up @@ -86,7 +83,9 @@ func FetchPluginContent(ctx context.Context, imageURL string, credentialsStore c
// and returns credentials.Store.
// This is typical way how private registries are used with Docker and Kubernetes.
func CredentialsStoreFromString(s string) (credentials.Store, error) {
// Create temporary file
// TODO: Create temporary file, which is not great and should be changed,
// but it's the only way to use credentials.NewFileStore(...) which robustly
// parses config.json as used by Docker and Kubernetes.
tmpFile, err := os.CreateTemp("", "credentials")
if err != nil {
return nil, fmt.Errorf("failed to create temporary file: %w", err)
Expand Down
1 change: 1 addition & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ KongPluginInstallationStatus defines the observed state of KongPluginInstallatio
| Field | Description |
| --- | --- |
| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#condition-v1-meta) array_ | Conditions describe the current conditions of this KongPluginInstallation. |
| `underlyingConfigMapName` _string_ | UnderlyingConfigMapName is the name of the ConfigMap that contains the plugin's content. It is set when the plugin is successfully fetched and unpacked. |


_Appears in:_
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/cert-manager/cert-manager v1.15.1
github.com/cloudflare/cfssl v1.6.5
github.com/go-logr/logr v1.4.2
github.com/google/go-containerregistry v0.6.0
github.com/google/go-containerregistry v0.20.1
github.com/google/uuid v1.6.0
github.com/kong/kubernetes-ingress-controller/v3 v3.2.3
github.com/kong/kubernetes-telemetry v0.1.4
Expand Down
Loading

0 comments on commit b7c5456

Please sign in to comment.