diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index b69c8dd2579..9eba52acdc1 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -26,6 +26,7 @@ import ( "github.com/jonboulle/clockwork" "github.com/spf13/cobra" + "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -303,6 +304,8 @@ func (o *ApplyOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error { visitedUids := sets.NewString() visitedNamespaces := sets.NewString() + var objs []runtime.Object + count := 0 err = r.Visit(func(info *resource.Info, err error) error { if err != nil { @@ -354,6 +357,11 @@ func (o *ApplyOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error { count++ + if printObject { + objs = append(objs, info.Object) + return nil + } + printer, err := o.ToPrinter("created") if err != nil { return err @@ -416,20 +424,52 @@ func (o *ApplyOptions) Run(f cmdutil.Factory, cmd *cobra.Command) error { } count++ + if printObject { + objs = append(objs, info.Object) + return nil + } + printer, err := o.ToPrinter("configured") if err != nil { return err } return printer.PrintObj(info.AsVersioned(), o.Out) }) - if err != nil { return err } + if count == 0 { return fmt.Errorf("no objects passed to apply") } + // print objects + if len(objs) > 0 { + printer, err := o.ToPrinter("") + if err != nil { + return err + } + + objToPrint := objs[0] + if len(objs) > 1 { + list := &v1.List{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", + }, + ListMeta: metav1.ListMeta{}, + } + if err := meta.SetList(list, objs); err != nil { + return err + } + + objToPrint = list + } + if err := printer.PrintObj(objToPrint, o.Out); err != nil { + return err + } + } + if !o.Prune { return nil } diff --git a/pkg/kubectl/cmd/apply_test.go b/pkg/kubectl/cmd/apply_test.go index 536329b7b0a..d6064939153 100644 --- a/pkg/kubectl/cmd/apply_test.go +++ b/pkg/kubectl/cmd/apply_test.go @@ -32,6 +32,7 @@ import ( "github.com/spf13/cobra" autoscalingv1 "k8s.io/api/autoscaling/v1" + corev1 "k8s.io/api/core/v1" kubeerr "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -89,6 +90,7 @@ func validateApplyArgs(cmd *cobra.Command, args []string) error { } const ( + filenameCM = "../../../test/fixtures/pkg/kubectl/cmd/apply/cm.yaml" filenameRC = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.yaml" filenameRCArgs = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-args.yaml" filenameRCLastAppliedArgs = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied-args.yaml" @@ -105,6 +107,21 @@ const ( filenameWidgetServerside = "../../../test/fixtures/pkg/kubectl/cmd/apply/widget-serverside.yaml" ) +func readConfigMapList(t *testing.T, filename string) []byte { + data := readBytesFromFile(t, filename) + cmList := corev1.ConfigMapList{} + if err := runtime.DecodeInto(testapi.Default.Codec(), data, &cmList); err != nil { + t.Fatal(err) + } + + cmListBytes, err := runtime.Encode(testapi.Default.Codec(), &cmList) + if err != nil { + t.Fatal(err) + } + + return cmListBytes +} + func readBytesFromFile(t *testing.T, filename string) []byte { file, err := os.Open(filename) if err != nil { @@ -255,6 +272,48 @@ func walkMapPath(t *testing.T, start map[string]interface{}, path []string) map[ return finish } +func TestRunApplyPrintsValidObjectList(t *testing.T) { + initTestErrorHandler(t) + cmBytes := readConfigMapList(t, filenameCM) + pathCM := "/namespaces/test/configmaps" + + tf := cmdtesting.NewTestFactory() + defer tf.Cleanup() + + tf.UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case strings.HasPrefix(p, pathCM) && m != "GET": + pod := ioutil.NopCloser(bytes.NewReader(cmBytes)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: pod}, nil + case strings.HasPrefix(p, pathCM) && m != "PATCH": + pod := ioutil.NopCloser(bytes.NewReader(cmBytes)) + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: pod}, nil + default: + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + tf.ClientConfigVal = defaultClientConfig() + buf := bytes.NewBuffer([]byte{}) + errBuf := bytes.NewBuffer([]byte{}) + + cmd := NewCmdApply("kubectl", tf, buf, errBuf) + cmd.Flags().Set("filename", filenameCM) + cmd.Flags().Set("output", "json") + cmd.Flags().Set("dry-run", "true") + cmd.Run(cmd, []string{}) + + // ensure that returned list can be unmarshaled back into a configmap list + cmList := corev1.List{} + if err := runtime.DecodeInto(testapi.Default.Codec(), buf.Bytes(), &cmList); err != nil { + t.Fatal(err) + } +} + func TestRunApplyViewLastApplied(t *testing.T) { _, rcBytesWithConfig := readReplicationController(t, filenameRCLASTAPPLIED) _, rcBytesWithArgs := readReplicationController(t, filenameRCLastAppliedArgs) diff --git a/test/fixtures/pkg/kubectl/cmd/apply/cm.yaml b/test/fixtures/pkg/kubectl/cmd/apply/cm.yaml new file mode 100644 index 00000000000..571afe8ae56 --- /dev/null +++ b/test/fixtures/pkg/kubectl/cmd/apply/cm.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +items: +- kind: ConfigMap + apiVersion: v1 + metadata: + name: test + data: + key1: apple +- kind: ConfigMap + apiVersion: v1 + metadata: + name: test2 + data: + key2: apple +kind: ConfigMapList +metadata: {}