From 49f586af39a96f8b144045f2d732bdd578d77a80 Mon Sep 17 00:00:00 2001 From: Jeffrey Regan Date: Thu, 31 May 2018 16:50:34 -0700 Subject: [PATCH] Create and document diff package. --- pkg/commands/build.go | 3 +- pkg/commands/diff.go | 25 +---- pkg/diff/directory.go | 38 +++++++ pkg/diff/doc.go | 2 + pkg/diff/program.go | 59 ++++++++++ pkg/diff/rundiff.go | 63 +++++++++++ pkg/resource/resource.go | 42 ++++++- .../resource_test.go} | 37 +------ pkg/util/diff.go | 104 ------------------ pkg/util/util.go | 80 -------------- 10 files changed, 210 insertions(+), 243 deletions(-) create mode 100644 pkg/diff/directory.go create mode 100644 pkg/diff/doc.go create mode 100644 pkg/diff/program.go create mode 100644 pkg/diff/rundiff.go rename pkg/{util/util_test.go => resource/resource_test.go} (64%) delete mode 100644 pkg/util/diff.go delete mode 100644 pkg/util/util.go diff --git a/pkg/commands/build.go b/pkg/commands/build.go index 5a6be2372..32a842f45 100644 --- a/pkg/commands/build.go +++ b/pkg/commands/build.go @@ -27,7 +27,6 @@ import ( "github.com/kubernetes-sigs/kustomize/pkg/app" "github.com/kubernetes-sigs/kustomize/pkg/constants" "github.com/kubernetes-sigs/kustomize/pkg/loader" - kutil "github.com/kubernetes-sigs/kustomize/pkg/util" "github.com/kubernetes-sigs/kustomize/pkg/util/fs" ) @@ -94,7 +93,7 @@ func (o *buildOptions) RunBuild(out, errOut io.Writer, fs fs.FileSystem) error { } // Output the objects. - res, err := kutil.Encode(allResources) + res, err := allResources.EncodeAsYaml() if err != nil { return err } diff --git a/pkg/commands/diff.go b/pkg/commands/diff.go index 85c35073b..7166d31e2 100644 --- a/pkg/commands/diff.go +++ b/pkg/commands/diff.go @@ -25,10 +25,9 @@ import ( "github.com/kubernetes-sigs/kustomize/pkg/app" "github.com/kubernetes-sigs/kustomize/pkg/constants" + "github.com/kubernetes-sigs/kustomize/pkg/diff" "github.com/kubernetes-sigs/kustomize/pkg/loader" - "github.com/kubernetes-sigs/kustomize/pkg/util" "github.com/kubernetes-sigs/kustomize/pkg/util/fs" - "k8s.io/utils/exec" ) type diffOptions struct { @@ -68,12 +67,6 @@ func (o *diffOptions) Validate(cmd *cobra.Command, args []string) error { // RunDiff gets the differences between Application.Resources() and Application.RawResources(). func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error { - printer := util.Printer{} - diff := util.DiffProgram{ - Exec: exec.New(), - Stdout: out, - Stderr: errOut, - } l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)}) @@ -91,7 +84,7 @@ func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error { if err != nil { return err } - resources, err := application.Resources() + transformedResources, err := application.Resources() if err != nil { return err } @@ -100,17 +93,5 @@ func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error { return err } - transformedDir, err := util.WriteToDir(resources, "transformed", printer) - if err != nil { - return err - } - defer transformedDir.Delete() - - noopDir, err := util.WriteToDir(rawResources, "noop", printer) - if err != nil { - return err - } - defer noopDir.Delete() - - return diff.Run(noopDir.Name, transformedDir.Name) + return diff.RunDiff(rawResources, transformedResources, out, errOut) } diff --git a/pkg/diff/directory.go b/pkg/diff/directory.go new file mode 100644 index 000000000..b4308f10c --- /dev/null +++ b/pkg/diff/directory.go @@ -0,0 +1,38 @@ +package diff + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +// directory represents a new temp directory and lets one create files in it. +type directory struct { + n string +} + +// newDirectory makes a directory instance holding a new temp directory on disk. +// The directory name is the given prefix following by a random string. +func newDirectory(prefix string) (*directory, error) { + name, err := ioutil.TempDir("", prefix+"-") + if err != nil { + return nil, err + } + + return &directory{n: name}, nil +} + +// newFile creates a new file in the directory. +func (d *directory) newFile(name string) (*os.File, error) { + return os.OpenFile(filepath.Join(d.n, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700) +} + +// delete removes the directory recursively. +func (d *directory) delete() error { + return os.RemoveAll(d.n) +} + +// name is the name of the directory. +func (d *directory) name() string { + return d.n +} diff --git a/pkg/diff/doc.go b/pkg/diff/doc.go new file mode 100644 index 000000000..5921c481b --- /dev/null +++ b/pkg/diff/doc.go @@ -0,0 +1,2 @@ +// Package diff runs system diff to compare resource collections +package diff diff --git a/pkg/diff/program.go b/pkg/diff/program.go new file mode 100644 index 000000000..c6bd279e1 --- /dev/null +++ b/pkg/diff/program.go @@ -0,0 +1,59 @@ +/* +Copyright 2018 The Kubernetes 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 diff + +import ( + "io" + "os" + + "k8s.io/utils/exec" +) + +// program wraps the system diff program. +// If specified, the value of KUBERNETES_EXTERNAL_DIFF environment variable +// will be used instead of simply `diff(1)`. +type program struct { + stdout io.Writer + stderr io.Writer +} + +func newProgram(out, errOut io.Writer) *program { + return &program{ + stdout: out, + stderr: errOut, + } +} + +func (d *program) makeCommand(args ...string) exec.Cmd { + diff := "" + if envDiff := os.Getenv("KUBERNETES_EXTERNAL_DIFF"); envDiff != "" { + diff = envDiff + } else { + diff = "diff" + args = append([]string{"-u", "-N"}, args...) + } + cmd := exec.New().Command(diff, args...) + cmd.SetStdout(d.stdout) + cmd.SetStderr(d.stderr) + return cmd +} + +// run runs the detected diff program. `from` and `to` are the directory to diff. +func (d *program) run(from, to string) error { + d.makeCommand(from, to).Run() // Ignore diff return code + return nil +} diff --git a/pkg/diff/rundiff.go b/pkg/diff/rundiff.go new file mode 100644 index 000000000..785f6bdb8 --- /dev/null +++ b/pkg/diff/rundiff.go @@ -0,0 +1,63 @@ +package diff + +import ( + "github.com/ghodss/yaml" + + "io" + + "github.com/kubernetes-sigs/kustomize/pkg/resource" +) + +// RunDiff runs system diff program to compare two ResourceCollections. +func RunDiff(raw, transformed resource.ResourceCollection, + out, errOut io.Writer) error { + transformedDir, err := writeYamlToNewDir(transformed, "transformed") + if err != nil { + return err + } + defer transformedDir.delete() + + noopDir, err := writeYamlToNewDir(raw, "noop") + if err != nil { + return err + } + defer noopDir.delete() + + return newProgram(out, errOut).run(noopDir.name(), transformedDir.name()) +} + +// writeYamlToNewDir writes each obj in ResourceCollection to a file in a new directory. +// The directory's name will begin with the given prefix. +// Each file is named with GroupVersionKindName. +func writeYamlToNewDir(in resource.ResourceCollection, prefix string) (*directory, error) { + dir, err := newDirectory(prefix) + if err != nil { + return nil, err + } + + for gvkn, obj := range in { + f, err := dir.newFile(gvkn.String()) + if err != nil { + return nil, err + } + err = print(obj.Data, f) + f.Close() + if err != nil { + return nil, err + } + } + return dir, nil +} + +// Print the object as YAML. +func print(obj interface{}, w io.Writer) error { + if obj == nil { + return nil + } + data, err := yaml.Marshal(obj) + if err != nil { + return err + } + _, err = w.Write(data) + return err +} diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index 0bec076f1..f33037db3 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -17,7 +17,11 @@ limitations under the License. package resource import ( + "bytes" "encoding/json" + "sort" + + "github.com/ghodss/yaml" "github.com/kubernetes-sigs/kustomize/pkg/types" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -41,9 +45,6 @@ func (r *Resource) GVKN() types.GroupVersionKindName { return types.GroupVersionKindName{GVK: gvk, Name: r.Data.GetName()} } -// ResourceCollection is a map from GroupVersionKindName to Resource -type ResourceCollection map[types.GroupVersionKindName]*Resource - func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) { marshaled, err := json.Marshal(in) if err != nil { @@ -53,3 +54,38 @@ func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) err = out.UnmarshalJSON(marshaled) return &out, err } + +// ResourceCollection is a map from GroupVersionKindName to Resource +type ResourceCollection map[types.GroupVersionKindName]*Resource + +// EncodeAsYaml encodes the map `in` and output the encoded objects separated by `---`. +func (in ResourceCollection) EncodeAsYaml() ([]byte, error) { + gvknList := []types.GroupVersionKindName{} + for gvkn := range in { + gvknList = append(gvknList, gvkn) + } + sort.Sort(types.ByGVKN(gvknList)) + + firstObj := true + var b []byte + buf := bytes.NewBuffer(b) + for _, gvkn := range gvknList { + obj := in[gvkn].Data + out, err := yaml.Marshal(obj) + if err != nil { + return nil, err + } + if !firstObj { + _, err = buf.WriteString("---\n") + if err != nil { + return nil, err + } + } + _, err = buf.Write(out) + if err != nil { + return nil, err + } + firstObj = false + } + return buf.Bytes(), nil +} diff --git a/pkg/util/util_test.go b/pkg/resource/resource_test.go similarity index 64% rename from pkg/util/util_test.go rename to pkg/resource/resource_test.go index 3f7db3258..9d986fe15 100644 --- a/pkg/util/util_test.go +++ b/pkg/resource/resource_test.go @@ -14,15 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package resource import ( - "fmt" "reflect" "testing" - "github.com/kubernetes-sigs/kustomize/pkg/resource" - "github.com/kubernetes-sigs/kustomize/pkg/types" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -38,11 +35,11 @@ kind: ConfigMap metadata: name: cm2 `) - input := resource.ResourceCollection{ + input := ResourceCollection{ { GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Name: "cm1", - }: &resource.Resource{ + }: &Resource{ Data: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -56,7 +53,7 @@ metadata: { GVK: schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}, Name: "cm2", - }: &resource.Resource{ + }: &Resource{ Data: &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", @@ -68,7 +65,7 @@ metadata: }, }, } - out, err := Encode(input) + out, err := input.EncodeAsYaml() if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -76,27 +73,3 @@ metadata: t.Fatalf("%s doesn't match expected %s", out, encoded) } } - -func compareMap(m1, m2 resource.ResourceCollection) error { - if len(m1) != len(m2) { - keySet1 := []types.GroupVersionKindName{} - keySet2 := []types.GroupVersionKindName{} - for GVKn := range m1 { - keySet1 = append(keySet1, GVKn) - } - for GVKn := range m1 { - keySet2 = append(keySet2, GVKn) - } - return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2) - } - for GVKn, obj1 := range m1 { - obj2, found := m2[GVKn] - if !found { - return fmt.Errorf("%#v doesn't exist in %#v", GVKn, m2) - } - if !reflect.DeepEqual(obj1, obj2) { - return fmt.Errorf("%#v doesn't match %#v", obj1, obj2) - } - } - return nil -} diff --git a/pkg/util/diff.go b/pkg/util/diff.go deleted file mode 100644 index dbcb0a351..000000000 --- a/pkg/util/diff.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -Copyright 2018 The Kubernetes 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 util - -import ( - "io" - "io/ioutil" - "os" - "path/filepath" - - "github.com/ghodss/yaml" - - "k8s.io/utils/exec" -) - -// DiffProgram finds and run the diff program. The value of -// KUBERNETES_EXTERNAL_DIFF environment variable will be used a diff -// program. By default, `diff(1)` will be used. -type DiffProgram struct { - Exec exec.Interface - Stdout io.Writer - Stderr io.Writer -} - -func (d *DiffProgram) getCommand(args ...string) exec.Cmd { - diff := "" - if envDiff := os.Getenv("KUBERNETES_EXTERNAL_DIFF"); envDiff != "" { - diff = envDiff - } else { - diff = "diff" - args = append([]string{"-u", "-N"}, args...) - } - - cmd := d.Exec.Command(diff, args...) - cmd.SetStdout(d.Stdout) - cmd.SetStderr(d.Stderr) - - return cmd -} - -// Run runs the detected diff program. `from` and `to` are the directory to diff. -func (d *DiffProgram) Run(from, to string) error { - d.getCommand(from, to).Run() // Ignore diff return code - return nil -} - -// Printer is used to print an object. -type Printer struct{} - -// Print the object inside the writer w. -func (p *Printer) Print(obj interface{}, w io.Writer) error { - if obj == nil { - return nil - } - data, err := yaml.Marshal(obj) - if err != nil { - return err - } - _, err = w.Write(data) - return err - -} - -// Directory creates a new temp directory, and allows to easily create new files. -type Directory struct { - Name string -} - -// CreateDirectory does create the actual disk directory, and return a -// new representation of it. -func CreateDirectory(prefix string) (*Directory, error) { - name, err := ioutil.TempDir("", prefix+"-") - if err != nil { - return nil, err - } - - return &Directory{ - Name: name, - }, nil -} - -// NewFile creates a new file in the directory. -func (d *Directory) NewFile(name string) (*os.File, error) { - return os.OpenFile(filepath.Join(d.Name, name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700) -} - -// Delete removes the directory recursively. -func (d *Directory) Delete() error { - return os.RemoveAll(d.Name) -} diff --git a/pkg/util/util.go b/pkg/util/util.go deleted file mode 100644 index 736483279..000000000 --- a/pkg/util/util.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright 2018 The Kubernetes 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 util - -import ( - "bytes" - "sort" - - "github.com/ghodss/yaml" - - "github.com/kubernetes-sigs/kustomize/pkg/resource" - "github.com/kubernetes-sigs/kustomize/pkg/types" -) - -// Encode encodes the map `in` and output the encoded objects separated by `---`. -func Encode(in resource.ResourceCollection) ([]byte, error) { - gvknList := []types.GroupVersionKindName{} - for gvkn := range in { - gvknList = append(gvknList, gvkn) - } - sort.Sort(types.ByGVKN(gvknList)) - - firstObj := true - var b []byte - buf := bytes.NewBuffer(b) - for _, gvkn := range gvknList { - obj := in[gvkn].Data - out, err := yaml.Marshal(obj) - if err != nil { - return nil, err - } - if !firstObj { - _, err = buf.WriteString("---\n") - if err != nil { - return nil, err - } - } - _, err = buf.Write(out) - if err != nil { - return nil, err - } - firstObj = false - } - return buf.Bytes(), nil -} - -// WriteToDir write each object in ResourceCollection to a file named with GroupVersionKindName. -func WriteToDir(in resource.ResourceCollection, dirName string, printer Printer) (*Directory, error) { - dir, err := CreateDirectory(dirName) - if err != nil { - return &Directory{}, err - } - - for gvkn, obj := range in { - f, err := dir.NewFile(gvkn.String()) - if err != nil { - return &Directory{}, err - } - defer f.Close() - err = printer.Print(obj.Data, f) - if err != nil { - return &Directory{}, err - } - } - return dir, nil -}