-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
- Loading branch information
1 parent
f3dee1c
commit 6a47c80
Showing
5 changed files
with
394 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,4 +14,5 @@ | |
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
bin/ | ||
bin/ | ||
testbin/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package resmgr | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
"strings" | ||
"sync/atomic" | ||
"testing" | ||
|
||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
apiruntime "k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
yamlutil "k8s.io/apimachinery/pkg/util/yaml" | ||
"sigs.k8s.io/cli-utils/pkg/kstatus/polling" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil" | ||
"sigs.k8s.io/controller-runtime/pkg/envtest" | ||
) | ||
|
||
var manager *ResourceManager | ||
|
||
func TestMain(m *testing.M) { | ||
testEnv := &envtest.Environment{} | ||
|
||
cfg, err := testEnv.Start() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
kubeClient, err := client.NewWithWatch(cfg, client.Options{Scheme: newScheme()}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
restMapper, err := apiutil.NewDynamicRESTMapper(cfg) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
c, err := client.New(cfg, client.Options{Mapper: restMapper}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
poller := polling.NewStatusPoller(c, restMapper) | ||
|
||
manager = &ResourceManager{ | ||
kubeClient: kubeClient, | ||
kstatusPoller: poller, | ||
fmt: &ResourceFormatter{}, | ||
fieldOwner: "resource-manager", | ||
} | ||
|
||
code := m.Run() | ||
|
||
testEnv.Stop() | ||
|
||
os.Exit(code) | ||
} | ||
|
||
func readManifest(manifest, namespace string) ([]*unstructured.Unstructured, error) { | ||
data, err := os.ReadFile(manifest) | ||
if err != nil { | ||
return nil, err | ||
} | ||
yml := fmt.Sprintf(string(data), namespace) | ||
|
||
reader := yamlutil.NewYAMLOrJSONDecoder(strings.NewReader(yml), 2048) | ||
objects := make([]*unstructured.Unstructured, 0) | ||
for { | ||
obj := &unstructured.Unstructured{} | ||
err := reader.Decode(obj) | ||
if err != nil { | ||
if err == io.EOF { | ||
err = nil | ||
break | ||
} | ||
return objects, err | ||
} | ||
|
||
if obj.IsList() { | ||
err = obj.EachListItem(func(item apiruntime.Object) error { | ||
obj := item.(*unstructured.Unstructured) | ||
objects = append(objects, obj) | ||
return nil | ||
}) | ||
if err != nil { | ||
return objects, err | ||
} | ||
continue | ||
} | ||
|
||
objects = append(objects, obj) | ||
} | ||
|
||
return objects, nil | ||
|
||
} | ||
|
||
func setNamespace(objects []*unstructured.Unstructured, namespace string) { | ||
for _, object := range objects { | ||
object.SetNamespace(namespace) | ||
} | ||
|
||
u := &unstructured.Unstructured{} | ||
u.SetGroupVersionKind(schema.GroupVersionKind{ | ||
Group: "", | ||
Kind: "Namespace", | ||
Version: "v1", | ||
}) | ||
u.SetName(namespace) | ||
objects = append(objects, u) | ||
} | ||
|
||
var nextNameId int64 | ||
|
||
func generateName(prefix string) string { | ||
id := atomic.AddInt64(&nextNameId, 1) | ||
return fmt.Sprintf("%s-%d", prefix, id) | ||
} | ||
|
||
func removeObject(s []*unstructured.Unstructured, index int) []*unstructured.Unstructured { | ||
return append(s[:index], s[index+1:]...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
package resmgr | ||
|
||
import ( | ||
"context" | ||
"encoding/base64" | ||
"fmt" | ||
"sort" | ||
"testing" | ||
"time" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
func TestApply(t *testing.T) { | ||
timeout := 10 * time.Second | ||
ctx, cancel := context.WithTimeout(context.Background(), timeout) | ||
defer cancel() | ||
|
||
objects, err := readManifest("testdata/test1.yaml", generateName("ns")) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// create objects | ||
createdChangeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// expected created order | ||
sort.Sort(ApplyOrder(objects)) | ||
var expected []string | ||
for _, object := range objects { | ||
expected = append(expected, manager.fmt.Unstructured(object)) | ||
} | ||
|
||
// verify the change set contains only created actions | ||
var output []string | ||
for _, entry := range createdChangeSet.Entries { | ||
if diff := cmp.Diff(entry.Action, string(CreatedAction)); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
output = append(output, entry.Subject) | ||
} | ||
|
||
// verify the change set contains all objects in the right order | ||
if diff := cmp.Diff(expected, output); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
|
||
// no-op apply | ||
unchangedChangeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// verify the change set contains only unchanged actions | ||
for _, entry := range unchangedChangeSet.Entries { | ||
if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
output = append(output, entry.Subject) | ||
} | ||
|
||
// extract configmap | ||
var configMap *unstructured.Unstructured | ||
for _, object := range objects { | ||
if object.GetKind() == "ConfigMap" { | ||
configMap = object | ||
break | ||
} | ||
} | ||
configMapName := manager.fmt.Unstructured(configMap) | ||
|
||
// update a value in the configmap | ||
err = unstructured.SetNestedField(configMap.Object, "val", "data", "key") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// apply changes | ||
configuredChangeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// verify the change set contains the configured action only for the configmap | ||
for _, entry := range configuredChangeSet.Entries { | ||
if entry.Subject == configMapName { | ||
if diff := cmp.Diff(string(ConfiguredAction), entry.Action); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
} else { | ||
if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
} | ||
} | ||
|
||
// get the configmap from cluster | ||
configMapClone := configMap.DeepCopy() | ||
err = manager.kubeClient.Get(ctx, client.ObjectKeyFromObject(configMapClone), configMapClone) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// get data value from the in-cluster configmap | ||
val, _, err := unstructured.NestedFieldCopy(configMapClone.Object, "data", "key") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// verify the configmap was updated in cluster with the right data value | ||
if diff := cmp.Diff(val, "val"); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
|
||
// delete the configmap | ||
deletedChangeSet, err := manager.DeleteAll(ctx, []*unstructured.Unstructured{configMap}) | ||
for _, entry := range deletedChangeSet.Entries { | ||
if diff := cmp.Diff(string(DeletedAction), entry.Action); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
} | ||
|
||
// reapply objects | ||
changeSet, err := manager.ApplyAllStaged(ctx, objects, false, timeout) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// verify the configmap was recreated | ||
for _, entry := range changeSet.Entries { | ||
if entry.Subject == configMapName { | ||
if diff := cmp.Diff(string(CreatedAction), entry.Action); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
} else { | ||
if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
} | ||
} | ||
|
||
// extract secret | ||
var secret *unstructured.Unstructured | ||
for _, object := range objects { | ||
if object.GetKind() == "Secret" { | ||
secret = object | ||
break | ||
} | ||
} | ||
secretName := manager.fmt.Unstructured(secret) | ||
|
||
// update a value in the secret | ||
err = unstructured.SetNestedField(secret.Object, "val-secret", "stringData", "key") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// apply and expect to fail | ||
changeSet, err = manager.ApplyAllStaged(ctx, objects, false, timeout) | ||
if err == nil { | ||
t.Fatal("Expected error got none") | ||
} | ||
|
||
// verify that the error message does not contain sensitive information | ||
expectedErr := fmt.Sprintf("%s is invalid, error: secret is is immutable", manager.fmt.Unstructured(secret)) | ||
if diff := cmp.Diff(expectedErr, err.Error()); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
|
||
// force apply | ||
changeSet, err = manager.ApplyAllStaged(ctx, objects, true, timeout) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// verify the secret was recreated | ||
for _, entry := range changeSet.Entries { | ||
if entry.Subject == secretName { | ||
if diff := cmp.Diff(string(CreatedAction), entry.Action); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
} else { | ||
if diff := cmp.Diff(string(UnchangedAction), entry.Action); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
} | ||
} | ||
|
||
// get the secret from cluster | ||
secretClone := secret.DeepCopy() | ||
err = manager.kubeClient.Get(ctx, client.ObjectKeyFromObject(secretClone), secretClone) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// get data value from the in-cluster secret | ||
val, _, err = unstructured.NestedFieldCopy(secretClone.Object, "data", "key") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// verify the secret was updated in cluster with the right data value | ||
if diff := cmp.Diff(val, base64.StdEncoding.EncodeToString([]byte("val-secret"))); diff != "" { | ||
t.Errorf("Mismatch from expected value (-want +got):\n%s", diff) | ||
} | ||
} |
Oops, something went wrong.