Skip to content

Commit

Permalink
feat: loaded resources support list of fake client
Browse files Browse the repository at this point in the history
This logic can be removed after upgrading to controller-runtime v0.10.1
Ref: kubernetes-sigs/controller-runtime#1662
  • Loading branch information
l-qing committed Jan 5, 2022
1 parent c4bc1e0 commit 738c45c
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 3 deletions.
54 changes: 51 additions & 3 deletions testing/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,33 @@ func LoadKubeResourcesAsUnstructured(file string) (objs []unstructured.Unstructu
}

// LoadKubeResources loading kubernetes resources
func LoadKubeResources(file string, clt client.Client) (err error) {
func LoadKubeResources(file string, clt client.Client, converts ...ConvertRuntimeObjctToClientObjectFunc) (err error) {
errs := []error{}
objs, err := LoadKubeResourcesAsUnstructured(file)
if err != nil {
return
}
OUTER:
for _, obj := range objs {
runtimeObj, err := convertFromUnstructuredIfNecessary(clt.Scheme(), &obj)
if err != nil {
errs = append(errs, err)
continue
}
for _, convert := range converts {
if clientObj, err := convert(runtimeObj); err == nil {
if err = clt.Create(context.Background(), clientObj); err != nil {
errs = append(errs, err)
}
continue OUTER
}
}
if err = clt.Create(context.Background(), &obj); err != nil {
return
errs = append(errs, err)
continue
}
}
return
return errors.NewAggregate(errs)
}

// UnstructedToTyped converts an unstructured object into a object
Expand Down Expand Up @@ -135,3 +151,35 @@ func LoadResourceFromFile(scheme *runtime.Scheme, path string) (obj runtime.Obje
}
return objs[0], nil
}

type ConvertRuntimeObjctToClientObjectFunc func(runtime.Object) (client.Object, error)

// This logic can be removed after upgrading to controller-runtime v0.10.1
// Copy from: https://github.com/kubernetes-sigs/controller-runtime/pull/1662/files

// convertFromUnstructuredIfNecessary will convert *unstructured.Unstructured for a GVK that is recocnized
// by the schema into the whatever the schema produces with New() for said GVK.
// This is required because the tracker unconditionally saves on manipulations, but it's List() implementation
// tries to assign whatever it finds into a ListType it gets from schema.New() - Thus we have to ensure
// we save as the very same type, otherwise subsequent List requests will fail.
func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (runtime.Object, error) {
u, isUnstructured := o.(*unstructured.Unstructured)
if !isUnstructured || !s.Recognizes(u.GroupVersionKind()) {
return o, nil
}

typed, err := s.New(u.GroupVersionKind())
if err != nil {
return nil, fmt.Errorf("scheme recognizes %s but failed to produce an object for it: %w", u.GroupVersionKind().String(), err)
}

unstructuredSerialized, err := json.Marshal(u)
if err != nil {
return nil, fmt.Errorf("failed to serialize %T: %w", unstructuredSerialized, err)
}
if err := json.Unmarshal(unstructuredSerialized, typed); err != nil {
return nil, fmt.Errorf("failed to unmarshal the content of %T into %T: %w", u, typed, err)
}

return typed, nil
}
132 changes: 132 additions & 0 deletions testing/kubernetes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
Copyright 2021 The Katanomi Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package testing

import (
"context"
"fmt"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
)

var _ = Describe("LoadKubeResources", func() {

var (
ctx context.Context
scheme *runtime.Scheme
clt client.Client
err error

configmap *corev1.ConfigMap
secret *corev1.Secret
configmapKey = client.ObjectKey{
Namespace: "default",
Name: "configmap",
}
secretKey = client.ObjectKey{
Namespace: "default",
Name: "secret",
}
)

BeforeEach(func() {
scheme = runtime.NewScheme()
corev1.AddToScheme(scheme)
ctx = context.TODO()
clt = fake.NewClientBuilder().WithScheme(scheme).Build()
})

When("load resources without convert", func() {
BeforeEach(func() {
Expect(LoadKubeResources("testdata/loadkuberesources.yaml", clt)).To(Succeed())
})
It("should get configmap success", func() {
configmap = new(corev1.ConfigMap)
err = clt.Get(ctx, configmapKey, configmap)
Expect(err).Should(BeNil())
})
It("should get secret success", func() {
secret = new(corev1.Secret)
err = clt.Get(ctx, secretKey, secret)
Expect(err).Should(BeNil())
})
})

When("load resources with one convert", func() {
BeforeEach(func() {
Expect(LoadKubeResources("testdata/loadkuberesources.yaml", clt, convertConfigmap)).To(Succeed())
})
It("should list configmap success", func() {
configmapList := &corev1.ConfigMapList{}
err = clt.List(ctx, configmapList, client.InNamespace(configmapKey.Namespace))
Expect(err).Should(BeNil())
Expect(configmapList.Items).To(HaveLen(1))
})
PIt("should list secret failed", func() {
// This case will failed after upgrading to controller-runtime v0.10.1
secretList := &corev1.SecretList{}
err = clt.List(ctx, secretList, client.InNamespace(secretKey.Namespace))
Expect(err).Should(Not(BeNil()))
Expect(err.Error()).To(Equal("item[0]: can't assign or convert unstructured.Unstructured into v1.Secret"))
})
})

When("load resources with two converts", func() {
BeforeEach(func() {
Expect(LoadKubeResources("testdata/loadkuberesources.yaml", clt, convertConfigmap, convertSecret)).To(Succeed())
})
It("should list configmap success", func() {
configmapList := &corev1.ConfigMapList{}
err = clt.List(ctx, configmapList, client.InNamespace(configmapKey.Namespace))
Expect(err).Should(BeNil())
Expect(configmapList.Items).To(HaveLen(1))
})
It("should list secret success", func() {
secretList := &corev1.SecretList{}
err = clt.List(ctx, secretList, client.InNamespace(secretKey.Namespace))
Expect(err).Should(BeNil())
Expect(secretList.Items).To(HaveLen(1))
})
})

})

func convertConfigmap(runtimeObj runtime.Object) (obj client.Object, err error) {
switch v := runtimeObj.(type) {
case *corev1.ConfigMap:
obj = v
default:
err = fmt.Errorf("Unsupported gvk: %s", runtimeObj.GetObjectKind().GroupVersionKind())
}
return
}

func convertSecret(runtimeObj runtime.Object) (obj client.Object, err error) {
switch v := runtimeObj.(type) {
case *corev1.Secret:
obj = v
default:
err = fmt.Errorf("Unsupported gvk: %s", runtimeObj.GetObjectKind().GroupVersionKind())
}
return
}
16 changes: 16 additions & 0 deletions testing/testdata/loadkuberesources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: configmap
namespace: default
---
apiVersion: v1
kind: Secret
type: kubernetes.io/basic-auth
metadata:
name: secret
namespace: default
data:
password: YWRtaW4=
username: YWRtaW4=

29 changes: 29 additions & 0 deletions testing/testing_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2022 The Katanomi Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package testing_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestTesting(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Testing Suite")
}

0 comments on commit 738c45c

Please sign in to comment.