From 5da24cd22855e083ad2f9f381bf29c5c31b3c65c Mon Sep 17 00:00:00 2001 From: John Patterson Date: Wed, 28 Sep 2016 08:06:49 -0500 Subject: [PATCH] Adding support for parsing multiple docs (#23) --- fixtures/deployment-service.yml | 32 +++++++ fixtures/{deployment.yaml => deployment.yml} | 0 main.go | 35 +++++--- main_test.go | 2 +- resources.go | 93 +++++++++++++------- resources_test.go | 82 ++++++++++++++--- 6 files changed, 186 insertions(+), 58 deletions(-) create mode 100644 fixtures/deployment-service.yml rename fixtures/{deployment.yaml => deployment.yml} (100%) diff --git a/fixtures/deployment-service.yml b/fixtures/deployment-service.yml new file mode 100644 index 0000000..e64cd81 --- /dev/null +++ b/fixtures/deployment-service.yml @@ -0,0 +1,32 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx + labels: + app: nginx +spec: + ports: + - port: 80 + selector: + app: nginx + type: LoadBalancer +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: nginx + labels: + app: nginx +spec: + replicas: 3 + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:latest + ports: + - containerPort: 80 diff --git a/fixtures/deployment.yaml b/fixtures/deployment.yml similarity index 100% rename from fixtures/deployment.yaml rename to fixtures/deployment.yml diff --git a/main.go b/main.go index 8cca2f4..83c848e 100644 --- a/main.go +++ b/main.go @@ -3,13 +3,11 @@ package main import ( "flag" "fmt" - "io/ioutil" "log" "os" "strings" "k8s.io/kubernetes/pkg/api/v1" - "k8s.io/kubernetes/pkg/util/yaml" ) var ( @@ -74,14 +72,7 @@ func main() { defer in.Close() } - // read in doc - data, err := ioutil.ReadAll(in) - if err != nil { - log.Fatal(err) - } - - // ensure doc is JSON - data, err = yaml.ToJSON(data) + resources, err := ParseDocs(in) if err != nil { log.Fatal(err) } @@ -148,9 +139,27 @@ func main() { // inject environment variables into the supplied resource doc // and print the result to STDOUT - object, err := InjectVars(data, envVars) - if err = printResource(object, toYAML); err != nil { - log.Fatal(err) + for _, resource := range resources { + var result interface{} + switch resource.Kind { + case "Deployment": + result, err = resource.InjectVarsDeployment(envVars) + case "DaemonSet": + result, err = resource.InjectVarsDaemonSet(envVars) + case "ReplicaSet": + result, err = resource.InjectVarsReplicaSet(envVars) + case "ReplicationController": + result, err = resource.InjectVarsRC(envVars) + default: + result, err = resource.UnmarshalGeneric() + } + if err != nil { + log.Fatal(err) + } + + if err = printResource(result, toYAML); err != nil { + log.Fatal(err) + } } } diff --git a/main_test.go b/main_test.go index 2f10f33..70244cb 100644 --- a/main_test.go +++ b/main_test.go @@ -11,7 +11,7 @@ func TestMainWithYAML(t *testing.T) { "kenv", "-v", "fixtures/vars.env", - "fixtures/deployment.yaml", + "fixtures/deployment.yml", } main() diff --git a/resources.go b/resources.go index b9aa642..0ece442 100644 --- a/resources.go +++ b/resources.go @@ -2,39 +2,56 @@ package main import ( "encoding/json" - "fmt" - "log" + "io" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/yaml" ) -func InjectVars(data []byte, envVars []v1.EnvVar) (runtime.Object, error) { - kind, err := getDocKind(data) - if err != nil { - log.Fatal(err) - } +// KubeResource represents a resource kind and raw data to be used +// later for injecting EnvVars +type KubeResource struct { + Kind string + Data []byte +} + +// ParseDocs iterates through YAML or JSON docs and discovers +// their type returning a list of KubeResources +func ParseDocs(reader io.Reader) ([]KubeResource, error) { + resources := []KubeResource{} + decoder := yaml.NewYAMLOrJSONDecoder(reader, 4096) + + for { + rawExtension := runtime.RawExtension{} + err := decoder.Decode(&rawExtension) + if err == io.EOF { + break + } else if err != nil { + return resources, err + } - switch kind { - case "Deployment": - return injectVarsDeployment(data, envVars) - case "DaemonSet": - return injectVarsDaemonSet(data, envVars) - case "ReplicaSet": - return injectVarsReplicaSet(data, envVars) - case "ReplicationController": - return injectVarsReplicationController(data, envVars) - default: - return &v1beta1.Deployment{}, fmt.Errorf("Kind %s not supported\n", kind) + kind, err := getResourceKind(rawExtension.Raw) + if err != nil { + return resources, err + } + + resources = append(resources, KubeResource{ + Kind: kind, + Data: rawExtension.Raw, + }) } + + return resources, nil } -// injectVarsDeployment inserts EnvVars into a deployment doc -func injectVarsDeployment(data []byte, envVars []v1.EnvVar) (*v1beta1.Deployment, error) { +// InjectVarsDeployment inserts EnvVars into a Deployment doc +func (k *KubeResource) InjectVarsDeployment(envVars []v1.EnvVar) (*v1beta1.Deployment, error) { deployment := &v1beta1.Deployment{} - if err := json.Unmarshal(data, deployment); err != nil { + + if err := json.Unmarshal(k.Data, &deployment); err != nil { return deployment, err } @@ -42,16 +59,15 @@ func injectVarsDeployment(data []byte, envVars []v1.EnvVar) (*v1beta1.Deployment deployment.Spec.Template.Spec, envVars, ) - deployment.Spec.Template.Spec = podSpec return deployment, nil } -// injectVarsDaemonSet inserts EnvVars into a daemonSet doc -func injectVarsDaemonSet(data []byte, envVars []v1.EnvVar) (*v1beta1.DaemonSet, error) { +// InjectVarsDaemonSet inserts EnvVars into a daemonSet doc +func (k *KubeResource) InjectVarsDaemonSet(envVars []v1.EnvVar) (*v1beta1.DaemonSet, error) { daemonSet := &v1beta1.DaemonSet{} - if err := json.Unmarshal(data, daemonSet); err != nil { + if err := json.Unmarshal(k.Data, daemonSet); err != nil { return daemonSet, err } @@ -63,10 +79,10 @@ func injectVarsDaemonSet(data []byte, envVars []v1.EnvVar) (*v1beta1.DaemonSet, return daemonSet, nil } -// injectVarsReplicaSet inserts EnvVars into a replicaSet doc -func injectVarsReplicaSet(data []byte, envVars []v1.EnvVar) (*v1beta1.ReplicaSet, error) { +// InjectVarsReplicaSet inserts EnvVars into a replicaSet doc +func (k *KubeResource) InjectVarsReplicaSet(envVars []v1.EnvVar) (*v1beta1.ReplicaSet, error) { replicaSet := &v1beta1.ReplicaSet{} - if err := json.Unmarshal(data, replicaSet); err != nil { + if err := json.Unmarshal(k.Data, replicaSet); err != nil { return replicaSet, err } @@ -79,10 +95,10 @@ func injectVarsReplicaSet(data []byte, envVars []v1.EnvVar) (*v1beta1.ReplicaSet return replicaSet, nil } -// injectVarsReplicationController inserts EnvVars into a replicationController doc -func injectVarsReplicationController(data []byte, envVars []v1.EnvVar) (*v1.ReplicationController, error) { +// InjectVarsReplicationController inserts EnvVars into a replicationController doc +func (k *KubeResource) InjectVarsRC(envVars []v1.EnvVar) (*v1.ReplicationController, error) { replicationController := &v1.ReplicationController{} - if err := json.Unmarshal(data, replicationController); err != nil { + if err := json.Unmarshal(k.Data, replicationController); err != nil { return replicationController, err } @@ -95,8 +111,19 @@ func injectVarsReplicationController(data []byte, envVars []v1.EnvVar) (*v1.Repl return replicationController, nil } -// getDocKind unmarshalls a file and returns the kind of resource doc -func getDocKind(data []byte) (string, error) { +// UnmarshalGeneric does not attempt to unmarshal to a known type, +// instead returns a generic interface object for displaying to the user +func (k *KubeResource) UnmarshalGeneric() (interface{}, error) { + var generic interface{} + if err := json.Unmarshal(k.Data, &generic); err != nil { + return generic, err + } + + return generic, nil +} + +// getResourceKind unmarshalls a file and returns the kind of resource doc +func getResourceKind(data []byte) (string, error) { typeMeta := unversioned.TypeMeta{} if err := json.Unmarshal(data, &typeMeta); err != nil { return "", err diff --git a/resources_test.go b/resources_test.go index a6f43df..fa80a40 100644 --- a/resources_test.go +++ b/resources_test.go @@ -2,14 +2,38 @@ package main import ( "io/ioutil" + "os" "reflect" "testing" "k8s.io/kubernetes/pkg/api/v1" ) +func TestParseDocs(t *testing.T) { + file, err := os.Open("fixtures/deployment-service.yml") + defer file.Close() + if err != nil { + t.Fatal(err) + } + + resources, err := ParseDocs(file) + if err != nil { + t.Fatal(err) + } + + if len(resources) != 2 { + t.Fatalf("Resource count is not 2: %+v", resources) + } +} + func TestInjectVarsDeployment(t *testing.T) { - data, err := ioutil.ReadFile("fixtures/deployment.json") + file, err := os.Open("fixtures/deployment.json") + defer file.Close() + if err != nil { + t.Fatal(err) + } + + resources, err := ParseDocs(file) if err != nil { t.Fatal(err) } @@ -25,7 +49,7 @@ func TestInjectVarsDeployment(t *testing.T) { }, } - deployment, err := injectVarsDeployment(data, envVars) + deployment, err := resources[0].InjectVarsDeployment(envVars) if err != nil { t.Fatal(err) } @@ -38,7 +62,13 @@ func TestInjectVarsDeployment(t *testing.T) { } func TestInjectVarsDaemonSet(t *testing.T) { - data, err := ioutil.ReadFile("fixtures/daemonset.json") + file, err := os.Open("fixtures/deployment.json") + defer file.Close() + if err != nil { + t.Fatal(err) + } + + resources, err := ParseDocs(file) if err != nil { t.Fatal(err) } @@ -54,7 +84,7 @@ func TestInjectVarsDaemonSet(t *testing.T) { }, } - daemonSet, err := injectVarsDaemonSet(data, envVars) + daemonSet, err := resources[0].InjectVarsDaemonSet(envVars) if err != nil { t.Fatal(err) } @@ -67,7 +97,13 @@ func TestInjectVarsDaemonSet(t *testing.T) { } func TestInjectVarsReplicaSet(t *testing.T) { - data, err := ioutil.ReadFile("fixtures/replicaset.json") + file, err := os.Open("fixtures/deployment.json") + defer file.Close() + if err != nil { + t.Fatal(err) + } + + resources, err := ParseDocs(file) if err != nil { t.Fatal(err) } @@ -83,7 +119,7 @@ func TestInjectVarsReplicaSet(t *testing.T) { }, } - replicaSet, err := injectVarsReplicaSet(data, envVars) + replicaSet, err := resources[0].InjectVarsReplicaSet(envVars) if err != nil { t.Fatal(err) } @@ -95,8 +131,14 @@ func TestInjectVarsReplicaSet(t *testing.T) { } } -func TestInjectVarsReplicationController(t *testing.T) { - data, err := ioutil.ReadFile("fixtures/replicationcontroller.json") +func TestInjectVarsRC(t *testing.T) { + file, err := os.Open("fixtures/deployment.json") + defer file.Close() + if err != nil { + t.Fatal(err) + } + + resources, err := ParseDocs(file) if err != nil { t.Fatal(err) } @@ -112,7 +154,7 @@ func TestInjectVarsReplicationController(t *testing.T) { }, } - replicationController, err := injectVarsReplicationController(data, envVars) + replicationController, err := resources[0].InjectVarsRC(envVars) if err != nil { t.Fatal(err) } @@ -124,13 +166,31 @@ func TestInjectVarsReplicationController(t *testing.T) { } } -func TestGetDocKind(t *testing.T) { +func TestUnmarshalGeneric(t *testing.T) { + file, err := os.Open("fixtures/deployment.json") + defer file.Close() + if err != nil { + t.Fatal(err) + } + + resources, err := ParseDocs(file) + if err != nil { + t.Fatal(err) + } + + _, err = resources[0].UnmarshalGeneric() + if err != nil { + t.Fatal(err) + } +} + +func TestGetResourceKind(t *testing.T) { data, err := ioutil.ReadFile("fixtures/deployment.json") if err != nil { t.Fatal(err) } - kind, err := getDocKind(data) + kind, err := getResourceKind(data) if err != nil { t.Fatal(err) }