From 02d69aab351f6b67b0cc4d853e4575d18e85c76c Mon Sep 17 00:00:00 2001 From: yolandadu Date: Tue, 7 Jul 2020 11:00:47 -0500 Subject: [PATCH] Add support for applying and deleting configs on cluster This commit is to add support to apply generated configs on cluster and then also enable users to delete them from cluster. --- generators/pkg/manager/manage.go | 86 +++++++++++ generators/pkg/manager/manage_test.go | 205 ++++++++++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 generators/pkg/manager/manage.go create mode 100644 generators/pkg/manager/manage_test.go diff --git a/generators/pkg/manager/manage.go b/generators/pkg/manager/manage.go new file mode 100644 index 000000000..987e0f6a5 --- /dev/null +++ b/generators/pkg/manager/manage.go @@ -0,0 +1,86 @@ +package manager + +import ( + "context" + "flag" + "fmt" + "io" + "path/filepath" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" + "sigs.k8s.io/controller-runtime/pkg/client" + + //"sigs.k8s.io/yaml" + "k8s.io/apimachinery/pkg/util/yaml" +) + +// Create the Kubernetes client +func GetKubeClient() (client.Client, error) { + var kubeconfig *string + if home := homedir.HomeDir(); home != "" { + kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") + } else { + kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file") + } + flag.Parse() + + // use the current context in kubeconfig + config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) + if err != nil { + return nil, fmt.Errorf("fail to build config from the flags: %w", err) + } + + return client.New(config, client.Options{}) +} + +func read(reader io.Reader) ([]*unstructured.Unstructured, error) { + //read the input file + r := yaml.NewYAMLToJSONDecoder(reader) + list := []*unstructured.Unstructured{} + for { + //unmarshal the yaml object + u := new(unstructured.Unstructured) + if err := r.Decode(u); err != nil { + if err == io.EOF { + return list, nil + } + return list, fmt.Errorf("failed to unmarshal the reader: %w", err) + } + //setup the namespace to default + if u.GetNamespace() == "" { + u.SetNamespace("default") + } + list = append(list, u) + } +} + +// Create resources from io.Reader on Kubernetes objects using client +func CreateResource(cl client.Writer, reader io.Reader) error { + resources, err := read(reader) + if err != nil { + return fmt.Errorf("fail to read the resources: %w", err) + } + for _, r := range resources { + if err := cl.Create(context.Background(), r); err != nil { + return fmt.Errorf("failed to create the resource: %w", err) + } + } + return nil +} + +// Delete resources from io.Reader on Kubernetes objects using client +func DeleteResource(cl client.Writer, reader io.Reader) error { + resources, err := read(reader) + if err != nil { + return fmt.Errorf("fail to read the resources: %w", err) + } + for _, r := range resources { + if err := cl.Delete(context.Background(), r); err != nil { + return fmt.Errorf("failed to delete the resource: %w", err) + } + } + return nil +} diff --git a/generators/pkg/manager/manage_test.go b/generators/pkg/manager/manage_test.go new file mode 100644 index 000000000..42a4ba5bc --- /dev/null +++ b/generators/pkg/manager/manage_test.go @@ -0,0 +1,205 @@ +package manager + +import ( + "bytes" + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/yaml" +) + +type testClient struct { + client.Writer + list []runtime.Object +} + +func (t *testClient) Create(ctx context.Context, obj runtime.Object, opts ...client.CreateOption) error { + t.list = append(t.list, obj) + return nil +} + +func (t *testClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOption) error { + t.list = append(t.list, obj) + return nil +} + +// testdata +var resources = []*unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "name": "test-task", + "kind": "Task", + "apiVersion": "tekton.dev/v1beta1", + }, + }, + + { + Object: map[string]interface{}{ + "name": "test-task", + "kind": "Task", + "apiVersion": "tekton.dev/v1beta1", + "metadata": map[string]interface{}{ + "namespace": "default", + }, + }, + }, + + { + Object: map[string]interface{}{ + "name": "test-task", + "kind": "Task", + "apiVersion": "tekton.dev/v1beta1", + "metadata": map[string]interface{}{ + "namespace": "default", + }, + }, + }, + + { + Object: map[string]interface{}{ + "apiVersion": "tekton.dev/v1beta1", + "kind": "Task", + "metadata": map[string]interface{}{ + "namespace": "default", + "name": "echo-hello-world", + }, + "spec": map[string]interface{}{ + "steps": map[string]interface{}{ + "name": "echo", + "image": "ubuntu", + "command": []interface{}{string("echo")}, + "args": []interface{}{string("Hello world")}, + }, + }, + }, + }, +} + +func TestApplyResource(t *testing.T) { + tables := []struct { + u *unstructured.Unstructured + want []runtime.Object + }{ + { + resources[0], + []runtime.Object{ + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "name": "test-task", + "kind": "Task", + "apiVersion": "tekton.dev/v1beta1", + "metadata": map[string]interface{}{ + "namespace": "default", + }, + }, + }, + }, + }, + + { + resources[1], + []runtime.Object{ + resources[1], + }, + }, + + { + resources[2], + []runtime.Object{ + resources[2], + }, + }, + + { + resources[3], + []runtime.Object{ + resources[3], + }, + }, + } + + for _, table := range tables { + b, err := yaml.Marshal(table.u) + if err != nil { + t.Fatalf("fail to marshal the test data: %v", err) + } + + buf := bytes.NewBuffer(b) + client := &testClient{} + if err := CreateResource(client, buf); err != nil { + t.Fatalf("fail to create resource: %v", err) + } + + if diff := cmp.Diff(table.want, client.list); diff != "" { + t.Errorf("Objects mismatch (-want +got): \n %s", diff) + } + + } + +} + +func TestDeleteResource(t *testing.T) { + tables := []struct { + u *unstructured.Unstructured + want []runtime.Object + }{ + { + resources[0], + []runtime.Object{ + &unstructured.Unstructured{ + Object: map[string]interface{}{ + "name": "test-task", + "kind": "Task", + "apiVersion": "tekton.dev/v1beta1", + "metadata": map[string]interface{}{ + "namespace": "default", + }, + }, + }, + }, + }, + + { + resources[1], + []runtime.Object{ + resources[1], + }, + }, + + { + resources[2], + []runtime.Object{ + resources[2], + }, + }, + + { + resources[3], + []runtime.Object{ + resources[3], + }, + }, + } + + for _, table := range tables { + b, err := yaml.Marshal(table.u) + if err != nil { + t.Fatalf("fail to marshal the test data: %v", err) + } + + buf := bytes.NewBuffer(b) + client := &testClient{} + if err := DeleteResource(client, buf); err != nil { + t.Fatalf("fail to delete resource: %v", err) + } + + if diff := cmp.Diff(table.want, client.list); diff != "" { + t.Errorf("Objects mismatch (-want +got): \n %s", diff) + } + + } +}