From 34699f3ff06e2a9dcdd0e18bd3031b7f37d86f30 Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Thu, 21 Feb 2019 09:39:51 -0800 Subject: [PATCH 1/7] Add KustomizationVisitor --- staging/publishing/import-restrictions.yaml | 1 + .../pkg/genericclioptions/resource/BUILD | 2 + .../pkg/genericclioptions/resource/visitor.go | 20 ++ .../sample-cli-plugin/Godeps/Godeps.json | 232 ++++++++++++++++++ 4 files changed, 255 insertions(+) diff --git a/staging/publishing/import-restrictions.yaml b/staging/publishing/import-restrictions.yaml index 135d86454b5..05eef5ca12f 100644 --- a/staging/publishing/import-restrictions.yaml +++ b/staging/publishing/import-restrictions.yaml @@ -27,6 +27,7 @@ - k8s.io/api/core/v1 - k8s.io/cli-runtime/pkg/genericclioptions/printers - k8s.io/cli-runtime/pkg/genericclioptions/resource + - k8s.io/cli-runtime/pkg/kustomize - baseImportPath: "./vendor/k8s.io/apimachinery/" allowedImports: diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD index 22b34de0087..3221ba1eb70 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/BUILD @@ -35,12 +35,14 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library", + "//staging/src/k8s.io/cli-runtime/pkg/kustomize:go_default_library", "//staging/src/k8s.io/client-go/discovery:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/restmapper:go_default_library", "//vendor/golang.org/x/text/encoding/unicode:go_default_library", "//vendor/golang.org/x/text/transform:go_default_library", + "//vendor/sigs.k8s.io/kustomize/pkg/fs:go_default_library", ], ) diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go index b4e3b359a1c..a679ce90af2 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/visitor.go @@ -39,6 +39,8 @@ import ( utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/watch" + "k8s.io/cli-runtime/pkg/kustomize" + "sigs.k8s.io/kustomize/pkg/fs" ) const ( @@ -520,6 +522,24 @@ func (v *FileVisitor) Visit(fn VisitorFunc) error { return v.StreamVisitor.Visit(fn) } +// KustomizeVisitor is wrapper around a StreamVisitor, to handle Kustomization directories +type KustomizeVisitor struct { + Path string + *StreamVisitor +} + +// Visit in a KustomizeVisitor gets the output of Kustomize build and save it in the Streamvisitor +func (v *KustomizeVisitor) Visit(fn VisitorFunc) error { + fSys := fs.MakeRealFS() + var out bytes.Buffer + err := kustomize.RunKustomizeBuild(&out, fSys, v.Path) + if err != nil { + return err + } + v.StreamVisitor.Reader = bytes.NewReader(out.Bytes()) + return v.StreamVisitor.Visit(fn) +} + // StreamVisitor reads objects from an io.Reader and walks them. A stream visitor can only be // visited once. // TODO: depends on objects being in JSON format before being passed to decode - need to implement diff --git a/staging/src/k8s.io/sample-cli-plugin/Godeps/Godeps.json b/staging/src/k8s.io/sample-cli-plugin/Godeps/Godeps.json index e3eb79beb55..22b93712b0b 100644 --- a/staging/src/k8s.io/sample-cli-plugin/Godeps/Godeps.json +++ b/staging/src/k8s.io/sample-cli-plugin/Godeps/Godeps.json @@ -6,14 +6,50 @@ "./..." ], "Deps": [ + { + "ImportPath": "github.com/PuerkitoBio/purell", + "Rev": "8a290539e2e8629dbc4e6bad948158f790ec31f4" + }, + { + "ImportPath": "github.com/PuerkitoBio/urlesc", + "Rev": "5bd2802263f21d8788851d5305584c82a5c75d7e" + }, { "ImportPath": "github.com/davecgh/go-spew/spew", "Rev": "782f4967f2dc4564575ca782fe2d04090b5faca8" }, + { + "ImportPath": "github.com/emicklei/go-restful", + "Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46" + }, + { + "ImportPath": "github.com/emicklei/go-restful/log", + "Rev": "ff4f55a206334ef123e4f79bbf348980da81ca46" + }, { "ImportPath": "github.com/evanphx/json-patch", "Rev": "5858425f75500d40c52783dce87d085a483ce135" }, + { + "ImportPath": "github.com/ghodss/yaml", + "Rev": "c7ce16629ff4cd059ed96ed06419dd3856fd3577" + }, + { + "ImportPath": "github.com/go-openapi/jsonpointer", + "Rev": "ef5f0afec364d3b9396b7b77b43dbe26bf1f8004" + }, + { + "ImportPath": "github.com/go-openapi/jsonreference", + "Rev": "8483a886a90412cd6858df4ea3483dce9c8e35a3" + }, + { + "ImportPath": "github.com/go-openapi/spec", + "Rev": "5bae59e25b21498baea7f9d46e9c147ec106a42e" + }, + { + "ImportPath": "github.com/go-openapi/swag", + "Rev": "5899d5c5e619fda5fa86e14795a835f473ca284c" + }, { "ImportPath": "github.com/gogo/protobuf/proto", "Rev": "342cbe0a04158f6dcb03ca0079991a51a4248c02" @@ -82,6 +118,18 @@ "ImportPath": "github.com/json-iterator/go", "Rev": "ab8a2e0c74be9d3be70b3184d9acc634935ded82" }, + { + "ImportPath": "github.com/mailru/easyjson/buffer", + "Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d" + }, + { + "ImportPath": "github.com/mailru/easyjson/jlexer", + "Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d" + }, + { + "ImportPath": "github.com/mailru/easyjson/jwriter", + "Rev": "2f5df55504ebc322e4d52d34df6a1f5b503bf26d" + }, { "ImportPath": "github.com/modern-go/concurrent", "Rev": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" @@ -94,6 +142,10 @@ "ImportPath": "github.com/peterbourgon/diskv", "Rev": "5f041e8faa004a95c88a202771f4cc3e991971e6" }, + { + "ImportPath": "github.com/pkg/errors", + "Rev": "645ef00459ed84a119197bfb8d8205042c6df63d" + }, { "ImportPath": "github.com/spf13/cobra", "Rev": "c439c4fa093711d42e1b01acb1235b52004753c1" @@ -142,6 +194,10 @@ "ImportPath": "golang.org/x/sys/windows", "Rev": "95c6576299259db960f6c5b9b69ea52422860fce" }, + { + "ImportPath": "golang.org/x/text/cases", + "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" + }, { "ImportPath": "golang.org/x/text/encoding", "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" @@ -158,10 +214,22 @@ "ImportPath": "golang.org/x/text/encoding/unicode", "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" }, + { + "ImportPath": "golang.org/x/text/internal", + "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" + }, + { + "ImportPath": "golang.org/x/text/internal/tag", + "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" + }, { "ImportPath": "golang.org/x/text/internal/utf8internal", "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" }, + { + "ImportPath": "golang.org/x/text/language", + "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" + }, { "ImportPath": "golang.org/x/text/runes", "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" @@ -170,6 +238,10 @@ "ImportPath": "golang.org/x/text/secure/bidirule", "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" }, + { + "ImportPath": "golang.org/x/text/secure/precis", + "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" + }, { "ImportPath": "golang.org/x/text/transform", "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" @@ -182,6 +254,10 @@ "ImportPath": "golang.org/x/text/unicode/norm", "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" }, + { + "ImportPath": "golang.org/x/text/width", + "Rev": "b19bf474d317b857955b12035d2c5acb57ce8b01" + }, { "ImportPath": "golang.org/x/time/rate", "Rev": "f51c12702a4d776e4c1fa9b0fabab841babae631" @@ -326,6 +402,10 @@ "ImportPath": "k8s.io/api/storage/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/api/equality", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/api/errors", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -338,6 +418,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/api/resource", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/api/validation", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -350,6 +434,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructuredscheme", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/validation", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -430,6 +518,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/util/json", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/util/naming", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -446,6 +538,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/util/sets", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/pkg/util/validation", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -466,6 +562,10 @@ "ImportPath": "k8s.io/apimachinery/pkg/watch", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -482,6 +582,42 @@ "ImportPath": "k8s.io/cli-runtime/pkg/genericclioptions/resource", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, + { + "ImportPath": "k8s.io/cli-runtime/pkg/kustomize", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/cli-runtime/pkg/kustomize/k8sdeps", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/configmapandsecret", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kunstruct", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/kv", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/hash", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/transformer/patch", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, + { + "ImportPath": "k8s.io/cli-runtime/pkg/kustomize/k8sdeps/validator", + "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + }, { "ImportPath": "k8s.io/client-go/discovery", "Rev": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -582,10 +718,106 @@ "ImportPath": "k8s.io/klog", "Rev": "8139d8cb77af419532b33dfa7dd09fbc5f1d344f" }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/common", + "Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e" + }, + { + "ImportPath": "k8s.io/kube-openapi/pkg/util/proto", + "Rev": "d7c86cdc46e3a4fcf892b32dd7bc3aa775e0870e" + }, { "ImportPath": "k8s.io/utils/integer", "Rev": "ed37f7428a91fc2a81070808937195dcd46d320e" }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/commands/build", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/constants", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/expansion", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/factory", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/fs", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/git", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/gvk", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/ifc", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/ifc/transformer", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/image", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/internal/error", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/loader", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/patch", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/patch/transformer", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/resid", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/resmap", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/resource", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/target", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/transformers", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/transformers/config", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/transformers/config/defaultconfig", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, + { + "ImportPath": "sigs.k8s.io/kustomize/pkg/types", + "Rev": "ce7e5ee2c30cc5856fea01fe423cf167f2a2d0c3" + }, { "ImportPath": "sigs.k8s.io/yaml", "Rev": "fd68e9863619f6ec2fdd8625fe1f02e7c877e480" From 46f4378bd91fe441b2ff3396342fa261d704f367 Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Mon, 25 Feb 2019 16:29:27 -0800 Subject: [PATCH 2/7] add -k flag to FilenameFlags in cli-runtime --- .../artifacts/kustomization/configMap.yaml | 7 + .../artifacts/kustomization/deployment.yaml | 30 +++++ .../kustomization/kustomization.yaml | 5 + .../artifacts/kustomization/service.yaml | 12 ++ .../kustomization/should-not-create.yaml | 7 + .../kustomization/should-not-load.yaml | 8 ++ .../pkg/genericclioptions/filename_flags.go | 8 ++ .../pkg/genericclioptions/resource/builder.go | 27 ++++ .../resource/builder_test.go | 120 ++++++++++++++++++ 9 files changed, 224 insertions(+) create mode 100644 staging/src/k8s.io/cli-runtime/artifacts/kustomization/configMap.yaml create mode 100644 staging/src/k8s.io/cli-runtime/artifacts/kustomization/deployment.yaml create mode 100644 staging/src/k8s.io/cli-runtime/artifacts/kustomization/kustomization.yaml create mode 100644 staging/src/k8s.io/cli-runtime/artifacts/kustomization/service.yaml create mode 100644 staging/src/k8s.io/cli-runtime/artifacts/kustomization/should-not-create.yaml create mode 100644 staging/src/k8s.io/cli-runtime/artifacts/kustomization/should-not-load.yaml diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/configMap.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/configMap.yaml new file mode 100644 index 00000000000..e335ab8cc8a --- /dev/null +++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/configMap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: the-map +data: + altGreeting: "Good Morning!" + enableRisky: "false" diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/deployment.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/deployment.yaml new file mode 100644 index 00000000000..6e794090805 --- /dev/null +++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/deployment.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: the-deployment +spec: + replicas: 3 + template: + metadata: + labels: + deployment: hello + spec: + containers: + - name: the-container + image: monopole/hello:1 + command: ["/hello", + "--port=8080", + "--enableRiskyFeature=$(ENABLE_RISKY)"] + ports: + - containerPort: 8080 + env: + - name: ALT_GREETING + valueFrom: + configMapKeyRef: + name: the-map + key: altGreeting + - name: ENABLE_RISKY + valueFrom: + configMapKeyRef: + name: the-map + key: enableRisky diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/kustomization.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/kustomization.yaml new file mode 100644 index 00000000000..eef31957114 --- /dev/null +++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/kustomization.yaml @@ -0,0 +1,5 @@ +nameprefix: test- +resources: +- deployment.yaml +- service.yaml +- configMap.yaml diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/service.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/service.yaml new file mode 100644 index 00000000000..e238f70021b --- /dev/null +++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/service.yaml @@ -0,0 +1,12 @@ +kind: Service +apiVersion: v1 +metadata: + name: the-service +spec: + selector: + deployment: hello + type: LoadBalancer + ports: + - protocol: TCP + port: 8666 + targetPort: 8080 diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/should-not-create.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/should-not-create.yaml new file mode 100644 index 00000000000..a927e6b98b6 --- /dev/null +++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/should-not-create.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: should-not-create-map +data: + altGreeting: "Good Morning!" + enableRisky: "false" diff --git a/staging/src/k8s.io/cli-runtime/artifacts/kustomization/should-not-load.yaml b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/should-not-load.yaml new file mode 100644 index 00000000000..f9be4c33a14 --- /dev/null +++ b/staging/src/k8s.io/cli-runtime/artifacts/kustomization/should-not-load.yaml @@ -0,0 +1,8 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +nameprefix: test- +resources: +- deployment.yaml +- service.yaml +- configMap.yaml diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/filename_flags.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/filename_flags.go index 348a9c6368e..0d43c7808a4 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/filename_flags.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/filename_flags.go @@ -32,6 +32,7 @@ type FileNameFlags struct { Usage string Filenames *[]string + Kustomize *string Recursive *bool } @@ -48,6 +49,9 @@ func (o *FileNameFlags) ToOptions() resource.FilenameOptions { if o.Filenames != nil { options.Filenames = *o.Filenames } + if o.Kustomize != nil { + options.Kustomize = *o.Kustomize + } return options } @@ -68,4 +72,8 @@ func (o *FileNameFlags) AddFlags(flags *pflag.FlagSet) { } flags.SetAnnotation("filename", cobra.BashCompFilenameExt, annotations) } + if o.Kustomize != nil { + flags.StringVarP(o.Kustomize, "kustomize", "k", *o.Kustomize, + "Process a kustomization directory. This flag can't be used together with -f or -R.") + } } diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go index f3f8173a9c4..ad5247e9b4f 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go @@ -130,9 +130,28 @@ func IsUsageError(err error) bool { type FilenameOptions struct { Filenames []string + Kustomize string Recursive bool } +func (o *FilenameOptions) validate() []error { + var errs []error + if len(o.Filenames) > 0 && len(o.Kustomize) > 0 { + errs = append(errs, fmt.Errorf("only one of -f or -k can be specified")) + } + if len(o.Kustomize) > 0 && o.Recursive { + errs = append(errs, fmt.Errorf("-R is not allowed to work with -k")) + } + return errs +} + +func (o *FilenameOptions) RequireFilenameOrKustomize() error { + if len(o.Filenames) == 0 && len(o.Kustomize) == 0 { + return fmt.Errorf("must specify one of -f and -k") + } + return nil +} + type resourceTuple struct { Resource string Name string @@ -195,6 +214,10 @@ func (b *Builder) AddError(err error) *Builder { // If ContinueOnError() is set prior to this method, objects on the path that are not // recognized will be ignored (but logged at V(2)). func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *FilenameOptions) *Builder { + if errs := filenameOptions.validate(); len(errs) > 0 { + b.errs = append(b.errs, errs...) + return b + } recursive := filenameOptions.Recursive paths := filenameOptions.Filenames for _, s := range paths { @@ -215,6 +238,10 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename b.Path(recursive, s) } } + if filenameOptions.Kustomize != "" { + b.paths = append(b.paths, &KustomizeVisitor{filenameOptions.Kustomize, + NewStreamVisitor(nil, b.mapper, filenameOptions.Kustomize, b.schema)}) + } if enforceNamespace { b.RequireNamespace() diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go index 8aaba3ca22d..90b417b282f 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go @@ -374,6 +374,62 @@ func writeTestFile(t *testing.T, path string, contents string) { } } +func TestFilenameOptionsValidate(t *testing.T) { + testcases := []struct { + filenames []string + kustomize string + recursive bool + errExp bool + msgExp string + }{ + { + filenames: []string{"file"}, + kustomize: "dir", + errExp: true, + msgExp: "only one of -f or -k can be specified", + }, + { + kustomize: "dir", + recursive: true, + errExp: true, + msgExp: "-R is not allowed to work with -k", + }, + { + filenames: []string{"file"}, + errExp: false, + }, + { + filenames: []string{"dir"}, + recursive: true, + errExp: false, + }, + { + kustomize: "dir", + errExp: false, + }, + } + for _, testcase := range testcases { + o := &FilenameOptions{ + Kustomize: testcase.kustomize, + Filenames: testcase.filenames, + Recursive: testcase.recursive, + } + errs := o.validate() + if testcase.errExp { + if len(errs) == 0 { + t.Fatalf("expected error not happened") + } + if errs[0].Error() != testcase.msgExp { + t.Fatalf("expected %s, but got %#v", testcase.msgExp, errs[0]) + } + } else { + if len(errs) > 0 { + t.Fatalf("Unexpected error %#v", errs) + } + } + } +} + func TestPathBuilderWithMultiple(t *testing.T) { // create test dirs tmpDir, err := utiltesting.MkTmpdir("recursive_test_multiple") @@ -513,6 +569,70 @@ func TestDirectoryBuilder(t *testing.T) { } } +func TestKustomizeDirectoryBuilder(t *testing.T) { + tests := []struct { + directory string + expectErr bool + errMsg string + number int + expectedNames []string + }{ + { + directory: "../../../artifacts/guestbook", + expectErr: true, + errMsg: "No kustomization file found", + }, + // TODO(Liujingfang1): Fix this test in bazel test + //{ + // directory: "../../../artifacts/kustomization", + // expectErr: false, + // expectedNames: []string{"test-the-map", "test-the-deployment", "test-the-service"}, + //}, + { + directory: "../../../artifacts/kustomization/should-not-load.yaml", + expectErr: true, + errMsg: "must be a directory to be a root", + }, + } + for _, tt := range tests { + b := newDefaultBuilder(). + FilenameParam(false, &FilenameOptions{Kustomize: tt.directory}). + NamespaceParam("test").DefaultNamespace() + test := &testVisitor{} + err := b.Do().Visit(test.Handle) + if tt.expectErr { + if err == nil { + t.Fatalf("expected error unhappened") + } + if !strings.Contains(err.Error(), tt.errMsg) { + t.Fatalf("expected %s but got %s", tt.errMsg, err.Error()) + } + } else { + if err != nil || len(test.Infos) < tt.number { + t.Fatalf("unexpected response: %v %#v", err, test.Infos) + } + contained := func(name string) bool { + for _, info := range test.Infos { + if info.Name == name && info.Namespace == "test" && info.Object != nil { + return true + } + } + return false + } + + allFound := true + for _, name := range tt.expectedNames { + if !contained(name) { + allFound = false + } + } + if !allFound { + t.Errorf("unexpected responses: %#v", test.Infos) + } + } + } +} + func TestNamespaceOverride(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) From 0c026bfac163f0bb04283a7c2bf6b5a39ea1d3e0 Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Mon, 25 Feb 2019 16:30:27 -0800 Subject: [PATCH 3/7] add -k flags to kubectl subcommands --- pkg/kubectl/cmd/delete/delete_flags.go | 7 +++++-- pkg/kubectl/cmd/util/helpers.go | 10 ++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/kubectl/cmd/delete/delete_flags.go b/pkg/kubectl/cmd/delete/delete_flags.go index 4a65a9c1a81..0cab48b174e 100644 --- a/pkg/kubectl/cmd/delete/delete_flags.go +++ b/pkg/kubectl/cmd/delete/delete_flags.go @@ -156,9 +156,11 @@ func NewDeleteCommandFlags(usage string) *DeleteFlags { filenames := []string{} recursive := false + kustomize := "" return &DeleteFlags{ - FileNameFlags: &genericclioptions.FileNameFlags{Usage: usage, Filenames: &filenames, Recursive: &recursive}, + // Not using helpers.go since it provides function to add '-k' for FileNameOptions, but not FileNameFlags + FileNameFlags: &genericclioptions.FileNameFlags{Usage: usage, Filenames: &filenames, Kustomize: &kustomize, Recursive: &recursive}, LabelSelector: &labelSelector, FieldSelector: &fieldSelector, @@ -186,10 +188,11 @@ func NewDeleteFlags(usage string) *DeleteFlags { wait := false filenames := []string{} + kustomize := "" recursive := false return &DeleteFlags{ - FileNameFlags: &genericclioptions.FileNameFlags{Usage: usage, Filenames: &filenames, Recursive: &recursive}, + FileNameFlags: &genericclioptions.FileNameFlags{Usage: usage, Filenames: &filenames, Kustomize: &kustomize, Recursive: &recursive}, Cascade: &cascade, GracePeriod: &gracePeriod, diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index 0c6bf585a9c..e024d4e2124 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -291,8 +291,8 @@ func UsageErrorf(cmd *cobra.Command, format string, args ...interface{}) error { return fmt.Errorf("%s\nSee '%s -h' for help and examples", msg, cmd.CommandPath()) } -func IsFilenameSliceEmpty(filenames []string) bool { - return len(filenames) == 0 +func IsFilenameSliceEmpty(filenames []string, directory string) bool { + return len(filenames) == 0 && directory == "" } func GetFlagString(cmd *cobra.Command, flag string) string { @@ -382,6 +382,7 @@ func AddValidateOptionFlags(cmd *cobra.Command, options *ValidateOptions) { func AddFilenameOptionFlags(cmd *cobra.Command, options *resource.FilenameOptions, usage string) { AddJsonFilenameFlag(cmd.Flags(), &options.Filenames, "Filename, directory, or URL to files "+usage) + AddKustomizeFlag(cmd.Flags(), &options.Kustomize) cmd.Flags().BoolVarP(&options.Recursive, "recursive", "R", options.Recursive, "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.") } @@ -394,6 +395,11 @@ func AddJsonFilenameFlag(flags *pflag.FlagSet, value *[]string, usage string) { flags.SetAnnotation("filename", cobra.BashCompFilenameExt, annotations) } +// AddKustomizeFlag adds kustomize flag to a command +func AddKustomizeFlag(flags *pflag.FlagSet, value *string) { + flags.StringVarP(value, "kustomize", "k", *value, "Process the kustomization directory. This flag can't be used together with -f or -R.") +} + // AddDryRunFlag adds dry-run flag to a command. Usually used by mutations. func AddDryRunFlag(cmd *cobra.Command) { cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.") From b38cf738d6de4edc65d458fba2c353cf5d933178 Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Mon, 25 Feb 2019 16:30:50 -0800 Subject: [PATCH 4/7] update kubectl subcommand with -k changes --- pkg/kubectl/cmd/annotate/annotate.go | 2 +- pkg/kubectl/cmd/apply/apply.go | 10 ++++++++-- pkg/kubectl/cmd/auth/reconcile.go | 5 ++++- pkg/kubectl/cmd/certificates/certificates.go | 2 +- pkg/kubectl/cmd/convert/convert.go | 5 ++++- pkg/kubectl/cmd/create/create.go | 5 ++--- pkg/kubectl/cmd/delete/delete.go | 5 ++++- pkg/kubectl/cmd/describe/describe.go | 2 +- pkg/kubectl/cmd/diff/diff.go | 6 +++++- pkg/kubectl/cmd/get/get.go | 5 ++++- pkg/kubectl/cmd/label/label.go | 2 +- pkg/kubectl/cmd/replace/replace.go | 8 ++++++-- pkg/kubectl/cmd/rollout/rollout_history.go | 2 +- pkg/kubectl/cmd/rollout/rollout_pause.go | 2 +- pkg/kubectl/cmd/rollout/rollout_resume.go | 2 +- pkg/kubectl/cmd/rollout/rollout_status.go | 2 +- pkg/kubectl/cmd/rollout/rollout_undo.go | 2 +- pkg/kubectl/cmd/set/set_image.go | 2 +- 18 files changed, 47 insertions(+), 22 deletions(-) diff --git a/pkg/kubectl/cmd/annotate/annotate.go b/pkg/kubectl/cmd/annotate/annotate.go index 860cc10050c..fe8d15dc926 100644 --- a/pkg/kubectl/cmd/annotate/annotate.go +++ b/pkg/kubectl/cmd/annotate/annotate.go @@ -208,7 +208,7 @@ func (o AnnotateOptions) Validate() error { if o.all && len(o.fieldSelector) > 0 { return fmt.Errorf("cannot set --all and --field-selector at the same time") } - if len(o.resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { + if len(o.resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { return fmt.Errorf("one or more resources must be specified as or /") } if len(o.newAnnotations) < 1 && len(o.removeAnnotations) < 1 { diff --git a/pkg/kubectl/cmd/apply/apply.go b/pkg/kubectl/cmd/apply/apply.go index 11ed554822a..99393101b18 100644 --- a/pkg/kubectl/cmd/apply/apply.go +++ b/pkg/kubectl/cmd/apply/apply.go @@ -115,6 +115,9 @@ var ( # Apply the configuration in pod.json to a pod. kubectl apply -f ./pod.json + # Apply resources from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml. + kubectl apply -k dir/ + # Apply the JSON passed into stdin to a pod. cat pod.json | kubectl apply -f - @@ -152,7 +155,7 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions o.cmdBaseName = baseName cmd := &cobra.Command{ - Use: "apply -f FILENAME", + Use: "apply (-f FILENAME | -k DIRECTORY)", DisableFlagsInUseLine: true, Short: i18n.T("Apply a configuration to a resource by filename or stdin"), Long: applyLong, @@ -170,7 +173,6 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions o.RecordFlags.AddFlags(cmd) o.PrintFlags.AddFlags(cmd) - cmd.MarkFlagRequired("filename") cmd.Flags().BoolVar(&o.Overwrite, "overwrite", o.Overwrite, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration") cmd.Flags().BoolVar(&o.Prune, "prune", o.Prune, "Automatically delete resource objects, including the uninitialized ones, that do not appear in the configs and are created by either apply or create --save-config. Should be used with either -l or --all.") cmdutil.AddValidateFlags(cmd) @@ -237,6 +239,10 @@ func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { return err } o.DeleteOptions = o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams) + err = o.DeleteOptions.FilenameOptions.RequireFilenameOrKustomize() + if err != nil { + return err + } o.OpenAPISchema, _ = f.OpenAPISchema() o.Validator, err = f.Validator(cmdutil.GetFlagBool(cmd, "validate")) diff --git a/pkg/kubectl/cmd/auth/reconcile.go b/pkg/kubectl/cmd/auth/reconcile.go index 950192d8e96..b9de9d2efb9 100644 --- a/pkg/kubectl/cmd/auth/reconcile.go +++ b/pkg/kubectl/cmd/auth/reconcile.go @@ -107,13 +107,16 @@ func NewCmdReconcile(f cmdutil.Factory, streams genericclioptions.IOStreams) *co cmd.Flags().BoolVar(&o.DryRun, "dry-run", o.DryRun, "If true, display results but do not submit changes") cmd.Flags().BoolVar(&o.RemoveExtraPermissions, "remove-extra-permissions", o.RemoveExtraPermissions, "If true, removes extra permissions added to roles") cmd.Flags().BoolVar(&o.RemoveExtraSubjects, "remove-extra-subjects", o.RemoveExtraSubjects, "If true, removes extra subjects added to rolebindings") - cmd.MarkFlagRequired("filename") return cmd } // Complete completes all the required options func (o *ReconcileOptions) Complete(cmd *cobra.Command, f cmdutil.Factory, args []string) error { + if err := o.FilenameOptions.RequireFilenameOrKustomize(); err != nil { + return err + } + if len(args) > 0 { return errors.New("no arguments are allowed") } diff --git a/pkg/kubectl/cmd/certificates/certificates.go b/pkg/kubectl/cmd/certificates/certificates.go index fb537f2dd68..bd8d3707635 100644 --- a/pkg/kubectl/cmd/certificates/certificates.go +++ b/pkg/kubectl/cmd/certificates/certificates.go @@ -104,7 +104,7 @@ func (o *CertificateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg } func (o *CertificateOptions) Validate() error { - if len(o.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { + if len(o.csrNames) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { return fmt.Errorf("one or more CSRs must be specified as or -f ") } return nil diff --git a/pkg/kubectl/cmd/convert/convert.go b/pkg/kubectl/cmd/convert/convert.go index 506b7365774..b34a4aff91f 100644 --- a/pkg/kubectl/cmd/convert/convert.go +++ b/pkg/kubectl/cmd/convert/convert.go @@ -107,12 +107,15 @@ func NewCmdConvert(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *co cmdutil.AddValidateFlags(cmd) cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "to need to get converted.") - cmd.MarkFlagRequired("filename") return cmd } // Complete collects information required to run Convert command from command line. func (o *ConvertOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) (err error) { + err = o.FilenameOptions.RequireFilenameOrKustomize() + if err != nil { + return err + } o.builder = f.NewBuilder o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace() diff --git a/pkg/kubectl/cmd/create/create.go b/pkg/kubectl/cmd/create/create.go index 1a8f230bc2d..f005ee37e23 100644 --- a/pkg/kubectl/cmd/create/create.go +++ b/pkg/kubectl/cmd/create/create.go @@ -103,7 +103,8 @@ func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cob Long: createLong, Example: createExample, Run: func(cmd *cobra.Command, args []string) { - if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) { + if cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) { + ioStreams.ErrOut.Write([]byte("Error: must specify one of -f and -k\n\n")) defaultRunFunc := cmdutil.DefaultSubCommandRun(ioStreams.ErrOut) defaultRunFunc(cmd, args) return @@ -119,7 +120,6 @@ func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cob usage := "to use to create the resource" cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, usage) - cmd.MarkFlagRequired("filename") cmdutil.AddValidateFlags(cmd) cmd.Flags().BoolVar(&o.EditBeforeCreate, "edit", o.EditBeforeCreate, "Edit the API resource before creating") cmd.Flags().Bool("windows-line-endings", runtime.GOOS == "windows", @@ -184,7 +184,6 @@ func (o *CreateOptions) ValidateArgs(cmd *cobra.Command, args []string) error { // Complete completes all the required options func (o *CreateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { var err error - o.RecordFlags.Complete(cmd) o.Recorder, err = o.RecordFlags.ToRecorder() if err != nil { diff --git a/pkg/kubectl/cmd/delete/delete.go b/pkg/kubectl/cmd/delete/delete.go index eabc6c6df0f..0eedd205510 100644 --- a/pkg/kubectl/cmd/delete/delete.go +++ b/pkg/kubectl/cmd/delete/delete.go @@ -71,6 +71,9 @@ var ( # Delete a pod using the type and name specified in pod.json. kubectl delete -f ./pod.json + # Delete resources from a directory containing kustomization.yaml - e.g. dir/kustomization.yaml. + kubectl delete -k dir + # Delete a pod based on the type and name in the JSON passed into stdin. cat pod.json | kubectl delete -f - @@ -119,7 +122,7 @@ func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra deleteFlags := NewDeleteCommandFlags("containing the resource to delete.") cmd := &cobra.Command{ - Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])", + Use: "delete ([-f FILENAME] | [-k DIRECTORY] | TYPE [(NAME | -l label | --all)])", DisableFlagsInUseLine: true, Short: i18n.T("Delete resources by filenames, stdin, resources and names, or by resources and label selector"), Long: deleteLong, diff --git a/pkg/kubectl/cmd/describe/describe.go b/pkg/kubectl/cmd/describe/describe.go index b0f9cb1d480..bd857809028 100644 --- a/pkg/kubectl/cmd/describe/describe.go +++ b/pkg/kubectl/cmd/describe/describe.go @@ -131,7 +131,7 @@ func (o *DescribeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [ o.EnforceNamespace = false } - if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) { + if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) { return fmt.Errorf("You must specify the type of resource to describe. %s\n", cmdutil.SuggestAPIResources(o.CmdParent)) } diff --git a/pkg/kubectl/cmd/diff/diff.go b/pkg/kubectl/cmd/diff/diff.go index e64e8a0990d..5f681987673 100644 --- a/pkg/kubectl/cmd/diff/diff.go +++ b/pkg/kubectl/cmd/diff/diff.go @@ -118,7 +118,6 @@ func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C usage := "contains the configuration to diff" cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) cmdutil.AddServerSideApplyFlags(cmd) - cmd.MarkFlagRequired("filename") return cmd } @@ -395,6 +394,11 @@ func isConflict(err error) bool { func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { var err error + err = o.FilenameOptions.RequireFilenameOrKustomize() + if err != nil { + return err + } + o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd) o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd) if o.ForceConflicts && !o.ServerSideApply { diff --git a/pkg/kubectl/cmd/get/get.go b/pkg/kubectl/cmd/get/get.go index 73a450d36f2..b3fa41b3c5c 100644 --- a/pkg/kubectl/cmd/get/get.go +++ b/pkg/kubectl/cmd/get/get.go @@ -115,6 +115,9 @@ var ( # List a pod identified by type and name specified in "pod.yaml" in JSON output format. kubectl get -f pod.yaml -o json + # List resources from a directory with kustomization.yaml - e.g. dir/kustomization.yaml. + kubectl get -k dir/ + # Return only the phase value of the specified pod. kubectl get -o template pod/web-pod-13je7 --template={{.status.phase}} @@ -257,7 +260,7 @@ func (o *GetOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []stri switch { case o.Watch || o.WatchOnly: default: - if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { + if len(args) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { fmt.Fprintf(o.ErrOut, "You must specify the type of resource to get. %s\n\n", cmdutil.SuggestAPIResources(o.CmdParent)) fullCmdName := cmd.Parent().CommandPath() usageString := "Required resource not specified." diff --git a/pkg/kubectl/cmd/label/label.go b/pkg/kubectl/cmd/label/label.go index 63781453f53..eca1fbbbed1 100644 --- a/pkg/kubectl/cmd/label/label.go +++ b/pkg/kubectl/cmd/label/label.go @@ -205,7 +205,7 @@ func (o *LabelOptions) Validate() error { if o.all && len(o.fieldSelector) > 0 { return fmt.Errorf("cannot set --all and --field-selector at the same time") } - if len(o.resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) { + if len(o.resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) { return fmt.Errorf("one or more resources must be specified as or /") } if len(o.newLabels) < 1 && len(o.removeLabels) < 1 && !o.list { diff --git a/pkg/kubectl/cmd/replace/replace.go b/pkg/kubectl/cmd/replace/replace.go index dd329160e9f..9c3456074b0 100644 --- a/pkg/kubectl/cmd/replace/replace.go +++ b/pkg/kubectl/cmd/replace/replace.go @@ -117,7 +117,6 @@ func NewCmdReplace(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobr o.DeleteFlags.AddFlags(cmd) o.RecordFlags.AddFlags(cmd) - cmd.MarkFlagRequired("filename") cmdutil.AddValidateFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd) @@ -163,6 +162,11 @@ func (o *ReplaceOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] } o.DeleteOptions = deleteOpts + err = o.DeleteOptions.FilenameOptions.RequireFilenameOrKustomize() + if err != nil { + return err + } + schema, err := f.Validator(o.validate) if err != nil { return err @@ -189,7 +193,7 @@ func (o *ReplaceOptions) Validate(cmd *cobra.Command) error { return fmt.Errorf("--timeout must have --force specified") } - if cmdutil.IsFilenameSliceEmpty(o.DeleteOptions.FilenameOptions.Filenames) { + if cmdutil.IsFilenameSliceEmpty(o.DeleteOptions.FilenameOptions.Filenames, o.DeleteOptions.FilenameOptions.Kustomize) { return cmdutil.UsageErrorf(cmd, "Must specify --filename to replace") } diff --git a/pkg/kubectl/cmd/rollout/rollout_history.go b/pkg/kubectl/cmd/rollout/rollout_history.go index d9245508184..3661a624002 100644 --- a/pkg/kubectl/cmd/rollout/rollout_history.go +++ b/pkg/kubectl/cmd/rollout/rollout_history.go @@ -123,7 +123,7 @@ func (o *RolloutHistoryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, // Validate makes sure all the provided values for command-line options are valid func (o *RolloutHistoryOptions) Validate() error { - if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { + if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { return fmt.Errorf("required resource not specified") } if o.Revision < 0 { diff --git a/pkg/kubectl/cmd/rollout/rollout_pause.go b/pkg/kubectl/cmd/rollout/rollout_pause.go index b29963d6497..56dd4e1c8c5 100644 --- a/pkg/kubectl/cmd/rollout/rollout_pause.go +++ b/pkg/kubectl/cmd/rollout/rollout_pause.go @@ -117,7 +117,7 @@ func (o *PauseOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []st } func (o *PauseOptions) Validate() error { - if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { + if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { return fmt.Errorf("required resource not specified") } return nil diff --git a/pkg/kubectl/cmd/rollout/rollout_resume.go b/pkg/kubectl/cmd/rollout/rollout_resume.go index f1f9e4e33db..d46e9a6ec8b 100644 --- a/pkg/kubectl/cmd/rollout/rollout_resume.go +++ b/pkg/kubectl/cmd/rollout/rollout_resume.go @@ -121,7 +121,7 @@ func (o *ResumeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s } func (o *ResumeOptions) Validate() error { - if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { + if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { return fmt.Errorf("required resource not specified") } return nil diff --git a/pkg/kubectl/cmd/rollout/rollout_status.go b/pkg/kubectl/cmd/rollout/rollout_status.go index fde72df1d8b..38a16af9141 100644 --- a/pkg/kubectl/cmd/rollout/rollout_status.go +++ b/pkg/kubectl/cmd/rollout/rollout_status.go @@ -148,7 +148,7 @@ func (o *RolloutStatusOptions) Complete(f cmdutil.Factory, args []string) error // Validate makes sure all the provided values for command-line options are valid func (o *RolloutStatusOptions) Validate() error { - if len(o.BuilderArgs) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames) { + if len(o.BuilderArgs) == 0 && cmdutil.IsFilenameSliceEmpty(o.FilenameOptions.Filenames, o.FilenameOptions.Kustomize) { return fmt.Errorf("required resource not specified") } diff --git a/pkg/kubectl/cmd/rollout/rollout_undo.go b/pkg/kubectl/cmd/rollout/rollout_undo.go index c4db88af208..a06e5c5fec2 100644 --- a/pkg/kubectl/cmd/rollout/rollout_undo.go +++ b/pkg/kubectl/cmd/rollout/rollout_undo.go @@ -126,7 +126,7 @@ func (o *UndoOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str } func (o *UndoOptions) Validate() error { - if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { + if len(o.Resources) == 0 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { return fmt.Errorf("required resource not specified") } return nil diff --git a/pkg/kubectl/cmd/set/set_image.go b/pkg/kubectl/cmd/set/set_image.go index ef422879ba9..7cf1b13f360 100644 --- a/pkg/kubectl/cmd/set/set_image.go +++ b/pkg/kubectl/cmd/set/set_image.go @@ -198,7 +198,7 @@ func (o *SetImageOptions) Validate() error { if o.All && len(o.Selector) > 0 { errors = append(errors, fmt.Errorf("cannot set --all and --selector at the same time")) } - if len(o.Resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames) { + if len(o.Resources) < 1 && cmdutil.IsFilenameSliceEmpty(o.Filenames, o.Kustomize) { errors = append(errors, fmt.Errorf("one or more resources must be specified as or /")) } if len(o.ContainerImages) < 1 { From beacc87ebf6ea7006985e67094b5a86909464eb9 Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Mon, 25 Feb 2019 16:31:03 -0800 Subject: [PATCH 5/7] add cmdline tests for -k --- hack/testdata/kustomize/configmap.yaml | 7 +++++ hack/testdata/kustomize/deployment.yaml | 35 ++++++++++++++++++++++ hack/testdata/kustomize/kustomization.yaml | 5 ++++ hack/testdata/kustomize/service.yaml | 12 ++++++++ test/cmd/apply.sh | 15 ++++++++++ test/cmd/create.sh | 25 ++++++++++++++++ test/cmd/get.sh | 23 ++++++++++++++ test/cmd/legacy-script.sh | 3 ++ 8 files changed, 125 insertions(+) create mode 100644 hack/testdata/kustomize/configmap.yaml create mode 100644 hack/testdata/kustomize/deployment.yaml create mode 100644 hack/testdata/kustomize/kustomization.yaml create mode 100644 hack/testdata/kustomize/service.yaml diff --git a/hack/testdata/kustomize/configmap.yaml b/hack/testdata/kustomize/configmap.yaml new file mode 100644 index 00000000000..e335ab8cc8a --- /dev/null +++ b/hack/testdata/kustomize/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: the-map +data: + altGreeting: "Good Morning!" + enableRisky: "false" diff --git a/hack/testdata/kustomize/deployment.yaml b/hack/testdata/kustomize/deployment.yaml new file mode 100644 index 00000000000..13c096f487f --- /dev/null +++ b/hack/testdata/kustomize/deployment.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: the-deployment + labels: + deployment: hello +spec: + replicas: 3 + selector: + matchLabels: + deployment: hello + template: + metadata: + labels: + deployment: hello + spec: + containers: + - name: the-container + image: monopole/hello:1 + command: ["/hello", + "--port=8080", + "--enableRiskyFeature=$(ENABLE_RISKY)"] + ports: + - containerPort: 8080 + env: + - name: ALT_GREETING + valueFrom: + configMapKeyRef: + name: the-map + key: altGreeting + - name: ENABLE_RISKY + valueFrom: + configMapKeyRef: + name: the-map + key: enableRisky diff --git a/hack/testdata/kustomize/kustomization.yaml b/hack/testdata/kustomize/kustomization.yaml new file mode 100644 index 00000000000..b680da17c66 --- /dev/null +++ b/hack/testdata/kustomize/kustomization.yaml @@ -0,0 +1,5 @@ +nameprefix: test- +resources: +- deployment.yaml +- service.yaml +- configmap.yaml diff --git a/hack/testdata/kustomize/service.yaml b/hack/testdata/kustomize/service.yaml new file mode 100644 index 00000000000..e238f70021b --- /dev/null +++ b/hack/testdata/kustomize/service.yaml @@ -0,0 +1,12 @@ +kind: Service +apiVersion: v1 +metadata: + name: the-service +spec: + selector: + deployment: hello + type: LoadBalancer + ports: + - protocol: TCP + port: 8666 + targetPort: 8080 diff --git a/test/cmd/apply.sh b/test/cmd/apply.sh index be33cbc0cd0..6f230a645c0 100755 --- a/test/cmd/apply.sh +++ b/test/cmd/apply.sh @@ -223,6 +223,21 @@ __EOF__ # cleanup kubectl delete -f hack/testdata/service-revision2.yaml "${kube_flags[@]}" + ## kubectl apply -k somedir + kubectl apply -k hack/testdata/kustomize + kube::test::get_object_assert 'configmap test-the-map' "{{${id_field}}}" 'test-the-map' + kube::test::get_object_assert 'deployment test-the-deployment' "{{${id_field}}}" 'test-the-deployment' + kube::test::get_object_assert 'service test-the-service' "{{${id_field}}}" 'test-the-service' + # cleanup + kubectl delete configmap/test-the-map deployment/test-the-deployment service/test-the-service + + ## kubectl apply --kustomize somedir + kubectl apply --kustomize hack/testdata/kustomize + kube::test::get_object_assert 'configmap test-the-map' "{{${id_field}}}" 'test-the-map' + kube::test::get_object_assert 'deployment test-the-deployment' "{{${id_field}}}" 'test-the-deployment' + kube::test::get_object_assert 'service test-the-service' "{{${id_field}}}" 'test-the-service' + # cleanup + kubectl delete -k hack/testdata/kustomize set +o nounset set +o errexit diff --git a/test/cmd/create.sh b/test/cmd/create.sh index 5792883f569..e21b04d8cfb 100755 --- a/test/cmd/create.sh +++ b/test/cmd/create.sh @@ -108,3 +108,28 @@ run_create_job_tests() { set +o nounset set +o errexit } + +run_kubectl_create_kustomization_directory_tests() { + set -o nounset + set -o errexit + + ## kubectl create -k for kustomization directory + # Pre-condition: no ConfigMap, Deployment, Service exist + kube::test::get_object_assert configmaps "{{range.items}}{{$id_field}}:{{end}}" '' + kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" '' + kube::test::get_object_assert services "{{range.items}}{{$id_field}}:{{end}}" '' + # Command + kubectl create -k hack/testdata/kustomize + # Post-condition: test-the-map, test-the-deployment, test-the-service exist + + # Check that all items in the list are printed + kube::test::get_object_assert 'configmap test-the-map' "{{${id_field}}}" 'test-the-map' + kube::test::get_object_assert 'deployment test-the-deployment' "{{${id_field}}}" 'test-the-deployment' + kube::test::get_object_assert 'service test-the-service' "{{${id_field}}}" 'test-the-service' + + # cleanup + kubectl delete -k hack/testdata/kustomize + + set +o nounset + set +o errexit +} \ No newline at end of file diff --git a/test/cmd/get.sh b/test/cmd/get.sh index f14e65cb804..936abb6f57c 100755 --- a/test/cmd/get.sh +++ b/test/cmd/get.sh @@ -202,6 +202,29 @@ run_kubectl_get_tests() { # cleanup kubectl delete pods redis-master valid-pod "${kube_flags[@]}" + ### Test 'kubectl get -k ' prints all the items built from a kustomization directory + # Pre-condition: no ConfigMap, Deployment, Service exist + kube::test::get_object_assert configmaps "{{range.items}}{{$id_field}}:{{end}}" '' + kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" '' + kube::test::get_object_assert services "{{range.items}}{{$id_field}}:{{end}}" '' + # Command + kubectl apply -k hack/testdata/kustomize + # Post-condition: test-the-map, test-the-deployment, test-the-service exist + + # Check that all items in the list are printed + output_message=$(kubectl get -k hack/testdata/kustomize -o jsonpath="{..metadata.name}" "${kube_flags[@]}") + kube::test::if_has_string "${output_message}" "test-the-map" + kube::test::if_has_string "${output_message}" "test-the-deployment" + kube::test::if_has_string "${output_message}" "test-the-service" + + # cleanup + kubectl delete -k hack/testdata/kustomize + + # Check that all items in the list are deleted + kube::test::get_object_assert configmaps "{{range.items}}{{$id_field}}:{{end}}" '' + kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" '' + kube::test::get_object_assert services "{{range.items}}{{$id_field}}:{{end}}" '' + set +o nounset set +o errexit } diff --git a/test/cmd/legacy-script.sh b/test/cmd/legacy-script.sh index 3eea6394656..5489b16d751 100755 --- a/test/cmd/legacy-script.sh +++ b/test/cmd/legacy-script.sh @@ -492,6 +492,9 @@ runTests() { if kube::test::if_supports_resource "${secrets}" ; then record_command run_create_secret_tests fi + if kube::test::if_supports_resource "${deployments}"; then + record_command run_kubectl_create_kustomization_directory_tests + fi ###################### # Delete # From f55d87ab613e4a3af57ad1041e9fa5a005f86e18 Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Tue, 26 Feb 2019 09:48:51 -0800 Subject: [PATCH 6/7] fix the failed test --- .../resource/builder_test.go | 97 +++++++++++++++++-- 1 file changed, 91 insertions(+), 6 deletions(-) diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go index 90b417b282f..8c0fb836163 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go @@ -569,7 +569,93 @@ func TestDirectoryBuilder(t *testing.T) { } } +func setupKustomizeDirectory() (string, error) { + path, err := ioutil.TempDir("/tmp", "") + if err != nil { + return "", err + } + + contents := map[string]string{ + "configmap.yaml": ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: the-map +data: + altGreeting: "Good Morning!" + enableRisky: "false" +`, + "deployment.yaml": ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: the-deployment +spec: + replicas: 3 + template: + metadata: + labels: + deployment: hello + spec: + containers: + - name: the-container + image: monopole/hello:1 + command: ["/hello", + "--port=8080", + "--enableRiskyFeature=$(ENABLE_RISKY)"] + ports: + - containerPort: 8080 + env: + - name: ALT_GREETING + valueFrom: + configMapKeyRef: + name: the-map + key: altGreeting + - name: ENABLE_RISKY + valueFrom: + configMapKeyRef: + name: the-map + key: enableRisky +`, + "service.yaml": ` +kind: Service +apiVersion: v1 +metadata: + name: the-service +spec: + selector: + deployment: hello + type: LoadBalancer + ports: + - protocol: TCP + port: 8666 + targetPort: 8080 +`, + "kustomization.yaml": ` +nameprefix: test- +resources: +- deployment.yaml +- service.yaml +- configmap.yaml +`, + } + + for filename, content := range contents { + err = ioutil.WriteFile(filepath.Join(path, filename), []byte(content), 0660) + if err != nil { + return "", err + } + } + return path, nil +} + func TestKustomizeDirectoryBuilder(t *testing.T) { + dir, err := setupKustomizeDirectory() + if err != nil { + t.Fatalf("unexpected error %v", err) + } + defer os.RemoveAll(dir) + tests := []struct { directory string expectErr bool @@ -582,12 +668,11 @@ func TestKustomizeDirectoryBuilder(t *testing.T) { expectErr: true, errMsg: "No kustomization file found", }, - // TODO(Liujingfang1): Fix this test in bazel test - //{ - // directory: "../../../artifacts/kustomization", - // expectErr: false, - // expectedNames: []string{"test-the-map", "test-the-deployment", "test-the-service"}, - //}, + { + directory: dir, + expectErr: false, + expectedNames: []string{"test-the-map", "test-the-deployment", "test-the-service"}, + }, { directory: "../../../artifacts/kustomization/should-not-load.yaml", expectErr: true, From 127c2f1e7410b68510a850c44e7f6e3c63bb42f4 Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Tue, 26 Feb 2019 13:53:56 -0800 Subject: [PATCH 7/7] address comments --- .../cli-runtime/pkg/genericclioptions/resource/builder.go | 2 +- .../pkg/genericclioptions/resource/builder_test.go | 7 ++++++- test/cmd/apply.sh | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go index ad5247e9b4f..08528fa2981 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder.go @@ -140,7 +140,7 @@ func (o *FilenameOptions) validate() []error { errs = append(errs, fmt.Errorf("only one of -f or -k can be specified")) } if len(o.Kustomize) > 0 && o.Recursive { - errs = append(errs, fmt.Errorf("-R is not allowed to work with -k")) + errs = append(errs, fmt.Errorf("the -k flag can't be used with -f or -R")) } return errs } diff --git a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go index 8c0fb836163..d2e0d6ffec0 100644 --- a/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go +++ b/staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource/builder_test.go @@ -392,7 +392,7 @@ func TestFilenameOptionsValidate(t *testing.T) { kustomize: "dir", recursive: true, errExp: true, - msgExp: "-R is not allowed to work with -k", + msgExp: "the -k flag can't be used with -f or -R", }, { filenames: []string{"file"}, @@ -673,6 +673,11 @@ func TestKustomizeDirectoryBuilder(t *testing.T) { expectErr: false, expectedNames: []string{"test-the-map", "test-the-deployment", "test-the-service"}, }, + { + directory: filepath.Join(dir, "kustomization.yaml"), + expectErr: true, + errMsg: "must be a directory to be a root", + }, { directory: "../../../artifacts/kustomization/should-not-load.yaml", expectErr: true, diff --git a/test/cmd/apply.sh b/test/cmd/apply.sh index 6f230a645c0..2f9bac52db9 100755 --- a/test/cmd/apply.sh +++ b/test/cmd/apply.sh @@ -229,7 +229,7 @@ __EOF__ kube::test::get_object_assert 'deployment test-the-deployment' "{{${id_field}}}" 'test-the-deployment' kube::test::get_object_assert 'service test-the-service' "{{${id_field}}}" 'test-the-service' # cleanup - kubectl delete configmap/test-the-map deployment/test-the-deployment service/test-the-service + kubectl delete -k hack/testdata/kustomize ## kubectl apply --kustomize somedir kubectl apply --kustomize hack/testdata/kustomize @@ -237,7 +237,7 @@ __EOF__ kube::test::get_object_assert 'deployment test-the-deployment' "{{${id_field}}}" 'test-the-deployment' kube::test::get_object_assert 'service test-the-service' "{{${id_field}}}" 'test-the-service' # cleanup - kubectl delete -k hack/testdata/kustomize + kubectl delete --kustomize hack/testdata/kustomize set +o nounset set +o errexit