Skip to content

Commit

Permalink
fix: allow existing namespaces to be used (#49)
Browse files Browse the repository at this point in the history
* fix: allow existing namespaces to be used

There are some use cases where we'd want to use an existing namespace.
Without this patch, we can get a client-side conflict. If the namespace
already exists, just add the label metadata to the namespace and don't
touch anything else. For any other resource we should keep the existing
behavior.

Signed-off-by: Blake Pettersson <[email protected]>

* chore: please the linter

Signed-off-by: Blake Pettersson <[email protected]>

---------

Signed-off-by: Blake Pettersson <[email protected]>
  • Loading branch information
blakepettersson authored Aug 21, 2024
1 parent ac73d9c commit f2824a3
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 13 deletions.
40 changes: 33 additions & 7 deletions internal/clients/kube/apply_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package kube

import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/crossplane/crossplane-runtime/pkg/logging"
"k8s.io/apimachinery/pkg/types"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -21,6 +25,7 @@ type ApplyClient struct {
dynamicClient dynamic.Interface
clientset kubernetes.Interface
discoveryRestMapper meta.RESTMapper
logger logging.Logger
}

type applyObject struct {
Expand All @@ -29,7 +34,7 @@ type applyObject struct {
unstructuredObj map[string]interface{}
}

func NewApplyClient(dynamicClient dynamic.Interface, clientset kubernetes.Interface) (*ApplyClient, error) {
func NewApplyClient(dynamicClient dynamic.Interface, clientset kubernetes.Interface, logger logging.Logger) (*ApplyClient, error) {
groupResources, err := restmapper.GetAPIGroupResources(clientset.Discovery())
if err != nil {
return &ApplyClient{}, fmt.Errorf("error setting up API discovery for dynamic client: %w", err)
Expand All @@ -38,6 +43,7 @@ func NewApplyClient(dynamicClient dynamic.Interface, clientset kubernetes.Interf
return &ApplyClient{
dynamicClient: dynamicClient,
clientset: clientset,
logger: logger,
discoveryRestMapper: restmapper.NewDiscoveryRESTMapper(groupResources),
}, nil
}
Expand All @@ -62,6 +68,8 @@ func (a ApplyClient) ApplyManifests(ctx context.Context, manifests string, delet
return err
}

a.logger.Debug("Applying k8s object", "name", applyObject.name, "gvk", gvk)

if delete {
err = a.deleteObject(ctx, mapping, applyObject)
if errors.IsNotFound(err) {
Expand Down Expand Up @@ -132,19 +140,37 @@ func (a ApplyClient) deleteObject(ctx context.Context, mapping *meta.RESTMapping

func (a ApplyClient) applyObject(ctx context.Context, mapping *meta.RESTMapping, applyObject applyObject) error {
if isClusterScopedResource(mapping.Resource.Resource) {
// Don't risk overwriting a namespace if it already exists
if mapping.Resource.Resource == "namespaces" {
_, err := a.dynamicClient.Resource(mapping.Resource).Get(ctx, applyObject.name, v1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
_, err = a.dynamicClient.Resource(mapping.Resource).Apply(ctx, applyObject.name, &unstructured.Unstructured{Object: applyObject.unstructuredObj}, v1.ApplyOptions{FieldManager: "application/apply-patch"})
return err
}

return err
}

// Object already exists, just patch the namespace metadata.
metadata := applyObject.unstructuredObj["metadata"]
patchBytes, err := json.Marshal(metadata)
if err != nil {
return err
}

_, err = a.dynamicClient.Resource(mapping.Resource).Patch(ctx, applyObject.name, types.StrategicMergePatchType, patchBytes, v1.PatchOptions{FieldManager: "application/apply-patch"})
return err
}

_, err := a.dynamicClient.Resource(mapping.Resource).Apply(ctx, applyObject.name, &unstructured.Unstructured{Object: applyObject.unstructuredObj}, v1.ApplyOptions{FieldManager: "application/apply-patch"})
return err
}

_, err := a.dynamicClient.Resource(mapping.Resource).Namespace(applyObject.namespace).Apply(ctx, applyObject.name, &unstructured.Unstructured{Object: applyObject.unstructuredObj}, v1.ApplyOptions{FieldManager: "application/apply-patch"})

return err
}

func isClusterScopedResource(resource string) bool {
if resource == "namespaces" || resource == "clusterroles" || resource == "clusterrolebindings" {
return true
}

return false
return resource == "namespaces" || resource == "clusterroles" || resource == "clusterrolebindings"
}
10 changes: 5 additions & 5 deletions internal/clients/kube/apply_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"testing"

"github.com/crossplane/crossplane-runtime/pkg/logging"

"github.com/stretchr/testify/require"
dynamic "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes/fake"
Expand All @@ -12,20 +14,18 @@ import (
"github.com/akuityio/provider-crossplane-akuity/internal/clients/kube"
)

var (
ctx = context.TODO()
)
var ctx = context.TODO()

func TestApplyClient_EmptyManifests(t *testing.T) {
applyClient, err := kube.NewApplyClient(dynamic.NewSimpleDynamicClient(scheme.Scheme), fake.NewSimpleClientset())
applyClient, err := kube.NewApplyClient(dynamic.NewSimpleDynamicClient(scheme.Scheme), fake.NewSimpleClientset(), logging.NewNopLogger())
require.NoError(t, err)

err = applyClient.ApplyManifests(ctx, "", false)
require.NoError(t, err)
}

func TestApplyClient_ApplyManifestsInvalidKindErr(t *testing.T) {
applyClient, err := kube.NewApplyClient(dynamic.NewSimpleDynamicClient(scheme.Scheme), fake.NewSimpleClientset())
applyClient, err := kube.NewApplyClient(dynamic.NewSimpleDynamicClient(scheme.Scheme), fake.NewSimpleClientset(), logging.NewNopLogger())
require.NoError(t, err)

err = applyClient.ApplyManifests(ctx, "apiVersion: v1\nkind: InvalidKind\nmetadata:\n name: test-pod", false)
Expand Down
8 changes: 7 additions & 1 deletion internal/controller/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,11 +218,17 @@ func (c *External) Create(ctx context.Context, mg resource.Managed) (managed.Ext
}

if managedCluster.Spec.ForProvider.EnableInClusterKubeConfig || managedCluster.Spec.ForProvider.KubeConfigSecretRef.Name != "" {
c.logger.Debug("Retrieving cluster manifests....")
clusterManifests, err := c.client.GetClusterManifests(ctx, managedCluster.Spec.ForProvider.InstanceID, managedCluster.Spec.ForProvider.Name)
if err != nil {
return managed.ExternalCreation{}, fmt.Errorf("could not get cluster manifests to apply: %w", err)
}

c.logger.Debug("Applying cluster manifests",
"clusterName", managedCluster.Name,
"instanceID", managedCluster.Spec.ForProvider.InstanceID,
)
c.logger.Debug(clusterManifests)
err = c.applyClusterManifests(ctx, *managedCluster, clusterManifests, false)
if err != nil {
return managed.ExternalCreation{}, fmt.Errorf("could not apply cluster manifests: %w", err)
Expand Down Expand Up @@ -358,7 +364,7 @@ func (c *External) applyClusterManifests(ctx context.Context, managedCluster v1a
return fmt.Errorf("error creating typed client: %w", err)
}

applyClient, err := kube.NewApplyClient(dynamicClient, clientset)
applyClient, err := kube.NewApplyClient(dynamicClient, clientset, c.logger)
if err != nil {
return fmt.Errorf("error creating apply client: %w", err)
}
Expand Down

0 comments on commit f2824a3

Please sign in to comment.