From 9bace3e3794cc6198d97cdb545818f1d5620fdf1 Mon Sep 17 00:00:00 2001 From: Shiyang Wang Date: Thu, 16 Feb 2017 16:21:19 +0800 Subject: [PATCH] add apply-set-last-applied subcommand update update code update unit tests hack/update remove spew update bazel updated add comments remove unused parameter remove hardcode bump unit tests add new flags add unit tests add bazel genreate doc --- docs/.generated_docs | 2 + .../man/man1/kubectl-apply-set-last-applied.1 | 3 + .../kubectl/kubectl_apply_set-last-applied.md | 3 + hack/verify-flags/known-flags.txt | 1 + pkg/kubectl/cmd/BUILD | 1 + pkg/kubectl/cmd/apply.go | 1 + pkg/kubectl/cmd/apply_set_last_applied.go | 241 ++++++++++++++++++ pkg/kubectl/cmd/apply_test.go | 137 +++++++++- .../fixtures/pkg/kubectl/cmd/apply/patch.json | 1 + .../kubectl/cmd/apply/rc-no-annotation.yaml | 18 ++ .../pkg/kubectl/cmd/apply/rc-noexist.yaml | 18 ++ test/fixtures/pkg/kubectl/cmd/apply/rc.json | 33 +++ .../pkg/kubectl/cmd/apply/testdir/rc.yaml | 18 ++ .../pkg/kubectl/cmd/apply/testdir/rc1.yaml | 18 ++ 14 files changed, 491 insertions(+), 4 deletions(-) create mode 100644 docs/man/man1/kubectl-apply-set-last-applied.1 create mode 100644 docs/user-guide/kubectl/kubectl_apply_set-last-applied.md create mode 100644 pkg/kubectl/cmd/apply_set_last_applied.go create mode 100644 test/fixtures/pkg/kubectl/cmd/apply/patch.json create mode 100644 test/fixtures/pkg/kubectl/cmd/apply/rc-no-annotation.yaml create mode 100644 test/fixtures/pkg/kubectl/cmd/apply/rc-noexist.yaml create mode 100644 test/fixtures/pkg/kubectl/cmd/apply/rc.json create mode 100644 test/fixtures/pkg/kubectl/cmd/apply/testdir/rc.yaml create mode 100644 test/fixtures/pkg/kubectl/cmd/apply/testdir/rc1.yaml diff --git a/docs/.generated_docs b/docs/.generated_docs index efcaad246a8..b77f49f9452 100644 --- a/docs/.generated_docs +++ b/docs/.generated_docs @@ -12,6 +12,7 @@ docs/man/man1/kube-proxy.1 docs/man/man1/kube-scheduler.1 docs/man/man1/kubectl-annotate.1 docs/man/man1/kubectl-api-versions.1 +docs/man/man1/kubectl-apply-set-last-applied.1 docs/man/man1/kubectl-apply-view-last-applied.1 docs/man/man1/kubectl-apply.1 docs/man/man1/kubectl-attach.1 @@ -99,6 +100,7 @@ docs/user-guide/kubectl/kubectl.md docs/user-guide/kubectl/kubectl_annotate.md docs/user-guide/kubectl/kubectl_api-versions.md docs/user-guide/kubectl/kubectl_apply.md +docs/user-guide/kubectl/kubectl_apply_set-last-applied.md docs/user-guide/kubectl/kubectl_apply_view-last-applied.md docs/user-guide/kubectl/kubectl_attach.md docs/user-guide/kubectl/kubectl_autoscale.md diff --git a/docs/man/man1/kubectl-apply-set-last-applied.1 b/docs/man/man1/kubectl-apply-set-last-applied.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-apply-set-last-applied.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/user-guide/kubectl/kubectl_apply_set-last-applied.md b/docs/user-guide/kubectl/kubectl_apply_set-last-applied.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_apply_set-last-applied.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index 1a18d7dd9aa..576cad1da2b 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -121,6 +121,7 @@ core-kubeconfig cors-allowed-origins cpu-cfs-quota cpu-percent +create-annotation current-release-pr current-replicas daemonset-lookup-cache-size diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 95645cd6b92..e47bec6b3e4 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -14,6 +14,7 @@ go_library( "annotate.go", "apiversions.go", "apply.go", + "apply_set_last_applied.go", "apply_view_last_applied.go", "attach.go", "autoscale.go", diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index 98085275df6..5e8eba4f724 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -131,6 +131,7 @@ func NewCmdApply(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { // apply subcommands cmd.AddCommand(NewCmdApplyViewLastApplied(f, out, errOut)) + cmd.AddCommand(NewCmdApplySetLastApplied(f, out, errOut)) return cmd } diff --git a/pkg/kubectl/cmd/apply_set_last_applied.go b/pkg/kubectl/cmd/apply_set_last_applied.go new file mode 100644 index 00000000000..3ff1972c0fe --- /dev/null +++ b/pkg/kubectl/cmd/apply_set_last_applied.go @@ -0,0 +1,241 @@ +/* +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 cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + + "github.com/ghodss/yaml" + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + apijson "k8s.io/apimachinery/pkg/util/json" + "k8s.io/kubernetes/pkg/api/annotations" + "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" +) + +type SetLastAppliedOptions struct { + FilenameOptions resource.FilenameOptions + Selector string + InfoList []*resource.Info + Mapper meta.RESTMapper + Typer runtime.ObjectTyper + Namespace string + EnforceNamespace bool + DryRun bool + ShortOutput bool + CreateAnnotation bool + Output string + Codec runtime.Encoder + PatchBufferList [][]byte + Factory cmdutil.Factory + Out io.Writer + ErrOut io.Writer +} + +var ( + applySetLastAppliedLong = templates.LongDesc(` + Set the latest last-applied-configuration annotations by setting it to match the contents of a file. + This results in the last-applied-configuration being updated as though 'kubectl apply -f ' was run, + without updating any other parts of the object.`) + + applySetLastAppliedExample = templates.Examples(` + # Set the last-applied-configuration of a resource to match the contents of a file. + kubectl apply set-last-applied -f deploy.yaml + + # Execute set-last-applied against each configuration file in a directory. + kubectl apply set-last-applied -f path/ + + # Set the last-applied-configuration of a resource to match the contents of a file, will create the annotation if it does not already exist. + kubectl apply set-last-applied -f deploy.yaml --create-annotation=true + `) +) + +func NewCmdApplySetLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Command { + options := &SetLastAppliedOptions{Out: out, ErrOut: err} + cmd := &cobra.Command{ + Use: "set-last-applied -f FILENAME", + Short: "Set the last-applied-configuration annotation on a live object to match the contents of a file.", + Long: applySetLastAppliedLong, + Example: applySetLastAppliedExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(options.Complete(f, cmd)) + cmdutil.CheckErr(options.Validate(f, cmd)) + cmdutil.CheckErr(options.RunSetLastApplied(f, cmd)) + }, + } + + cmdutil.AddDryRunFlag(cmd) + cmdutil.AddRecordFlag(cmd) + cmdutil.AddPrinterFlags(cmd) + cmd.Flags().BoolVar(&options.CreateAnnotation, "create-annotation", false, "Will create 'last-applied-configuration' annotations if current objects doesn't have one") + usage := "that contains the last-applied-configuration annotations" + kubectl.AddJsonFilenameFlag(cmd, &options.FilenameOptions.Filenames, "Filename, directory, or URL to files "+usage) + + return cmd +} + +func (o *SetLastAppliedOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error { + o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run") + o.Output = cmdutil.GetFlagString(cmd, "output") + o.ShortOutput = o.Output == "name" + o.Codec = f.JSONEncoder() + + var err error + o.Mapper, o.Typer, err = f.UnstructuredObject() + if err != nil { + return err + } + + o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace() + if err != nil { + return err + } + + return nil +} + +func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command) error { + r := resource.NewBuilder(o.Mapper, o.Typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). + NamespaceParam(o.Namespace).DefaultNamespace(). + FilenameParam(o.EnforceNamespace, &o.FilenameOptions). + Latest(). + Flatten(). + Do() + err := r.Err() + if err != nil { + return err + } + + err = r.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + var diffBuf, patchBuf []byte + patchBuf, diffBuf, err = o.getPatch(info) + if err != nil { + return err + } + + // Verify the object exists in the cluster before trying to patch it. + if err := info.Get(); err != nil { + if errors.IsNotFound(err) { + return err + } else { + return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err) + } + } + oringalBuf, err := kubectl.GetOriginalConfiguration(info.Mapping, info.Object) + if err != nil { + return cmdutil.AddSourceToErr(fmt.Sprintf("retrieving current configuration of:\n%v\nfrom server for:", info), info.Source, err) + } + if oringalBuf == nil && !o.CreateAnnotation { + return cmdutil.UsageError(cmd, "no last-applied-configuration annotation found on resource: %s, to create the annotation, run the command with --create-annotation", info.Name) + } + + //only add to PatchBufferList when changed + if !bytes.Equal(stripComments(oringalBuf), stripComments(diffBuf)) { + o.PatchBufferList = append(o.PatchBufferList, patchBuf) + o.InfoList = append(o.InfoList, info) + } else { + fmt.Fprintf(o.Out, "set-last-applied %s: no changes required.\n", info.Name) + } + + return nil + }) + if err != nil { + return err + } + return nil +} + +func (o *SetLastAppliedOptions) RunSetLastApplied(f cmdutil.Factory, cmd *cobra.Command) error { + for i, patch := range o.PatchBufferList { + info := o.InfoList[i] + if !o.DryRun { + mapping := info.ResourceMapping() + client, err := f.UnstructuredClientForMapping(mapping) + if err != nil { + return err + } + helper := resource.NewHelper(client, mapping) + patchedObj, err := helper.Patch(o.Namespace, info.Name, types.MergePatchType, patch) + if err != nil { + return err + } + + if len(o.Output) > 0 && !o.ShortOutput { + info.Refresh(patchedObj, false) + return cmdutil.PrintResourceInfoForCommand(cmd, info, f, o.Out) + } + cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "configured") + + } else { + err := o.formatPrinter(o.Output, patch) + if err != nil { + return err + } + cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "configured") + } + } + return nil +} + +func (o *SetLastAppliedOptions) formatPrinter(output string, buf []byte) error { + yamlOutput, err := yaml.JSONToYAML(buf) + if err != nil { + return err + } + switch output { + case "json": + jsonBuffer := &bytes.Buffer{} + err = json.Indent(jsonBuffer, buf, "", " ") + if err != nil { + return err + } + fmt.Fprintf(o.Out, string(jsonBuffer.Bytes())) + case "yaml": + fmt.Fprintf(o.Out, string(yamlOutput)) + } + return nil +} + +func (o *SetLastAppliedOptions) getPatch(info *resource.Info) ([]byte, []byte, error) { + objMap := map[string]map[string]map[string]string{} + metadataMap := map[string]map[string]string{} + annotationsMap := map[string]string{} + localFile, err := runtime.Encode(o.Codec, info.VersionedObject) + if err != nil { + return nil, localFile, err + } + annotationsMap[annotations.LastAppliedConfigAnnotation] = string(localFile) + metadataMap["annotations"] = annotationsMap + objMap["metadata"] = metadataMap + jsonString, err := apijson.Marshal(objMap) + return jsonString, localFile, err +} diff --git a/pkg/kubectl/cmd/apply_test.go b/pkg/kubectl/cmd/apply_test.go index c233dd64f19..fe2ca8e7c07 100644 --- a/pkg/kubectl/cmd/apply_test.go +++ b/pkg/kubectl/cmd/apply_test.go @@ -59,10 +59,15 @@ func validateApplyArgs(cmd *cobra.Command, args []string) error { } const ( - filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml" - filenameRCLASTAPPLIED = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied.yaml" - filenameSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/service.yaml" - filenameRCSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-service.yaml" + filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml" + filenameRCNoAnnotation = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-no-annotation.yaml" + filenameRCLASTAPPLIED = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied.yaml" + filenameSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/service.yaml" + filenameRCSVC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-service.yaml" + filenameNoExistRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-noexist.yaml" + filenameRCPatchTest = "../../../test/fixtures/pkg/kubectl/cmd/apply/patch.json" + dirName = "../../../test/fixtures/pkg/kubectl/cmd/apply/testdir" + filenameRCJSON = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.json" ) func readBytesFromFile(t *testing.T, filename string) []byte { @@ -655,3 +660,127 @@ func TestApplyNULLPreservation(t *testing.T) { t.Fatal("No server-side patch call detected") } } + +func TestRunApplySetLastApplied(t *testing.T) { + initTestErrorHandler(t) + nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) + pathRC := "/namespaces/test/replicationcontrollers/" + nameRC + + noExistRC, _ := readAndAnnotateReplicationController(t, filenameNoExistRC) + noExistPath := "/namespaces/test/replicationcontrollers/" + noExistRC + + noAnnotationName, noAnnotationRC := readReplicationController(t, filenameRCNoAnnotation) + noAnnotationPath := "/namespaces/test/replicationcontrollers/" + noAnnotationName + + tests := []struct { + name, nameRC, pathRC, filePath, expectedErr, expectedOut, output string + }{ + { + name: "set with exist object", + filePath: filenameRC, + expectedErr: "", + expectedOut: "replicationcontroller/test-rc\n", + output: "name", + }, + { + name: "set with no-exist object", + filePath: filenameNoExistRC, + expectedErr: "Error from server (NotFound): the server could not find the requested resource (get replicationcontrollers no-exist)", + expectedOut: "", + output: "name", + }, + { + name: "set for the annotation does not exist on the live object", + filePath: filenameRCNoAnnotation, + expectedErr: "error: no last-applied-configuration annotation found on resource: no-annotation, to create the annotation, run the command with --create-annotation\nSee 'set-last-applied -h' for help and examples.", + expectedOut: "", + output: "name", + }, + { + name: "set with exist object output json", + filePath: filenameRCJSON, + expectedErr: "", + expectedOut: "replicationcontroller/test-rc\n", + output: "name", + }, + { + name: "set test for a directory of files", + filePath: dirName, + expectedErr: "", + expectedOut: "replicationcontroller/test-rc\nreplicationcontroller/test-rc\n", + output: "name", + }, + } + for _, test := range tests { + f, tf, codec, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{} + tf.UnstructuredClient = &fake.RESTClient{ + APIRegistry: api.Registry, + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == pathRC && m == "GET": + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == noAnnotationPath && m == "GET": + bodyRC := ioutil.NopCloser(bytes.NewReader(noAnnotationRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == noExistPath && m == "GET": + return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &api.Pod{})}, nil + case p == pathRC && m == "PATCH": + checkPatchString(t, req) + bodyRC := ioutil.NopCloser(bytes.NewReader(currentRC)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil + case p == "/api/v1/namespaces/test" && m == "GET": + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.Namespace{})}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + tf.ClientConfig = defaultClientConfig() + buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) + + cmdutil.BehaviorOnFatal(func(str string, code int) { + if str != test.expectedErr { + t.Errorf("%s: unexpected error: %s\nexpected: %s", test.name, str, test.expectedErr) + } + }) + + cmd := NewCmdApplySetLastApplied(f, buf, errBuf) + cmd.Flags().Set("filename", test.filePath) + cmd.Flags().Set("output", test.output) + cmd.Run(cmd, []string{}) + + if buf.String() != test.expectedOut { + t.Fatalf("%s: unexpected output: %s\nexpected: %s", test.name, buf.String(), test.expectedOut) + } + + } + +} + +func checkPatchString(t *testing.T, req *http.Request) { + checkString := string(readBytesFromFile(t, filenameRCPatchTest)) + patch, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Fatal(err) + } + + patchMap := map[string]interface{}{} + if err := json.Unmarshal(patch, &patchMap); err != nil { + t.Fatal(err) + } + + annotationsMap := walkMapPath(t, patchMap, []string{"metadata", "annotations"}) + if _, ok := annotationsMap[annotations.LastAppliedConfigAnnotation]; !ok { + t.Fatalf("patch does not contain annotation:\n%s\n", patch) + } + + resultString := annotationsMap["kubectl.kubernetes.io/last-applied-configuration"] + if resultString != checkString { + t.Fatalf("patch annotation is not correct, expect:%s\n but got:%s\n", checkString, resultString) + } +} diff --git a/test/fixtures/pkg/kubectl/cmd/apply/patch.json b/test/fixtures/pkg/kubectl/cmd/apply/patch.json new file mode 100644 index 00000000000..99542fb7022 --- /dev/null +++ b/test/fixtures/pkg/kubectl/cmd/apply/patch.json @@ -0,0 +1 @@ +{"apiVersion":"v1","kind":"ReplicationController","metadata":{"labels":{"name":"test-rc"},"name":"test-rc","namespace":"test"},"spec":{"replicas":1,"template":{"metadata":{"labels":{"name":"test-rc"}},"spec":{"containers":[{"image":"nginx","name":"test-rc","ports":[{"containerPort":80}]}]}}}} diff --git a/test/fixtures/pkg/kubectl/cmd/apply/rc-no-annotation.yaml b/test/fixtures/pkg/kubectl/cmd/apply/rc-no-annotation.yaml new file mode 100644 index 00000000000..2cf3b46a1f3 --- /dev/null +++ b/test/fixtures/pkg/kubectl/cmd/apply/rc-no-annotation.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: no-annotation + labels: + name: no-annotation +spec: + replicas: 1 + template: + metadata: + labels: + name: no-annotation + spec: + containers: + - name: no-annotation + image: nginx + ports: + - containerPort: 80 diff --git a/test/fixtures/pkg/kubectl/cmd/apply/rc-noexist.yaml b/test/fixtures/pkg/kubectl/cmd/apply/rc-noexist.yaml new file mode 100644 index 00000000000..df1d372ea4a --- /dev/null +++ b/test/fixtures/pkg/kubectl/cmd/apply/rc-noexist.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: no-exist + labels: + name: no-exist +spec: + replicas: 1 + template: + metadata: + labels: + name: no-exist + spec: + containers: + - name: no-exist + image: nginx + ports: + - containerPort: 80 diff --git a/test/fixtures/pkg/kubectl/cmd/apply/rc.json b/test/fixtures/pkg/kubectl/cmd/apply/rc.json new file mode 100644 index 00000000000..ae6db775f3b --- /dev/null +++ b/test/fixtures/pkg/kubectl/cmd/apply/rc.json @@ -0,0 +1,33 @@ +{ + "apiVersion": "v1", + "kind": "ReplicationController", + "metadata": { + "name": "test-rc", + "labels": { + "name": "test-rc" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "labels": { + "name": "test-rc" + } + }, + "spec": { + "containers": [ + { + "name": "test-rc", + "image": "nginx", + "ports": [ + { + "containerPort": 80 + } + ] + } + ] + } + } + } +} diff --git a/test/fixtures/pkg/kubectl/cmd/apply/testdir/rc.yaml b/test/fixtures/pkg/kubectl/cmd/apply/testdir/rc.yaml new file mode 100644 index 00000000000..1c1bf15be7f --- /dev/null +++ b/test/fixtures/pkg/kubectl/cmd/apply/testdir/rc.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: test-rc + labels: + name: test-rc +spec: + replicas: 1 + template: + metadata: + labels: + name: test-rc + spec: + containers: + - name: test-rc + image: nginx + ports: + - containerPort: 80 diff --git a/test/fixtures/pkg/kubectl/cmd/apply/testdir/rc1.yaml b/test/fixtures/pkg/kubectl/cmd/apply/testdir/rc1.yaml new file mode 100644 index 00000000000..1c1bf15be7f --- /dev/null +++ b/test/fixtures/pkg/kubectl/cmd/apply/testdir/rc1.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: ReplicationController +metadata: + name: test-rc + labels: + name: test-rc +spec: + replicas: 1 + template: + metadata: + labels: + name: test-rc + spec: + containers: + - name: test-rc + image: nginx + ports: + - containerPort: 80