diff --git a/Gopkg.lock b/Gopkg.lock index 79a0640cc..2f33900ea 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -217,9 +217,15 @@ packages = ["pkg/common"] revision = "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1" +[[projects]] + branch = "master" + name = "k8s.io/utils" + packages = ["exec"] + revision = "258e2a2fa64568210fbd6267cf1d8fd87c3cb86e" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "2b20626f5c5afea676ac5585d24de154de000f6a9fb0a307eb264a8cf3bd0253" + inputs-digest = "a778052416a71e5aca31256e71794eda11fed3c896b231ca36a045770abad827" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 49e444f40..ecd5b10a4 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -36,3 +36,7 @@ [[constraint]] branch = "master" name = "k8s.io/client-go" + +[[constraint]] + branch = "master" + name = "k8s.io/utils" diff --git a/pkg/kinflate/commands/commands.go b/pkg/kinflate/commands/commands.go index 3fdf3a6d7..434bf9be3 100644 --- a/pkg/kinflate/commands/commands.go +++ b/pkg/kinflate/commands/commands.go @@ -42,6 +42,7 @@ Find more information at: c.AddCommand( newCmdInflate(stdOut, stdErr), + newCmdDiff(stdOut, stdErr, fsys), newCmdInit(stdOut, stdErr, fsys), // 'add' sub command newCmdAdd(stdOut, stdErr, fsys), diff --git a/pkg/kinflate/commands/diff.go b/pkg/kinflate/commands/diff.go new file mode 100644 index 000000000..dcd38eb74 --- /dev/null +++ b/pkg/kinflate/commands/diff.go @@ -0,0 +1,107 @@ +/* +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 commands + +import ( + "errors" + "io" + + "github.com/spf13/cobra" + + "k8s.io/kubectl/pkg/kinflate/tree" + "k8s.io/kubectl/pkg/kinflate/util" + "k8s.io/kubectl/pkg/kinflate/util/fs" + "k8s.io/utils/exec" +) + +type diffOptions struct { + manifestPath string +} + +// newCmdDiff makes the diff command. +func newCmdDiff(out, errOut io.Writer, fs fs.FileSystem) *cobra.Command { + var o diffOptions + + cmd := &cobra.Command{ + Use: "diff", + Short: "diff between transformed resources and untransformed resources", + Long: "diff between transformed resources and untransformed resources and the subpackages are all transformed.", + Example: `diff -f .`, + RunE: func(cmd *cobra.Command, args []string) error { + err := o.Validate(cmd, args) + if err != nil { + return err + } + err = o.Complete(cmd, args) + if err != nil { + return err + } + return o.RunDiff(out, errOut, fs) + }, + } + + cmd.Flags().StringVarP(&o.manifestPath, "filename", "f", "", "Pass in a Kube-manifest.yaml file or a directory that contains the file.") + cmd.MarkFlagRequired("filename") + return cmd +} + +// Validate validates diff command. +func (o *diffOptions) Validate(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return errors.New("The diff command takes no arguments.") + } + return nil +} + +// Complete completes diff command. +func (o *diffOptions) Complete(cmd *cobra.Command, args []string) error { + return nil +} + +// RunInit writes a manifest file. +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, + } + + inflateOp := inflateOptions{manifestPath: o.manifestPath, mode: tree.ModeNormal} + kobj1, err := inflateOp.runInflate(fs) + if err != nil { + return err + } + transformedDir, err := util.WriteToDir(kobj1, "transformed", printer) + if err != nil { + return err + } + defer transformedDir.Delete() + + inflateNoOp := inflateOptions{manifestPath: o.manifestPath, mode: tree.ModeNoop} + kobj2, err := inflateNoOp.runInflate(fs) + if err != nil { + return err + } + noopDir, err := util.WriteToDir(kobj2, "noop", printer) + if err != nil { + return err + } + defer noopDir.Delete() + + return diff.Run(noopDir.Name, transformedDir.Name) +} diff --git a/pkg/kinflate/commands/inflate.go b/pkg/kinflate/commands/inflate.go index e50bcf4d5..03731ffcf 100644 --- a/pkg/kinflate/commands/inflate.go +++ b/pkg/kinflate/commands/inflate.go @@ -30,8 +30,9 @@ import ( ) type inflateOptions struct { + outputdir string manifestPath string - namespace string + mode tree.ModeType } // newCmdInflate creates a new inflate command. @@ -56,7 +57,7 @@ func newCmdInflate(out, errOut io.Writer) *cobra.Command { fmt.Fprintf(errOut, "error: %v\n", err) os.Exit(1) } - err = o.RunKinflate(out, errOut) + err = o.RunInflate(out, errOut) if err != nil { fmt.Fprintf(errOut, "error: %v\n", err) os.Exit(1) @@ -66,7 +67,6 @@ func newCmdInflate(out, errOut io.Writer) *cobra.Command { cmd.Flags().StringVarP(&o.manifestPath, "filename", "f", "", "Pass in a Kube-manifest.yaml file or a directory that contains the file.") cmd.MarkFlagRequired("filename") - cmd.Flags().StringVarP(&o.namespace, "namespace", "o", "yaml", "Output mode. Support json or yaml.") return cmd } @@ -77,26 +77,39 @@ func (o *inflateOptions) Validate(cmd *cobra.Command, args []string) error { // Complete completes inflate command. func (o *inflateOptions) Complete(cmd *cobra.Command, args []string) error { + o.mode = tree.ModeNormal return nil } -// RunKinflate runs inflate command (do real work). -func (o *inflateOptions) RunKinflate(out, errOut io.Writer) error { +// runInflate does the real transformation. +func (o *inflateOptions) runInflate(fs fs.FileSystem) (types.KObject, error) { // Build a tree of ManifestData. - loader := tree.Loader{FS: fs.MakeRealFS(), InitialPath: o.manifestPath} + loader := tree.Loader{FS: fs, InitialPath: o.manifestPath} root, err := loader.LoadManifestDataFromPath() if err != nil { - return err + return nil, err } // Do the transformation for the tree. - err = root.Inflate() + err = root.Inflate(o.mode) + if err != nil { + return nil, err + } + + return types.KObject(root.Resources), nil +} + +// RunInflate runs inflate command (do real work). +func (o *inflateOptions) RunInflate(out, errOut io.Writer) error { + fs := fs.MakeRealFS() + + kobj, err := o.runInflate(fs) if err != nil { return err } // Output the objects. - res, err := kutil.Encode(types.KObject(root.Resources)) + res, err := kutil.Encode(kobj) if err != nil { return err } diff --git a/pkg/kinflate/commands/inflate_test.go b/pkg/kinflate/commands/inflate_test.go index 3758083ff..641c1cbbf 100644 --- a/pkg/kinflate/commands/inflate_test.go +++ b/pkg/kinflate/commands/inflate_test.go @@ -28,12 +28,14 @@ import ( "gopkg.in/yaml.v2" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/kubectl/pkg/kinflate/tree" ) type InflateTestCase struct { - Description string `yaml:"description"` - Args []string `yaml:"args"` - Filename string `yaml:"filename"` + Description string `yaml:"description"` + Args []string `yaml:"args"` + Filename string `yaml:"filename"` + Mode tree.ModeType `yaml:"mode"` // path to the file that contains the expected output ExpectedStdout string `yaml:"expectedStdout"` } @@ -82,16 +84,18 @@ func TestInflate(t *testing.T) { t.Fatalf("%s: %v", name, err) } + if testcase.Mode == "" { + testcase.Mode = tree.ModeNormal + } + ops := &inflateOptions{ + manifestPath: testcase.Filename, + mode: testcase.Mode, + } buf := bytes.NewBuffer([]byte{}) - - cmd := newCmdInflate(buf, os.Stderr) - cmd.Flags().Set("filename", testcase.Filename) - - err = cmd.Execute() + err = ops.RunInflate(buf, os.Stderr) if err != nil { t.Errorf("unexpected error: %v", err) } - actualBytes := buf.Bytes() if !updateKinflateExpected { expectedBytes, err := ioutil.ReadFile(testcase.ExpectedStdout) diff --git a/pkg/kinflate/commands/testdata/testcase-base-only/test.yaml b/pkg/kinflate/commands/testdata/testcase-base-only/test.yaml index d59601097..6400b0df7 100644 --- a/pkg/kinflate/commands/testdata/testcase-base-only/test.yaml +++ b/pkg/kinflate/commands/testdata/testcase-base-only/test.yaml @@ -2,4 +2,3 @@ description: base only args: [] filename: testdata/testcase-base-only/in expectedStdout: testdata/testcase-base-only/expected.yaml -expectedExitCode: 0 diff --git a/pkg/kinflate/commands/testdata/testcase-noop/Kube-manifest.yaml b/pkg/kinflate/commands/testdata/testcase-noop/Kube-manifest.yaml new file mode 100644 index 000000000..eb43086a1 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/Kube-manifest.yaml @@ -0,0 +1,19 @@ +apiVersion: manifest.k8s.io/v1alpha1 +kind: Manifest +metadata: + name: mysql-wordpress +description: wordpress app with mysql +namePrefix: my-awesome-app- +objectAnnotations: + note: This is a test annotation +packages: + - mysql/ + - wordpress/ +patches: + - patch.yaml +configmaps: + - name: app-env + env: configmap/app.env + - name: app-config + files: + - configmap/app-init.ini diff --git a/pkg/kinflate/commands/testdata/testcase-noop/configmap/app-init.ini b/pkg/kinflate/commands/testdata/testcase-noop/configmap/app-init.ini new file mode 100644 index 000000000..8ebb8fcb3 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/configmap/app-init.ini @@ -0,0 +1,2 @@ +FOO=bar +BAR=baz diff --git a/pkg/kinflate/commands/testdata/testcase-noop/configmap/app.env b/pkg/kinflate/commands/testdata/testcase-noop/configmap/app.env new file mode 100644 index 000000000..c4032090a --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/configmap/app.env @@ -0,0 +1,2 @@ +DB_USERNAME=admin +DB_PASSWORD=somepw diff --git a/pkg/kinflate/commands/testdata/testcase-noop/expected.yaml b/pkg/kinflate/commands/testdata/testcase-noop/expected.yaml new file mode 100644 index 000000000..4b3cf0dad --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/expected.yaml @@ -0,0 +1,81 @@ +apiVersion: v1 +kind: Service +metadata: + annotations: + note: This is a test annotation + labels: + app: mysql + component: mysql + name: mysql +spec: + ports: + - port: 3306 + selector: + app: mysql + component: mysql +--- +apiVersion: v1 +kind: Service +metadata: + annotations: + note: This is a test annotation + labels: + app: wordpress + component: wordpress + name: wordpress +spec: + ports: + - port: 80 + selector: + app: wordpress + component: wordpress +--- +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + annotations: + note: This is a test annotation + labels: + app: mysql + component: mysql + name: mysql +spec: + selector: + matchLabels: + component: mysql + template: + metadata: + annotations: + note: This is a test annotation + labels: + app: mysql + component: mysql + spec: + containers: + - image: mysql + name: mysql +--- +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + annotations: + note: This is a test annotation + labels: + app: wordpress + component: wordpress + name: wordpress +spec: + selector: + matchLabels: + component: wordpress + template: + metadata: + annotations: + note: This is a test annotation + labels: + app: wordpress + component: wordpress + spec: + containers: + - image: wordpress + name: wordpress diff --git a/pkg/kinflate/commands/testdata/testcase-noop/mysql/Kube-manifest.yaml b/pkg/kinflate/commands/testdata/testcase-noop/mysql/Kube-manifest.yaml new file mode 100644 index 000000000..7c6edc354 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/mysql/Kube-manifest.yaml @@ -0,0 +1,12 @@ +apiVersion: manifest.k8s.io/v1alpha1 +kind: Manifest +metadata: + name: mysql-app +description: mysql app for team foo +objectLabels: + component: mysql +objectAnnotations: + note: This is a test annotation +resources: + - deployment.yaml + - service.yaml diff --git a/pkg/kinflate/commands/testdata/testcase-noop/mysql/deployment.yaml b/pkg/kinflate/commands/testdata/testcase-noop/mysql/deployment.yaml new file mode 100644 index 000000000..5a40824c6 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/mysql/deployment.yaml @@ -0,0 +1,15 @@ +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: mysql + labels: + app: mysql +spec: + template: + metadata: + labels: + app: mysql + spec: + containers: + - name: mysql + image: mysql diff --git a/pkg/kinflate/commands/testdata/testcase-noop/mysql/service.yaml b/pkg/kinflate/commands/testdata/testcase-noop/mysql/service.yaml new file mode 100644 index 000000000..aa3f970b6 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/mysql/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: mysql + labels: + app: mysql +spec: + ports: + - port: 3306 + selector: + app: mysql diff --git a/pkg/kinflate/commands/testdata/testcase-noop/patch.yaml b/pkg/kinflate/commands/testdata/testcase-noop/patch.yaml new file mode 100644 index 000000000..f96417288 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/patch.yaml @@ -0,0 +1,17 @@ +apiVersion: apps/v1beta2 # for versions before 1.9.0 use apps/v1beta2 +kind: Deployment +metadata: + name: wordpress +spec: + template: + spec: + containers: + - name: wordpress + env: + - name: WORDPRESS_DB_HOST + value: mysql + - name: WORDPRESS_DB_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-pass + key: password diff --git a/pkg/kinflate/commands/testdata/testcase-noop/test.yaml b/pkg/kinflate/commands/testdata/testcase-noop/test.yaml new file mode 100644 index 000000000..f1dba4d74 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/test.yaml @@ -0,0 +1,5 @@ +description: noop +args: [] +filename: testdata/testcase-noop/ +mode: noop_mode +expectedStdout: testdata/testcase-noop/expected.yaml diff --git a/pkg/kinflate/commands/testdata/testcase-noop/wordpress/Kube-manifest.yaml b/pkg/kinflate/commands/testdata/testcase-noop/wordpress/Kube-manifest.yaml new file mode 100644 index 000000000..b28a74785 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/wordpress/Kube-manifest.yaml @@ -0,0 +1,12 @@ +apiVersion: manifest.k8s.io/v1alpha1 +kind: Manifest +metadata: + name: wordpress +description: wordpress app +objectLabels: + component: wordpress +objectAnnotations: + note: This is a test annotation +resources: + - deployment.yaml + - service.yaml diff --git a/pkg/kinflate/commands/testdata/testcase-noop/wordpress/deployment.yaml b/pkg/kinflate/commands/testdata/testcase-noop/wordpress/deployment.yaml new file mode 100644 index 000000000..d83d51097 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/wordpress/deployment.yaml @@ -0,0 +1,15 @@ +apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: wordpress + labels: + app: wordpress +spec: + template: + metadata: + labels: + app: wordpress + spec: + containers: + - name: wordpress + image: wordpress diff --git a/pkg/kinflate/commands/testdata/testcase-noop/wordpress/service.yaml b/pkg/kinflate/commands/testdata/testcase-noop/wordpress/service.yaml new file mode 100644 index 000000000..d1708ad21 --- /dev/null +++ b/pkg/kinflate/commands/testdata/testcase-noop/wordpress/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: wordpress + labels: + app: wordpress +spec: + ports: + - port: 80 + selector: + app: wordpress diff --git a/pkg/kinflate/commands/testdata/testcase-simple/test.yaml b/pkg/kinflate/commands/testdata/testcase-simple/test.yaml index 451f1210b..0ba295193 100644 --- a/pkg/kinflate/commands/testdata/testcase-simple/test.yaml +++ b/pkg/kinflate/commands/testdata/testcase-simple/test.yaml @@ -2,4 +2,3 @@ description: simple args: [] filename: ../examples/simple/instances/exampleinstance/ expectedStdout: testdata/testcase-simple/expected.yaml -expectedExitCode: 0 diff --git a/pkg/kinflate/commands/testdata/testcase-single-overlay/test.yaml b/pkg/kinflate/commands/testdata/testcase-single-overlay/test.yaml index 9fbb42b29..aec1475e5 100644 --- a/pkg/kinflate/commands/testdata/testcase-single-overlay/test.yaml +++ b/pkg/kinflate/commands/testdata/testcase-single-overlay/test.yaml @@ -2,4 +2,3 @@ description: single overlay args: [] filename: testdata/testcase-single-overlay/in/overlay/ expectedStdout: testdata/testcase-single-overlay/expected.yaml -expectedExitCode: 0 diff --git a/pkg/kinflate/tree/node.go b/pkg/kinflate/tree/node.go index 30a9b60d8..298e29cda 100644 --- a/pkg/kinflate/tree/node.go +++ b/pkg/kinflate/tree/node.go @@ -17,6 +17,9 @@ limitations under the License. package tree import ( + "fmt" + + "k8s.io/kubectl/pkg/kinflate/transformers" "k8s.io/kubectl/pkg/kinflate/types" ) @@ -64,10 +67,42 @@ func (md *ManifestData) allResources() error { return types.Merge(md.Resources, md.Secrets) } +// ModeType is the option type for kinflate inflate +type ModeType string + +const ( + // ModeNormal means regular transformation. + ModeNormal ModeType = "normal_mode" + // ModeNoop means no transformation. + ModeNoop ModeType = "noop_mode" +) + +func (md *ManifestData) preprocess(mode ModeType) error { + switch mode { + case ModeNormal: + return md.allResources() + case ModeNoop: + return nil + default: + return fmt.Errorf("unknown mode for inflate") + } +} + +func (md *ManifestData) makeTransformer(mode ModeType) (transformers.Transformer, error) { + switch mode { + case ModeNormal: + return DefaultTransformer(md) + case ModeNoop: + return transformers.NewNoOpTransformer(), nil + default: + return transformers.NewNoOpTransformer(), fmt.Errorf("unknown mode for inflate") + } +} + // Inflate will recursively do the transformation on all the nodes below. -func (md *ManifestData) Inflate() error { +func (md *ManifestData) Inflate(mode ModeType) error { for _, pkg := range md.Packages { - err := pkg.Inflate() + err := pkg.Inflate(ModeNormal) if err != nil { return err } @@ -80,10 +115,14 @@ func (md *ManifestData) Inflate() error { } } - err := md.allResources() + err := md.preprocess(mode) + if err != nil { + return err + } + + t, err := md.makeTransformer(mode) if err != nil { return err } - t, err := DefaultTransformer(md) return t.Transform(types.KObject(md.Resources)) } diff --git a/pkg/kinflate/types/util.go b/pkg/kinflate/types/util.go index 43475710b..c5f445bbf 100644 --- a/pkg/kinflate/types/util.go +++ b/pkg/kinflate/types/util.go @@ -18,11 +18,19 @@ package types import ( "fmt" + "strings" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) +func (gvkn GroupVersionKindName) String() string { + if gvkn.GVK.Group == "" { + return strings.Join([]string{gvkn.GVK.Version, gvkn.GVK.Kind, gvkn.Name}, "_") + ".yaml" + } + return strings.Join([]string{gvkn.GVK.Group, gvkn.GVK.Version, gvkn.GVK.Kind, gvkn.Name}, "_") + ".yaml" +} + // SelectByGVK returns true if `selector` selects `in`; otherwise, false. // If `selector` and `in` are the same, return true. // If `selector` is nil, it is considered as a wildcard and always return true. diff --git a/pkg/kinflate/util/diff.go b/pkg/kinflate/util/diff.go new file mode 100644 index 000000000..dbcb0a351 --- /dev/null +++ b/pkg/kinflate/util/diff.go @@ -0,0 +1,104 @@ +/* +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/kinflate/util/util.go b/pkg/kinflate/util/util.go index 0d35cd1c9..30b206a3a 100644 --- a/pkg/kinflate/util/util.go +++ b/pkg/kinflate/util/util.go @@ -114,3 +114,24 @@ func Encode(in types.KObject) ([]byte, error) { } return buf.Bytes(), nil } + +// WriteToDir write each object in KObject to a file named with GroupVersionKindName. +func WriteToDir(in types.KObject, 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, f) + if err != nil { + return &Directory{}, err + } + } + return dir, nil +} diff --git a/vendor/k8s.io/utils/.travis.yml b/vendor/k8s.io/utils/.travis.yml new file mode 100644 index 000000000..d39066ccf --- /dev/null +++ b/vendor/k8s.io/utils/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - 1.8.x + - 1.9.x +go_import_path: k8s.io/utils +script: + - diff -u <(echo -n) <(gofmt -d .) + - go tool vet . + - go test -v -race ./... diff --git a/vendor/k8s.io/utils/HOWTOMOVE.md b/vendor/k8s.io/utils/HOWTOMOVE.md new file mode 100644 index 000000000..49028998f --- /dev/null +++ b/vendor/k8s.io/utils/HOWTOMOVE.md @@ -0,0 +1,31 @@ +# How to move a utility pkg from other kubernetes repos + +It has 2 steps to move a pkg from other Kubernetes repos to `k8s.io/utils` repo: +- copy the pkg to `k8s.io/utils` repo +- update the import paths and `vendor/` in the repos that refer this pkg + +## Copy the pkg to `k8s.io/utils` repo + +Copying should preserve all the git history associated with it. +[Here](http://gbayer.com/development/moving-files-from-one-git-repository-to-another-preserving-history/) is a working approach. +Note: You may need to use `--allow-unrelated-histories` if you get error when running `git pull` following the post above. + +Then, you may need to restructure the package to make sure it has the following structure. + + . + ├── doc.go # Description for this package + ├── .go # utility go file + ├── _test.go # go unit tests + └── testing # All the testing framework + └── fake_.go # Testing framework go file + +[#5](https://github.com/kubernetes/utils/pull/5) is an example for this step. + +## Update the repos that refer the pkg + +You should update the import paths. +Then follow [this doc](https://github.com/kubernetes/community/blob/master/contributors/devel/godep.md) to update `vendor/` and `Godeps/`. + +You may want to run `make bazel-test` to make sure all new references work. + +[kubernetes/kubernetes#49234](https://github.com/kubernetes/kubernetes/pull/49234) is an example for this step. diff --git a/vendor/k8s.io/utils/LICENSE b/vendor/k8s.io/utils/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/k8s.io/utils/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/k8s.io/utils/README.md b/vendor/k8s.io/utils/README.md new file mode 100644 index 000000000..3765eed26 --- /dev/null +++ b/vendor/k8s.io/utils/README.md @@ -0,0 +1,57 @@ +# Utils + +[![Build Status]](https://travis-ci.org/kubernetes/utils) [![GoDoc](https://godoc.org/k8s.io/utils?status.svg)](https://godoc.org/k8s.io/utils) + +A set of Go libraries that provide low-level, +kubernetes-independent packages supplementing the [Go +standard libs]. + +## Purpose + +As Kubernetes grows and spins functionality out of its +[core] and into cooperating repositories like +[apiserver], [kubectl], [kubeadm], etc., the need +arises for leaf repositories to house shared code and +avoid cycles in repository relationships. + +This repository is intended to hold shared utilities +with no Kubernetes dependence that may be of interest +to any Go project. See these [instructions for moving] +an existing package to this repository. + + +## Criteria for adding code here + +- Used by multiple Kubernetes repositories. + +- Full unit test coverage. + +- Go tools compliant (`go get`, `go test`, etc.). + +- Complex enough to be worth vendoring, rather than copying. + +- Stable, or backward compatible, API. + +- _No dependence on any Kubernetes repository_. + +## Libraries + +- [Exec](/exec) provides an interface for `os/exec`. It makes it easier + to mock and replace in tests, especially with + the [FakeExec](exec/testing/fake_exec.go) struct. + +- [Temp](/temp) provides an interface to create temporary directories. It also + provides a [FakeDir](temp/temptest) implementation to replace in tests. + +- [Clock](/clock) provides an interface for time-based operations. It allows + mocking time for testing. + +[Build Status]: https://travis-ci.org/kubernetes/utils.svg?branch=master +[Go standard libs]: https://golang.org/pkg/#stdlib +[api]: https://github.com/kubernetes/api +[apiserver]: https://github.com/kubernetes/apiserver +[core]: https://github.com/kubernetes/kubernetes +[ingress]: https://github.com/kubernetes/ingress +[kubeadm]: https://github.com/kubernetes/kubeadm +[kubectl]: https://github.com/kubernetes/kubectl +[instructions for moving]: ./HOWTOMOVE.md diff --git a/vendor/k8s.io/utils/clock/clock.go b/vendor/k8s.io/utils/clock/clock.go new file mode 100644 index 000000000..3d53c62b1 --- /dev/null +++ b/vendor/k8s.io/utils/clock/clock.go @@ -0,0 +1,94 @@ +/* +Copyright 2014 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 clock + +import "time" + +// Clock allows for injecting fake or real clocks into code that +// needs to do arbitrary things based on time. +type Clock interface { + Now() time.Time + Since(time.Time) time.Duration + After(d time.Duration) <-chan time.Time + NewTimer(d time.Duration) Timer + Sleep(d time.Duration) + Tick(d time.Duration) <-chan time.Time +} + +var _ = Clock(RealClock{}) + +// RealClock really calls time.Now() +type RealClock struct{} + +// Now returns the current time. +func (RealClock) Now() time.Time { + return time.Now() +} + +// Since returns time since the specified timestamp. +func (RealClock) Since(ts time.Time) time.Duration { + return time.Since(ts) +} + +// Same as time.After(d). +func (RealClock) After(d time.Duration) <-chan time.Time { + return time.After(d) +} + +func (RealClock) NewTimer(d time.Duration) Timer { + return &realTimer{ + timer: time.NewTimer(d), + } +} + +func (RealClock) Tick(d time.Duration) <-chan time.Time { + return time.Tick(d) +} + +func (RealClock) Sleep(d time.Duration) { + time.Sleep(d) +} + +// Timer allows for injecting fake or real timers into code that +// needs to do arbitrary things based on time. +type Timer interface { + C() <-chan time.Time + Stop() bool + Reset(d time.Duration) bool +} + +var _ = Timer(&realTimer{}) + +// realTimer is backed by an actual time.Timer. +type realTimer struct { + timer *time.Timer +} + +// C returns the underlying timer's channel. +func (r *realTimer) C() <-chan time.Time { + return r.timer.C +} + +// Stop calls Stop() on the underlying timer. +func (r *realTimer) Stop() bool { + return r.timer.Stop() +} + +// Reset calls Reset() on the underlying timer. +func (r *realTimer) Reset(d time.Duration) bool { + return r.timer.Reset(d) +} diff --git a/vendor/k8s.io/utils/clock/testing/fake_clock.go b/vendor/k8s.io/utils/clock/testing/fake_clock.go new file mode 100644 index 000000000..2ee48b739 --- /dev/null +++ b/vendor/k8s.io/utils/clock/testing/fake_clock.go @@ -0,0 +1,254 @@ +/* +Copyright 2014 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 testing + +import ( + "sync" + "time" + + "k8s.io/utils/clock" +) + +var ( + _ = clock.Clock(&FakeClock{}) + _ = clock.Clock(&IntervalClock{}) +) + +// FakeClock implements clock.Clock, but returns an arbitrary time. +type FakeClock struct { + lock sync.RWMutex + time time.Time + + // waiters are waiting for the fake time to pass their specified time + waiters []fakeClockWaiter +} + +type fakeClockWaiter struct { + targetTime time.Time + stepInterval time.Duration + skipIfBlocked bool + destChan chan time.Time + fired bool +} + +func NewFakeClock(t time.Time) *FakeClock { + return &FakeClock{ + time: t, + } +} + +// Now returns f's time. +func (f *FakeClock) Now() time.Time { + f.lock.RLock() + defer f.lock.RUnlock() + return f.time +} + +// Since returns time since the time in f. +func (f *FakeClock) Since(ts time.Time) time.Duration { + f.lock.RLock() + defer f.lock.RUnlock() + return f.time.Sub(ts) +} + +// Fake version of time.After(d). +func (f *FakeClock) After(d time.Duration) <-chan time.Time { + f.lock.Lock() + defer f.lock.Unlock() + stopTime := f.time.Add(d) + ch := make(chan time.Time, 1) // Don't block! + f.waiters = append(f.waiters, fakeClockWaiter{ + targetTime: stopTime, + destChan: ch, + }) + return ch +} + +// Fake version of time.NewTimer(d). +func (f *FakeClock) NewTimer(d time.Duration) clock.Timer { + f.lock.Lock() + defer f.lock.Unlock() + stopTime := f.time.Add(d) + ch := make(chan time.Time, 1) // Don't block! + timer := &fakeTimer{ + fakeClock: f, + waiter: fakeClockWaiter{ + targetTime: stopTime, + destChan: ch, + }, + } + f.waiters = append(f.waiters, timer.waiter) + return timer +} + +func (f *FakeClock) Tick(d time.Duration) <-chan time.Time { + f.lock.Lock() + defer f.lock.Unlock() + tickTime := f.time.Add(d) + ch := make(chan time.Time, 1) // hold one tick + f.waiters = append(f.waiters, fakeClockWaiter{ + targetTime: tickTime, + stepInterval: d, + skipIfBlocked: true, + destChan: ch, + }) + + return ch +} + +// Move clock by Duration, notify anyone that's called After, Tick, or NewTimer +func (f *FakeClock) Step(d time.Duration) { + f.lock.Lock() + defer f.lock.Unlock() + f.setTimeLocked(f.time.Add(d)) +} + +// Sets the time. +func (f *FakeClock) SetTime(t time.Time) { + f.lock.Lock() + defer f.lock.Unlock() + f.setTimeLocked(t) +} + +// Actually changes the time and checks any waiters. f must be write-locked. +func (f *FakeClock) setTimeLocked(t time.Time) { + f.time = t + newWaiters := make([]fakeClockWaiter, 0, len(f.waiters)) + for i := range f.waiters { + w := &f.waiters[i] + if !w.targetTime.After(t) { + + if w.skipIfBlocked { + select { + case w.destChan <- t: + w.fired = true + default: + } + } else { + w.destChan <- t + w.fired = true + } + + if w.stepInterval > 0 { + for !w.targetTime.After(t) { + w.targetTime = w.targetTime.Add(w.stepInterval) + } + newWaiters = append(newWaiters, *w) + } + + } else { + newWaiters = append(newWaiters, f.waiters[i]) + } + } + f.waiters = newWaiters +} + +// Returns true if After has been called on f but not yet satisfied (so you can +// write race-free tests). +func (f *FakeClock) HasWaiters() bool { + f.lock.RLock() + defer f.lock.RUnlock() + return len(f.waiters) > 0 +} + +func (f *FakeClock) Sleep(d time.Duration) { + f.Step(d) +} + +// IntervalClock implements clock.Clock, but each invocation of Now steps the clock forward the specified duration +type IntervalClock struct { + Time time.Time + Duration time.Duration +} + +// Now returns i's time. +func (i *IntervalClock) Now() time.Time { + i.Time = i.Time.Add(i.Duration) + return i.Time +} + +// Since returns time since the time in i. +func (i *IntervalClock) Since(ts time.Time) time.Duration { + return i.Time.Sub(ts) +} + +// Unimplemented, will panic. +// TODO: make interval clock use FakeClock so this can be implemented. +func (*IntervalClock) After(d time.Duration) <-chan time.Time { + panic("IntervalClock doesn't implement After") +} + +// Unimplemented, will panic. +// TODO: make interval clock use FakeClock so this can be implemented. +func (*IntervalClock) NewTimer(d time.Duration) clock.Timer { + panic("IntervalClock doesn't implement NewTimer") +} + +// Unimplemented, will panic. +// TODO: make interval clock use FakeClock so this can be implemented. +func (*IntervalClock) Tick(d time.Duration) <-chan time.Time { + panic("IntervalClock doesn't implement Tick") +} + +func (*IntervalClock) Sleep(d time.Duration) { + panic("IntervalClock doesn't implement Sleep") +} + +var _ = clock.Timer(&fakeTimer{}) + +// fakeTimer implements clock.Timer based on a FakeClock. +type fakeTimer struct { + fakeClock *FakeClock + waiter fakeClockWaiter +} + +// C returns the channel that notifies when this timer has fired. +func (f *fakeTimer) C() <-chan time.Time { + return f.waiter.destChan +} + +// Stop stops the timer and returns true if the timer has not yet fired, or false otherwise. +func (f *fakeTimer) Stop() bool { + f.fakeClock.lock.Lock() + defer f.fakeClock.lock.Unlock() + + newWaiters := make([]fakeClockWaiter, 0, len(f.fakeClock.waiters)) + for i := range f.fakeClock.waiters { + w := &f.fakeClock.waiters[i] + if w != &f.waiter { + newWaiters = append(newWaiters, *w) + } + } + + f.fakeClock.waiters = newWaiters + + return !f.waiter.fired +} + +// Reset resets the timer to the fake clock's "now" + d. It returns true if the timer has not yet +// fired, or false otherwise. +func (f *fakeTimer) Reset(d time.Duration) bool { + f.fakeClock.lock.Lock() + defer f.fakeClock.lock.Unlock() + + active := !f.waiter.fired + + f.waiter.fired = false + f.waiter.targetTime = f.fakeClock.time.Add(d) + + return active +} diff --git a/vendor/k8s.io/utils/clock/testing/fake_clock_test.go b/vendor/k8s.io/utils/clock/testing/fake_clock_test.go new file mode 100644 index 000000000..58fea841c --- /dev/null +++ b/vendor/k8s.io/utils/clock/testing/fake_clock_test.go @@ -0,0 +1,184 @@ +/* +Copyright 2015 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 testing + +import ( + "testing" + "time" +) + +func TestFakeClock(t *testing.T) { + startTime := time.Now() + tc := NewFakeClock(startTime) + tc.Step(time.Second) + now := tc.Now() + if now.Sub(startTime) != time.Second { + t.Errorf("input: %s now=%s gap=%s expected=%s", startTime, now, now.Sub(startTime), time.Second) + } + + tt := tc.Now() + tc.SetTime(tt.Add(time.Hour)) + if tc.Now().Sub(tt) != time.Hour { + t.Errorf("input: %s now=%s gap=%s expected=%s", tt, tc.Now(), tc.Now().Sub(tt), time.Hour) + } +} + +func TestFakeClockSleep(t *testing.T) { + startTime := time.Now() + tc := NewFakeClock(startTime) + tc.Sleep(time.Duration(1) * time.Hour) + now := tc.Now() + if now.Sub(startTime) != time.Hour { + t.Errorf("Fake sleep failed, expected time to advance by one hour, instead, its %v", now.Sub(startTime)) + } +} + +func TestFakeAfter(t *testing.T) { + tc := NewFakeClock(time.Now()) + if tc.HasWaiters() { + t.Errorf("unexpected waiter?") + } + oneSec := tc.After(time.Second) + if !tc.HasWaiters() { + t.Errorf("unexpected lack of waiter?") + } + + oneOhOneSec := tc.After(time.Second + time.Millisecond) + twoSec := tc.After(2 * time.Second) + select { + case <-oneSec: + t.Errorf("unexpected channel read") + case <-oneOhOneSec: + t.Errorf("unexpected channel read") + case <-twoSec: + t.Errorf("unexpected channel read") + default: + } + + tc.Step(999 * time.Millisecond) + select { + case <-oneSec: + t.Errorf("unexpected channel read") + case <-oneOhOneSec: + t.Errorf("unexpected channel read") + case <-twoSec: + t.Errorf("unexpected channel read") + default: + } + + tc.Step(time.Millisecond) + select { + case <-oneSec: + // Expected! + case <-oneOhOneSec: + t.Errorf("unexpected channel read") + case <-twoSec: + t.Errorf("unexpected channel read") + default: + t.Errorf("unexpected non-channel read") + } + tc.Step(time.Millisecond) + select { + case <-oneSec: + // should not double-trigger! + t.Errorf("unexpected channel read") + case <-oneOhOneSec: + // Expected! + case <-twoSec: + t.Errorf("unexpected channel read") + default: + t.Errorf("unexpected non-channel read") + } +} + +func TestFakeTick(t *testing.T) { + tc := NewFakeClock(time.Now()) + if tc.HasWaiters() { + t.Errorf("unexpected waiter?") + } + oneSec := tc.Tick(time.Second) + if !tc.HasWaiters() { + t.Errorf("unexpected lack of waiter?") + } + + oneOhOneSec := tc.Tick(time.Second + time.Millisecond) + twoSec := tc.Tick(2 * time.Second) + select { + case <-oneSec: + t.Errorf("unexpected channel read") + case <-oneOhOneSec: + t.Errorf("unexpected channel read") + case <-twoSec: + t.Errorf("unexpected channel read") + default: + } + + tc.Step(999 * time.Millisecond) // t=.999 + select { + case <-oneSec: + t.Errorf("unexpected channel read") + case <-oneOhOneSec: + t.Errorf("unexpected channel read") + case <-twoSec: + t.Errorf("unexpected channel read") + default: + } + + tc.Step(time.Millisecond) // t=1.000 + select { + case <-oneSec: + // Expected! + case <-oneOhOneSec: + t.Errorf("unexpected channel read") + case <-twoSec: + t.Errorf("unexpected channel read") + default: + t.Errorf("unexpected non-channel read") + } + tc.Step(time.Millisecond) // t=1.001 + select { + case <-oneSec: + // should not double-trigger! + t.Errorf("unexpected channel read") + case <-oneOhOneSec: + // Expected! + case <-twoSec: + t.Errorf("unexpected channel read") + default: + t.Errorf("unexpected non-channel read") + } + + tc.Step(time.Second) // t=2.001 + tc.Step(time.Second) // t=3.001 + tc.Step(time.Second) // t=4.001 + tc.Step(time.Second) // t=5.001 + + // The one second ticker should not accumulate ticks + accumulatedTicks := 0 + drained := false + for !drained { + select { + case <-oneSec: + accumulatedTicks++ + default: + drained = true + } + } + if accumulatedTicks != 1 { + t.Errorf("unexpected number of accumulated ticks: %d", accumulatedTicks) + } +} diff --git a/vendor/k8s.io/utils/code-of-conduct.md b/vendor/k8s.io/utils/code-of-conduct.md new file mode 100644 index 000000000..0d15c00cf --- /dev/null +++ b/vendor/k8s.io/utils/code-of-conduct.md @@ -0,0 +1,3 @@ +# Kubernetes Community Code of Conduct + +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/vendor/k8s.io/utils/exec/doc.go b/vendor/k8s.io/utils/exec/doc.go new file mode 100644 index 000000000..cbb44bdb5 --- /dev/null +++ b/vendor/k8s.io/utils/exec/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2017 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 exec provides an injectable interface and implementations for running commands. +package exec // import "k8s.io/utils/exec" diff --git a/vendor/k8s.io/utils/exec/exec.go b/vendor/k8s.io/utils/exec/exec.go new file mode 100644 index 000000000..07735d881 --- /dev/null +++ b/vendor/k8s.io/utils/exec/exec.go @@ -0,0 +1,215 @@ +/* +Copyright 2017 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 exec + +import ( + "context" + "io" + osexec "os/exec" + "syscall" + "time" +) + +// ErrExecutableNotFound is returned if the executable is not found. +var ErrExecutableNotFound = osexec.ErrNotFound + +// Interface is an interface that presents a subset of the os/exec API. Use this +// when you want to inject fakeable/mockable exec behavior. +type Interface interface { + // Command returns a Cmd instance which can be used to run a single command. + // This follows the pattern of package os/exec. + Command(cmd string, args ...string) Cmd + + // CommandContext returns a Cmd instance which can be used to run a single command. + // + // The provided context is used to kill the process if the context becomes done + // before the command completes on its own. For example, a timeout can be set in + // the context. + CommandContext(ctx context.Context, cmd string, args ...string) Cmd + + // LookPath wraps os/exec.LookPath + LookPath(file string) (string, error) +} + +// Cmd is an interface that presents an API that is very similar to Cmd from os/exec. +// As more functionality is needed, this can grow. Since Cmd is a struct, we will have +// to replace fields with get/set method pairs. +type Cmd interface { + // Run runs the command to the completion. + Run() error + // CombinedOutput runs the command and returns its combined standard output + // and standard error. This follows the pattern of package os/exec. + CombinedOutput() ([]byte, error) + // Output runs the command and returns standard output, but not standard err + Output() ([]byte, error) + SetDir(dir string) + SetStdin(in io.Reader) + SetStdout(out io.Writer) + SetStderr(out io.Writer) + // Stops the command by sending SIGTERM. It is not guaranteed the + // process will stop before this function returns. If the process is not + // responding, an internal timer function will send a SIGKILL to force + // terminate after 10 seconds. + Stop() +} + +// ExitError is an interface that presents an API similar to os.ProcessState, which is +// what ExitError from os/exec is. This is designed to make testing a bit easier and +// probably loses some of the cross-platform properties of the underlying library. +type ExitError interface { + String() string + Error() string + Exited() bool + ExitStatus() int +} + +// Implements Interface in terms of really exec()ing. +type executor struct{} + +// New returns a new Interface which will os/exec to run commands. +func New() Interface { + return &executor{} +} + +// Command is part of the Interface interface. +func (executor *executor) Command(cmd string, args ...string) Cmd { + return (*cmdWrapper)(osexec.Command(cmd, args...)) +} + +// CommandContext is part of the Interface interface. +func (executor *executor) CommandContext(ctx context.Context, cmd string, args ...string) Cmd { + return (*cmdWrapper)(osexec.CommandContext(ctx, cmd, args...)) +} + +// LookPath is part of the Interface interface +func (executor *executor) LookPath(file string) (string, error) { + return osexec.LookPath(file) +} + +// Wraps exec.Cmd so we can capture errors. +type cmdWrapper osexec.Cmd + +var _ Cmd = &cmdWrapper{} + +func (cmd *cmdWrapper) SetDir(dir string) { + cmd.Dir = dir +} + +func (cmd *cmdWrapper) SetStdin(in io.Reader) { + cmd.Stdin = in +} + +func (cmd *cmdWrapper) SetStdout(out io.Writer) { + cmd.Stdout = out +} + +func (cmd *cmdWrapper) SetStderr(out io.Writer) { + cmd.Stderr = out +} + +// Run is part of the Cmd interface. +func (cmd *cmdWrapper) Run() error { + err := (*osexec.Cmd)(cmd).Run() + return handleError(err) +} + +// CombinedOutput is part of the Cmd interface. +func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) { + out, err := (*osexec.Cmd)(cmd).CombinedOutput() + return out, handleError(err) +} + +func (cmd *cmdWrapper) Output() ([]byte, error) { + out, err := (*osexec.Cmd)(cmd).Output() + return out, handleError(err) +} + +// Stop is part of the Cmd interface. +func (cmd *cmdWrapper) Stop() { + c := (*osexec.Cmd)(cmd) + + if c.Process == nil { + return + } + + c.Process.Signal(syscall.SIGTERM) + + time.AfterFunc(10*time.Second, func() { + if !c.ProcessState.Exited() { + c.Process.Signal(syscall.SIGKILL) + } + }) +} + +func handleError(err error) error { + if err == nil { + return nil + } + + switch e := err.(type) { + case *osexec.ExitError: + return &ExitErrorWrapper{e} + case *osexec.Error: + if e.Err == osexec.ErrNotFound { + return ErrExecutableNotFound + } + } + + return err +} + +// ExitErrorWrapper is an implementation of ExitError in terms of os/exec ExitError. +// Note: standard exec.ExitError is type *os.ProcessState, which already implements Exited(). +type ExitErrorWrapper struct { + *osexec.ExitError +} + +var _ ExitError = &ExitErrorWrapper{} + +// ExitStatus is part of the ExitError interface. +func (eew ExitErrorWrapper) ExitStatus() int { + ws, ok := eew.Sys().(syscall.WaitStatus) + if !ok { + panic("can't call ExitStatus() on a non-WaitStatus exitErrorWrapper") + } + return ws.ExitStatus() +} + +// CodeExitError is an implementation of ExitError consisting of an error object +// and an exit code (the upper bits of os.exec.ExitStatus). +type CodeExitError struct { + Err error + Code int +} + +var _ ExitError = CodeExitError{} + +func (e CodeExitError) Error() string { + return e.Err.Error() +} + +func (e CodeExitError) String() string { + return e.Err.Error() +} + +func (e CodeExitError) Exited() bool { + return true +} + +func (e CodeExitError) ExitStatus() int { + return e.Code +} diff --git a/vendor/k8s.io/utils/exec/exec_test.go b/vendor/k8s.io/utils/exec/exec_test.go new file mode 100644 index 000000000..99ff44df0 --- /dev/null +++ b/vendor/k8s.io/utils/exec/exec_test.go @@ -0,0 +1,141 @@ +/* +Copyright 2017 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 exec + +import ( + "context" + osexec "os/exec" + "testing" + "time" +) + +func TestExecutorNoArgs(t *testing.T) { + ex := New() + + cmd := ex.Command("true") + out, err := cmd.CombinedOutput() + if err != nil { + t.Errorf("expected success, got %v", err) + } + if len(out) != 0 { + t.Errorf("expected no output, got %q", string(out)) + } + + cmd = ex.Command("false") + out, err = cmd.CombinedOutput() + if err == nil { + t.Errorf("expected failure, got nil error") + } + if len(out) != 0 { + t.Errorf("expected no output, got %q", string(out)) + } + ee, ok := err.(ExitError) + if !ok { + t.Errorf("expected an ExitError, got %+v", err) + } + if ee.Exited() { + if code := ee.ExitStatus(); code != 1 { + t.Errorf("expected exit status 1, got %d", code) + } + } + + cmd = ex.Command("/does/not/exist") + out, err = cmd.CombinedOutput() + if err == nil { + t.Errorf("expected failure, got nil error") + } + if ee, ok := err.(ExitError); ok { + t.Errorf("expected non-ExitError, got %+v", ee) + } +} + +func TestExecutorWithArgs(t *testing.T) { + ex := New() + + cmd := ex.Command("echo", "stdout") + out, err := cmd.CombinedOutput() + if err != nil { + t.Errorf("expected success, got %+v", err) + } + if string(out) != "stdout\n" { + t.Errorf("unexpected output: %q", string(out)) + } + + cmd = ex.Command("/bin/sh", "-c", "echo stderr > /dev/stderr") + out, err = cmd.CombinedOutput() + if err != nil { + t.Errorf("expected success, got %+v", err) + } + if string(out) != "stderr\n" { + t.Errorf("unexpected output: %q", string(out)) + } +} + +func TestLookPath(t *testing.T) { + ex := New() + + shExpected, _ := osexec.LookPath("sh") + sh, _ := ex.LookPath("sh") + if sh != shExpected { + t.Errorf("unexpected result for LookPath: got %s, expected %s", sh, shExpected) + } +} + +func TestExecutableNotFound(t *testing.T) { + exec := New() + + cmd := exec.Command("fake_executable_name") + _, err := cmd.CombinedOutput() + if err != ErrExecutableNotFound { + t.Errorf("cmd.CombinedOutput(): Expected error ErrExecutableNotFound but got %v", err) + } + + cmd = exec.Command("fake_executable_name") + _, err = cmd.Output() + if err != ErrExecutableNotFound { + t.Errorf("cmd.Output(): Expected error ErrExecutableNotFound but got %v", err) + } + + cmd = exec.Command("fake_executable_name") + err = cmd.Run() + if err != ErrExecutableNotFound { + t.Errorf("cmd.Run(): Expected error ErrExecutableNotFound but got %v", err) + } +} + +func TestStopBeforeStart(t *testing.T) { + cmd := New().Command("echo", "hello") + + // no panic calling Stop before calling Run + cmd.Stop() + + cmd.Run() + + // no panic calling Stop after command is done + cmd.Stop() +} + +func TestTimeout(t *testing.T) { + exec := New() + ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond) + defer cancel() + + err := exec.CommandContext(ctx, "sleep", "2").Run() + if err != context.DeadlineExceeded { + t.Errorf("expected %v but got %v", context.DeadlineExceeded, err) + } +} diff --git a/vendor/k8s.io/utils/exec/new_test.go b/vendor/k8s.io/utils/exec/new_test.go new file mode 100644 index 000000000..dba9bb350 --- /dev/null +++ b/vendor/k8s.io/utils/exec/new_test.go @@ -0,0 +1,37 @@ +/* +Copyright 2017 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 exec_test + +import ( + "bytes" + "fmt" + + "k8s.io/utils/exec" +) + +func ExampleNew() { + exec := exec.New() + + cmd := exec.Command("echo", "Bonjour!") + buff := bytes.Buffer{} + cmd.SetStdout(&buff) + if err := cmd.Run(); err != nil { + panic(err) + } + fmt.Println(buff.String()) + // Output: Bonjour! +} diff --git a/vendor/k8s.io/utils/exec/testing/fake_exec.go b/vendor/k8s.io/utils/exec/testing/fake_exec.go new file mode 100644 index 000000000..32cbae252 --- /dev/null +++ b/vendor/k8s.io/utils/exec/testing/fake_exec.go @@ -0,0 +1,158 @@ +/* +Copyright 2017 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 testingexec + +import ( + "context" + "fmt" + "io" + + "k8s.io/utils/exec" +) + +// A simple scripted Interface type. +type FakeExec struct { + CommandScript []FakeCommandAction + CommandCalls int + LookPathFunc func(string) (string, error) +} + +var _ exec.Interface = &FakeExec{} + +type FakeCommandAction func(cmd string, args ...string) exec.Cmd + +func (fake *FakeExec) Command(cmd string, args ...string) exec.Cmd { + if fake.CommandCalls > len(fake.CommandScript)-1 { + panic(fmt.Sprintf("ran out of Command() actions. Could not handle command [%d]: %s args: %v", fake.CommandCalls, cmd, args)) + } + i := fake.CommandCalls + fake.CommandCalls++ + return fake.CommandScript[i](cmd, args...) +} + +func (fake *FakeExec) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd { + return fake.Command(cmd, args...) +} + +func (fake *FakeExec) LookPath(file string) (string, error) { + return fake.LookPathFunc(file) +} + +// A simple scripted Cmd type. +type FakeCmd struct { + Argv []string + CombinedOutputScript []FakeCombinedOutputAction + CombinedOutputCalls int + CombinedOutputLog [][]string + RunScript []FakeRunAction + RunCalls int + RunLog [][]string + Dirs []string + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer +} + +var _ exec.Cmd = &FakeCmd{} + +func InitFakeCmd(fake *FakeCmd, cmd string, args ...string) exec.Cmd { + fake.Argv = append([]string{cmd}, args...) + return fake +} + +type FakeCombinedOutputAction func() ([]byte, error) +type FakeRunAction func() ([]byte, []byte, error) + +func (fake *FakeCmd) SetDir(dir string) { + fake.Dirs = append(fake.Dirs, dir) +} + +func (fake *FakeCmd) SetStdin(in io.Reader) { + fake.Stdin = in +} + +func (fake *FakeCmd) SetStdout(out io.Writer) { + fake.Stdout = out +} + +func (fake *FakeCmd) SetStderr(out io.Writer) { + fake.Stderr = out +} + +func (fake *FakeCmd) Run() error { + if fake.RunCalls > len(fake.RunScript)-1 { + panic("ran out of Run() actions") + } + if fake.RunLog == nil { + fake.RunLog = [][]string{} + } + i := fake.RunCalls + fake.RunLog = append(fake.RunLog, append([]string{}, fake.Argv...)) + fake.RunCalls++ + stdout, stderr, err := fake.RunScript[i]() + if stdout != nil { + fake.Stdout.Write(stdout) + } + if stderr != nil { + fake.Stderr.Write(stderr) + } + return err +} + +func (fake *FakeCmd) CombinedOutput() ([]byte, error) { + if fake.CombinedOutputCalls > len(fake.CombinedOutputScript)-1 { + panic("ran out of CombinedOutput() actions") + } + if fake.CombinedOutputLog == nil { + fake.CombinedOutputLog = [][]string{} + } + i := fake.CombinedOutputCalls + fake.CombinedOutputLog = append(fake.CombinedOutputLog, append([]string{}, fake.Argv...)) + fake.CombinedOutputCalls++ + return fake.CombinedOutputScript[i]() +} + +func (fake *FakeCmd) Output() ([]byte, error) { + return nil, fmt.Errorf("unimplemented") +} + +func (fake *FakeCmd) Stop() { + // no-op +} + +// A simple fake ExitError type. +type FakeExitError struct { + Status int +} + +var _ exec.ExitError = FakeExitError{} + +func (fake FakeExitError) String() string { + return fmt.Sprintf("exit %d", fake.Status) +} + +func (fake FakeExitError) Error() string { + return fake.String() +} + +func (fake FakeExitError) Exited() bool { + return true +} + +func (fake FakeExitError) ExitStatus() int { + return fake.Status +} diff --git a/vendor/k8s.io/utils/temp/dir.go b/vendor/k8s.io/utils/temp/dir.go new file mode 100644 index 000000000..f5e4487b3 --- /dev/null +++ b/vendor/k8s.io/utils/temp/dir.go @@ -0,0 +1,70 @@ +/* +Copyright 2017 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 temp + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" +) + +// Directory is an interface to a temporary directory, in which you can +// create new files. +type Directory interface { + // NewFile creates a new file in that directory. Calling NewFile + // with the same filename twice will result in an error. + NewFile(name string) (io.WriteCloser, error) + // Delete removes the directory and its content. + Delete() error +} + +// TempDir is wrapping an temporary directory on disk. +type TempDir struct { + // Name is the name (full path) of the created directory. + Name string +} + +var _ Directory = &TempDir{} + +// CreateTempDir returns a new Directory wrapping a temporary directory +// on disk. +func CreateTempDir(prefix string) (*TempDir, error) { + name, err := ioutil.TempDir("", fmt.Sprintf("%s-", prefix)) + if err != nil { + return nil, err + } + + return &TempDir{ + Name: name, + }, nil +} + +// NewFile creates a new file in the specified directory. +func (d *TempDir) NewFile(name string) (io.WriteCloser, error) { + return os.OpenFile( + filepath.Join(d.Name, name), + os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL, + 0700, + ) +} + +// Delete the underlying directory, and all of its content. +func (d *TempDir) Delete() error { + return os.RemoveAll(d.Name) +} diff --git a/vendor/k8s.io/utils/temp/dir_test.go b/vendor/k8s.io/utils/temp/dir_test.go new file mode 100644 index 000000000..0ec58c456 --- /dev/null +++ b/vendor/k8s.io/utils/temp/dir_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2017 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 temp + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestTempDir(t *testing.T) { + dir, err := CreateTempDir("prefix") + if err != nil { + t.Fatal(err) + } + + // Delete the directory no matter what. + defer dir.Delete() + + // Make sure we have created the dir, with the proper name + _, err = os.Stat(dir.Name) + if err != nil { + t.Fatal(err) + } + if !strings.HasPrefix(filepath.Base(dir.Name), "prefix") { + t.Fatalf(`Directory doesn't start with "prefix": %q`, + dir.Name) + } + + // Verify that the directory is empty + entries, err := ioutil.ReadDir(dir.Name) + if err != nil { + t.Fatal(err) + } + if len(entries) != 0 { + t.Fatalf("Directory should be empty, has %d elements", + len(entries)) + } + + // Create a couple of files + _, err = dir.NewFile("ONE") + if err != nil { + t.Fatal(err) + } + _, err = dir.NewFile("TWO") + if err != nil { + t.Fatal(err) + } + // We can't create the same file twice + _, err = dir.NewFile("TWO") + if err == nil { + t.Fatal("NewFile should fail to create the same file twice") + } + + // We have created only two files + entries, err = ioutil.ReadDir(dir.Name) + if err != nil { + t.Fatal(err) + } + if len(entries) != 2 { + t.Fatalf("ReadDir should have two elements, has %d elements", + len(entries)) + } + + // Verify that deletion works + err = dir.Delete() + if err != nil { + t.Fatal(err) + } + _, err = os.Stat(dir.Name) + if err == nil { + t.Fatal("Directory should be gone, still present.") + } +} diff --git a/vendor/k8s.io/utils/temp/doc.go b/vendor/k8s.io/utils/temp/doc.go new file mode 100644 index 000000000..b7e0c15ae --- /dev/null +++ b/vendor/k8s.io/utils/temp/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2017 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 temp provides an interface to handle temporary files and +// directories. +package temp // import "k8s.io/utils/temp" diff --git a/vendor/k8s.io/utils/temp/temptest/dir.go b/vendor/k8s.io/utils/temp/temptest/dir.go new file mode 100644 index 000000000..b142765fd --- /dev/null +++ b/vendor/k8s.io/utils/temp/temptest/dir.go @@ -0,0 +1,66 @@ +/* +Copyright 2017 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 temptest + +import ( + "errors" + "fmt" + "io" + + "k8s.io/utils/temp" +) + +// FakeDir implements a Directory that is not backed on the +// filesystem. This is useful for testing since the created "files" are +// simple bytes.Buffer that can be inspected. +type FakeDir struct { + Files map[string]*FakeFile + Deleted bool +} + +var _ temp.Directory = &FakeDir{} + +// NewFile returns a new FakeFile if the filename doesn't exist already. +// This function will fail if the directory has already been deleted. +func (d *FakeDir) NewFile(name string) (io.WriteCloser, error) { + if d.Deleted { + return nil, errors.New("can't create file in deleted FakeDir") + } + if d.Files == nil { + d.Files = map[string]*FakeFile{} + } + f := d.Files[name] + if f != nil { + return nil, fmt.Errorf( + "FakeDir already has file named %q", + name, + ) + } + f = &FakeFile{} + d.Files[name] = f + return f, nil +} + +// Delete doesn't remove anything, but records that the directory has +// been deleted. +func (d *FakeDir) Delete() error { + if d.Deleted { + return errors.New("failed to re-delete FakeDir") + } + d.Deleted = true + return nil +} diff --git a/vendor/k8s.io/utils/temp/temptest/dir_test.go b/vendor/k8s.io/utils/temp/temptest/dir_test.go new file mode 100644 index 000000000..6a4631f37 --- /dev/null +++ b/vendor/k8s.io/utils/temp/temptest/dir_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2017 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 temptest + +import ( + "io" + "testing" +) + +func TestFakeDir(t *testing.T) { + d := &FakeDir{} + + f, err := d.NewFile("ONE") + if err != nil { + t.Fatal(err) + } + + n, err := io.WriteString(f, "Bonjour!") + if n != 8 || err != nil { + t.Fatalf( + `WriteString(f, "Bonjour!") = (%v, %v), expected (%v, %v)`, + n, err, + 0, nil, + ) + } + if got := d.Files["ONE"].Buffer.String(); got != "Bonjour!" { + t.Fatalf(`file content is %q, expected "Bonjour!"`, got) + } + + f, err = d.NewFile("ONE") + if err == nil { + t.Fatal("Same file could be created twice.") + } + + err = d.Delete() + if err != nil { + t.Fatal(err) + } + + err = d.Delete() + if err == nil { + t.Fatal("FakeDir could be deleted twice.") + } + + f, err = d.NewFile("TWO") + if err == nil { + t.Fatal("NewFile could be created in deleted dir") + } + + if !d.Deleted { + t.Fatal("FakeDir should be deleted.") + } +} diff --git a/vendor/k8s.io/utils/temp/temptest/doc.go b/vendor/k8s.io/utils/temp/temptest/doc.go new file mode 100644 index 000000000..28c4f6a18 --- /dev/null +++ b/vendor/k8s.io/utils/temp/temptest/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2017 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 temptest provides utilities for testing temp +// files/directories testing. +package temptest diff --git a/vendor/k8s.io/utils/temp/temptest/example_test.go b/vendor/k8s.io/utils/temp/temptest/example_test.go new file mode 100644 index 000000000..491a614b3 --- /dev/null +++ b/vendor/k8s.io/utils/temp/temptest/example_test.go @@ -0,0 +1,57 @@ +/* +Copyright 2017 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 temptest + +import ( + "errors" + "fmt" + "io" + + "k8s.io/utils/temp" +) + +func TestedCode(dir temp.Directory) error { + f, err := dir.NewFile("filename") + if err != nil { + return err + } + _, err = io.WriteString(f, "Bonjour!") + if err != nil { + return err + } + return dir.Delete() +} + +func Example() { + dir := FakeDir{} + + err := TestedCode(&dir) + if err != nil { + panic(err) + } + + if dir.Deleted == false { + panic(errors.New("Directory should have been deleted.")) + } + + if dir.Files["filename"] == nil { + panic(errors.New(`"filename" should have been created.`)) + } + + fmt.Println(dir.Files["filename"].Buffer.String()) + // Output: Bonjour! +} diff --git a/vendor/k8s.io/utils/temp/temptest/file.go b/vendor/k8s.io/utils/temp/temptest/file.go new file mode 100644 index 000000000..a6375fb91 --- /dev/null +++ b/vendor/k8s.io/utils/temp/temptest/file.go @@ -0,0 +1,52 @@ +/* +Copyright 2017 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 temptest + +import ( + "bytes" + "errors" + "io" +) + +// FakeFile is an implementation of a WriteCloser, that records what has +// been written in the file (in a bytes.Buffer) and if the file has been +// closed. +type FakeFile struct { + Buffer bytes.Buffer + Closed bool +} + +var _ io.WriteCloser = &FakeFile{} + +// Write appends the contents of p to the Buffer. If the file has +// already been closed, an error is returned. +func (f *FakeFile) Write(p []byte) (n int, err error) { + if f.Closed { + return 0, errors.New("can't write to closed FakeFile") + } + return f.Buffer.Write(p) +} + +// Close records that the file has been closed. If the file has already +// been closed, an error is returned. +func (f *FakeFile) Close() error { + if f.Closed { + return errors.New("FakeFile was closed multiple times") + } + f.Closed = true + return nil +} diff --git a/vendor/k8s.io/utils/temp/temptest/file_test.go b/vendor/k8s.io/utils/temp/temptest/file_test.go new file mode 100644 index 000000000..0ea198f14 --- /dev/null +++ b/vendor/k8s.io/utils/temp/temptest/file_test.go @@ -0,0 +1,56 @@ +/* +Copyright 2017 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 temptest + +import ( + "io" + "testing" +) + +func TestFakeFile(t *testing.T) { + f := &FakeFile{} + + n, err := io.WriteString(f, "Bonjour!") + if n != 8 || err != nil { + t.Fatalf( + `WriteString(f, "Bonjour!") = (%v, %v), expected (%v, %v)`, + n, err, + 8, nil, + ) + } + + err = f.Close() + if err != nil { + t.Fatal(err) + } + + // File can't be closed twice. + err = f.Close() + if err == nil { + t.Fatal("FakeFile could be closed twice") + } + + // File is not writable after close. + n, err = io.WriteString(f, "Bonjour!") + if n != 0 || err == nil { + t.Fatalf( + `WriteString(f, "Bonjour!") = (%v, %v), expected (%v, %v)`, + n, err, + 0, "non-nil", + ) + } +}