diff --git a/cmd/cmd.go b/cmd/cmd.go index f6be986..e5eafc3 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -21,6 +21,7 @@ import ( "io/ioutil" "os" "os/exec" + "strings" "unicode" "github.com/ghodss/yaml" @@ -145,26 +146,63 @@ func isJSON(s []byte) bool { return bytes.HasPrefix(bytes.TrimLeftFunc(s, unicode.IsSpace), []byte{'{'}) } +func hasDashes(s []byte) bool { + return strings.Contains(string(s), "\n---\n") +} + // NeatYAMLOrJSON converts 'in' to json if needed, invokes neat, and converts back if needed according the the outputFormat argument: yaml/json/same func NeatYAMLOrJSON(in []byte, outputFormat string) (out []byte, err error) { - var injson, outjson string + var injson, outjson, outyaml string + var hasDocs = false itsYaml := !isJSON(in) if itsYaml { - injsonbytes, err := yaml.YAMLToJSON(in) - if err != nil { - return nil, fmt.Errorf("error converting from yaml to json : %v", err) + hasDocs = hasDashes(in) + if hasDocs { + documents := strings.Split(string(in), "\n---\n") + for i, doc := range documents { + injsonbytes, err := yaml.YAMLToJSON([]byte(doc)) + if err != nil { + return nil, fmt.Errorf("[Doc n.%d]: error converting from yaml to json: %v", i+1, err) + } + res, err := Neat(string(injsonbytes)) + if err != nil { + return nil, fmt.Errorf("error from NeatPod: %v", err) + } + outjson += res + "\n" + if outputFormat == "yaml" || (outputFormat == "same" && itsYaml) { + oyaml, err := yaml.JSONToYAML([]byte(res)) + if err != nil { + return nil, fmt.Errorf("error converting from json to yaml : %v", err) + } + if i == 0 { + outyaml += string(oyaml) + } else { + outyaml += "---\n" + string(oyaml) + } + } + } + } else { + injsonbytes, err := yaml.YAMLToJSON([]byte(in)) + if err != nil { + return nil, fmt.Errorf("error converting from yaml to json: %v", err) + } + injson = string(injsonbytes) + outjson, err = Neat(injson) } - injson = string(injsonbytes) } else { injson = string(in) + outjson, err = Neat(injson) } - outjson, err = Neat(injson) if err != nil { return nil, fmt.Errorf("error neating : %v", err) } if outputFormat == "yaml" || (outputFormat == "same" && itsYaml) { + if hasDocs { + out = []byte(outyaml) + return + } out, err = yaml.JSONToYAML([]byte(outjson)) if err != nil { return nil, fmt.Errorf("error converting from json to yaml : %v", err) diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 538ae3a..7eb5b6e 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -39,7 +39,12 @@ func TestRootCmd(t *testing.T) { if err != nil { t.Errorf("error readin test data file %s: %v", resourceDataYAMLPath, err) } - + resourceDataMultiYAMLPath := "../test/fixtures/multidoc-raw.yaml" + resourceDataMultiYAMLBytes, err := ioutil.ReadFile(resourceDataMultiYAMLPath) + resourceDataMultiYAML := string(resourceDataMultiYAMLBytes) + if err != nil { + t.Errorf("error readin test data file %s: %v", resourceDataMultiYAMLPath, err) + } testcases := []struct { args []string stdin string @@ -64,6 +69,12 @@ func TestRootCmd(t *testing.T) { assertError: assertErrorNil, expOut: "apiVersion", }, + { + args: []string{}, + stdin: resourceDataMultiYAML, + assertError: assertErrorNil, + expOut: "apiVersion", + }, { args: []string{"-f", "-"}, stdin: resourceDataJSON, @@ -85,6 +96,12 @@ func TestRootCmd(t *testing.T) { assertError: assertErrorNil, expOut: "apiVersion", }, + { + args: []string{"-f", resourceDataMultiYAMLPath}, + stdin: "", + assertError: assertErrorNil, + expOut: "apiVersion", + }, { args: []string{"-f", resourceDataYAMLPath}, stdin: "", diff --git a/test/fixtures/multidoc-raw.yaml b/test/fixtures/multidoc-raw.yaml new file mode 100644 index 0000000..03c2a50 --- /dev/null +++ b/test/fixtures/multidoc-raw.yaml @@ -0,0 +1,159 @@ +apiVersion: v1 +data: + .dockerconfigjson: eyJhdXRocyI6eyJteXJlZ2lzdHJ5LnRlc3QiOnsidXNlcm5hbWUiOiJ1c2VyIiwicGFzc3dvcmQiOiJwYXNzIiwiZW1haWwiOiJ1c2VyQGVtYWlsLnRlc3QiLCJhdXRoIjoiZFhObGNqcHdZWE56In19fQ== +kind: Secret +metadata: + creationTimestamp: "2020-04-03T05:45:54Z" + name: myreg + namespace: default + resourceVersion: "3376" + selfLink: /api/v1/namespaces/default/secrets/myreg + uid: 62a187fd-756e-11ea-b27b-0242ac11002d +type: kubernetes.io/dockerconfigjson +--- +apiVersion: v1 +kind: Service +metadata: + creationTimestamp: "2019-04-24T20:12:14Z" + name: myappservice + namespace: default + resourceVersion: "187503" + selfLink: /api/v1/namespaces/default/services/myappservice + uid: 409de7fb-66cd-11e9-b6fa-0800271788ca +spec: + clusterIP: None + ports: + - port: 2222 + protocol: TCP + targetPort: 2222 + selector: + name: myapp + sessionAffinity: None + type: ClusterIP +status: + loadBalancer: {} +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + annotations: + hostPathProvisionerIdentity: 7de69121-4d7a-11e9-8684-0800271788ca + pv.kubernetes.io/provisioned-by: k8s.io/minikube-hostpath + creationTimestamp: "2019-03-23T14:52:51Z" + finalizers: + - kubernetes.io/pv-protection + name: pvc-54fad2fe-4d7b-11e9-9172-0800271788ca + resourceVersion: "186863" + selfLink: /api/v1/persistentvolumes/pvc-54fad2fe-4d7b-11e9-9172-0800271788ca + uid: 5527dbad-4d7b-11e9-9172-0800271788ca +spec: + accessModes: + - ReadWriteOnce + capacity: + storage: 2Gi + claimRef: + apiVersion: v1 + kind: PersistentVolumeClaim + name: prom-prometheus-alertmanager + namespace: default + resourceVersion: "860" + uid: 54fad2fe-4d7b-11e9-9172-0800271788ca + hostPath: + path: /tmp/hostpath-provisioner/pvc-54fad2fe-4d7b-11e9-9172-0800271788ca + type: "" + persistentVolumeReclaimPolicy: Delete + storageClassName: standard + volumeMode: Filesystem +status: + phase: Released +--- +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: "2019-04-24T19:55:27Z" + labels: + name: myapp + name: myapp + namespace: default + resourceVersion: "274103" + selfLink: /api/v1/namespaces/default/pods/myapp + uid: e8330f3c-66ca-11e9-b6fa-0800271788ca +spec: + containers: + - image: nginx + imagePullPolicy: Always + name: myapp + ports: + - containerPort: 1234 + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount + name: default-token-nmshj + readOnly: true + dnsPolicy: ClusterFirst + enableServiceLinks: true + nodeName: minikube + priority: 0 + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + serviceAccount: default + serviceAccountName: default + terminationGracePeriodSeconds: 30 + tolerations: + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 300 + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 300 + volumes: + - name: default-token-nmshj + secret: + defaultMode: 420 + secretName: default-token-nmshj +status: + conditions: + - lastProbeTime: null + lastTransitionTime: "2019-04-24T19:55:27Z" + status: "True" + type: Initialized + - lastProbeTime: null + lastTransitionTime: "2019-07-06T18:41:25Z" + status: "True" + type: Ready + - lastProbeTime: null + lastTransitionTime: "2019-07-06T18:41:25Z" + status: "True" + type: ContainersReady + - lastProbeTime: null + lastTransitionTime: "2019-04-24T19:55:27Z" + status: "True" + type: PodScheduled + containerStatuses: + - containerID: docker://92d7dc7a851453c2f1e75c4af42a9e72fea50127fede62dfbd5fbb6fb0481fcc + image: nginx:latest + imageID: docker-pullable://nginx@sha256:96fb261b66270b900ea5a2c17a26abbfabe95506e73c3a3c65869a6dbe83223a + lastState: + terminated: + containerID: docker://288fc0a2b98708d6a4661f59c54c4ae366c1acea642f000ba9615932dbff411f + exitCode: 0 + finishedAt: "2019-07-04T08:17:20Z" + reason: Completed + startedAt: "2019-07-03T05:55:39Z" + name: myapp + ready: true + restartCount: 3 + state: + running: + startedAt: "2019-07-06T18:41:25Z" + hostIP: 10.0.2.15 + phase: Running + podIP: 172.17.0.2 + qosClass: BestEffort + startTime: "2019-04-24T19:55:27Z"