mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 18:28:13 +00:00 
			
		
		
		
	Merge pull request #41146 from shiywang/apply-view1
Automatic merge from submit-queue (batch tested with PRs 41146, 41486, 41482, 41538, 41784) Add apply view-last-applied subcommand reopen pr https://github.com/kubernetes/kubernetes/pull/40984, implement part of https://github.com/kubernetes/community/pull/287 for now unit test all pass, the output looks like: ```console shiywang@dhcp-140-33 template $ ./kubectl apply view last-applied deployment nginx-deployment apiVersion: extensions/v1beta1 kind: Deployment metadata: creationTimestamp: null name: nginx-deployment spec: strategy: {} template: metadata: creationTimestamp: null labels: app: nginx spec: containers: - image: nginx:1.12.10 name: nginx ports: - containerPort: 80 resources: {} status: {} ``` ```release-note Support new kubectl apply view-last-applied command for viewing the last configuration file applied ``` not sure if there is any flag I should updated or the some error handling I should changed. will generate docs when you guys think is ok. cc @pwittrock @jessfraz @AdoHe @ymqytw
This commit is contained in:
		| @@ -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-view-last-applied.1 | ||||
| docs/man/man1/kubectl-apply.1 | ||||
| docs/man/man1/kubectl-attach.1 | ||||
| docs/man/man1/kubectl-autoscale.1 | ||||
| @@ -97,6 +98,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_view-last-applied.md | ||||
| docs/user-guide/kubectl/kubectl_attach.md | ||||
| docs/user-guide/kubectl/kubectl_autoscale.md | ||||
| docs/user-guide/kubectl/kubectl_certificate.md | ||||
|   | ||||
							
								
								
									
										3
									
								
								docs/man/man1/kubectl-apply-view-last-applied.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/man/man1/kubectl-apply-view-last-applied.1
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||
| @@ -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. | ||||
| @@ -14,6 +14,7 @@ go_library( | ||||
|         "annotate.go", | ||||
|         "apiversions.go", | ||||
|         "apply.go", | ||||
|         "apply_view_last_applied.go", | ||||
|         "attach.go", | ||||
|         "autoscale.go", | ||||
|         "certificates.go", | ||||
| @@ -98,6 +99,7 @@ go_library( | ||||
|         "//vendor:github.com/docker/distribution/reference", | ||||
|         "//vendor:github.com/docker/docker/pkg/term", | ||||
|         "//vendor:github.com/evanphx/json-patch", | ||||
|         "//vendor:github.com/ghodss/yaml", | ||||
|         "//vendor:github.com/golang/glog", | ||||
|         "//vendor:github.com/jonboulle/clockwork", | ||||
|         "//vendor:github.com/renstrom/dedent", | ||||
|   | ||||
| @@ -128,6 +128,10 @@ func NewCmdApply(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { | ||||
| 	cmdutil.AddPrinterFlags(cmd) | ||||
| 	cmdutil.AddRecordFlag(cmd) | ||||
| 	cmdutil.AddInclude3rdPartyFlags(cmd) | ||||
|  | ||||
| 	// apply subcommands | ||||
| 	cmd.AddCommand(NewCmdApplyViewLastApplied(f, out, errOut)) | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -59,9 +59,10 @@ func validateApplyArgs(cmd *cobra.Command, args []string) error { | ||||
| } | ||||
|  | ||||
| const ( | ||||
| 	filenameRC    = "../../../test/fixtures/pkg/kubectl/cmd/apply/rc.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" | ||||
| 	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" | ||||
| ) | ||||
|  | ||||
| func readBytesFromFile(t *testing.T, filename string) []byte { | ||||
| @@ -193,6 +194,129 @@ func walkMapPath(t *testing.T, start map[string]interface{}, path []string) map[ | ||||
| 	return finish | ||||
| } | ||||
|  | ||||
| func TestRunApplyViewLastApplied(t *testing.T) { | ||||
| 	_, rcBytesWithConfig := readReplicationController(t, filenameRCLASTAPPLIED) | ||||
| 	nameRC, rcBytes := readReplicationController(t, filenameRC) | ||||
| 	pathRC := "/namespaces/test/replicationcontrollers/" + nameRC | ||||
|  | ||||
| 	tests := []struct { | ||||
| 		name, nameRC, pathRC, filePath, outputFormat, expectedErr, expectedOut, selector string | ||||
| 		args                                                                             []string | ||||
| 		respBytes                                                                        []byte | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:         "view with file", | ||||
| 			filePath:     filenameRC, | ||||
| 			outputFormat: "", | ||||
| 			expectedErr:  "", | ||||
| 			expectedOut:  "test: 1234\n", | ||||
| 			selector:     "", | ||||
| 			args:         []string{}, | ||||
| 			respBytes:    rcBytesWithConfig, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "view with file json format", | ||||
| 			filePath:     filenameRC, | ||||
| 			outputFormat: "json", | ||||
| 			expectedErr:  "", | ||||
| 			expectedOut:  "{\n  \"test\": 1234\n}\n", | ||||
| 			selector:     "", | ||||
| 			args:         []string{}, | ||||
| 			respBytes:    rcBytesWithConfig, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "view resource/name invalid format", | ||||
| 			filePath:     "", | ||||
| 			outputFormat: "wide", | ||||
| 			expectedErr:  "error: Unexpected -o output mode: wide, the flag 'output' must be one of yaml|json\nSee 'view-last-applied -h' for help and examples.", | ||||
| 			expectedOut:  "", | ||||
| 			selector:     "", | ||||
| 			args:         []string{"rc", "test-rc"}, | ||||
| 			respBytes:    rcBytesWithConfig, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "view resource with label", | ||||
| 			filePath:     "", | ||||
| 			outputFormat: "", | ||||
| 			expectedErr:  "", | ||||
| 			expectedOut:  "test: 1234\n", | ||||
| 			selector:     "name=test-rc", | ||||
| 			args:         []string{"rc"}, | ||||
| 			respBytes:    rcBytesWithConfig, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "view resource without annotations", | ||||
| 			filePath:     "", | ||||
| 			outputFormat: "", | ||||
| 			expectedErr:  "error: no last-applied-configuration annotation found on resource: test-rc", | ||||
| 			expectedOut:  "", | ||||
| 			selector:     "", | ||||
| 			args:         []string{"rc", "test-rc"}, | ||||
| 			respBytes:    rcBytes, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "view resource no match", | ||||
| 			filePath:     "", | ||||
| 			outputFormat: "", | ||||
| 			expectedErr:  "Error from server (NotFound): the server could not find the requested resource (get replicationcontrollers no-match)", | ||||
| 			expectedOut:  "", | ||||
| 			selector:     "", | ||||
| 			args:         []string{"rc", "no-match"}, | ||||
| 			respBytes:    nil, | ||||
| 		}, | ||||
| 	} | ||||
| 	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(test.respBytes)) | ||||
| 					return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil | ||||
| 				case p == "/namespaces/test/replicationcontrollers" && m == "GET": | ||||
| 					bodyRC := ioutil.NopCloser(bytes.NewReader(test.respBytes)) | ||||
| 					return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: bodyRC}, nil | ||||
| 				case p == "/namespaces/test/replicationcontrollers/no-match" && m == "GET": | ||||
| 					return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: objBody(codec, &api.Pod{})}, 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 := NewCmdApplyViewLastApplied(f, buf, errBuf) | ||||
| 		if test.filePath != "" { | ||||
| 			cmd.Flags().Set("filename", test.filePath) | ||||
| 		} | ||||
| 		if test.outputFormat != "" { | ||||
| 			cmd.Flags().Set("output", test.outputFormat) | ||||
| 		} | ||||
| 		if test.selector != "" { | ||||
| 			cmd.Flags().Set("selector", test.selector) | ||||
| 		} | ||||
|  | ||||
| 		cmd.Run(cmd, test.args) | ||||
| 		if buf.String() != test.expectedOut { | ||||
| 			t.Fatalf("%s: unexpected output: %s\nexpected: %s", test.name, buf.String(), test.expectedOut) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestApplyObjectWithoutAnnotation(t *testing.T) { | ||||
| 	initTestErrorHandler(t) | ||||
| 	nameRC, rcBytes := readReplicationController(t, filenameRC) | ||||
|   | ||||
							
								
								
									
										165
									
								
								pkg/kubectl/cmd/apply_view_last_applied.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								pkg/kubectl/cmd/apply_view_last_applied.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| /* | ||||
| 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/apis/meta/v1/unstructured" | ||||
| 	"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 ViewLastAppliedOptions struct { | ||||
| 	FilenameOptions              resource.FilenameOptions | ||||
| 	Selector                     string | ||||
| 	LastAppliedConfigurationList []string | ||||
| 	OutputFormat                 string | ||||
| 	Factory                      cmdutil.Factory | ||||
| 	Out                          io.Writer | ||||
| 	ErrOut                       io.Writer | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	applyViewLastAppliedLong = templates.LongDesc(` | ||||
| 		View the latest last-applied-configuration annotations by type/name or file. | ||||
|  | ||||
| 		The default output will be printed to stdout in YAML format. One can use -o option | ||||
| 		to change output format.`) | ||||
|  | ||||
| 	applyViewLastAppliedExample = templates.Examples(` | ||||
| 		# View the last-applied-configuration annotations by type/name in YAML. | ||||
| 		kubectl apply view-last-applied deployment/nginx | ||||
|  | ||||
| 		# View the last-applied-configuration annotations by file in JSON | ||||
| 		kubectl apply view-last-applied -f deploy.yaml -o json`) | ||||
| ) | ||||
|  | ||||
| func NewCmdApplyViewLastApplied(f cmdutil.Factory, out, err io.Writer) *cobra.Command { | ||||
| 	options := &ViewLastAppliedOptions{Out: out, ErrOut: err} | ||||
| 	cmd := &cobra.Command{ | ||||
| 		Use:     "view-last-applied (TYPE [NAME | -l label] | TYPE/NAME | -f FILENAME)", | ||||
| 		Short:   "View latest last-applied-configuration annotations of a resource/object", | ||||
| 		Long:    applyViewLastAppliedLong, | ||||
| 		Example: applyViewLastAppliedExample, | ||||
| 		Run: func(cmd *cobra.Command, args []string) { | ||||
| 			cmdutil.CheckErr(options.ValidateOutputArgs(cmd)) | ||||
| 			cmdutil.CheckErr(options.Complete(f, args)) | ||||
| 			cmdutil.CheckErr(options.Validate(cmd)) | ||||
| 			cmdutil.CheckErr(options.RunApplyViewLastApplied()) | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	cmd.Flags().StringP("output", "o", "", "Output format. Must be one of yaml|json") | ||||
| 	cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.") | ||||
| 	usage := "that contains the last-applied-configuration annotations" | ||||
| 	cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) | ||||
|  | ||||
| 	return cmd | ||||
| } | ||||
|  | ||||
| func (o *ViewLastAppliedOptions) Complete(f cmdutil.Factory, args []string) error { | ||||
| 	mapper, typer, err := f.UnstructuredObject() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	cmdNamespace, enforceNamespace, err := f.DefaultNamespace() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). | ||||
| 		NamespaceParam(cmdNamespace).DefaultNamespace(). | ||||
| 		FilenameParam(enforceNamespace, &o.FilenameOptions). | ||||
| 		ResourceTypeOrNameArgs(enforceNamespace, args...). | ||||
| 		SelectorParam(o.Selector). | ||||
| 		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 | ||||
| 		} | ||||
|  | ||||
| 		configString, err := kubectl.GetOriginalConfiguration(info.Mapping, info.Object) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if configString == nil { | ||||
| 			return cmdutil.AddSourceToErr(fmt.Sprintf("no last-applied-configuration annotation found on resource: %s\n", info.Name), info.Source, err) | ||||
| 		} | ||||
| 		o.LastAppliedConfigurationList = append(o.LastAppliedConfigurationList, string(configString)) | ||||
| 		return nil | ||||
| 	}) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (o *ViewLastAppliedOptions) Validate(cmd *cobra.Command) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (o *ViewLastAppliedOptions) RunApplyViewLastApplied() error { | ||||
| 	for _, str := range o.LastAppliedConfigurationList { | ||||
| 		yamlOutput, err := yaml.JSONToYAML([]byte(str)) | ||||
| 		switch o.OutputFormat { | ||||
| 		case "json": | ||||
| 			jsonBuffer := &bytes.Buffer{} | ||||
| 			err = json.Indent(jsonBuffer, []byte(str), "", "  ") | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 			fmt.Fprintf(o.Out, string(jsonBuffer.Bytes())) | ||||
| 		case "yaml": | ||||
| 			fmt.Fprintf(o.Out, string(yamlOutput)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (o *ViewLastAppliedOptions) ValidateOutputArgs(cmd *cobra.Command) error { | ||||
| 	format := cmdutil.GetFlagString(cmd, "output") | ||||
| 	switch format { | ||||
| 	case "json": | ||||
| 		o.OutputFormat = "json" | ||||
| 		return nil | ||||
| 	// If flag -o is not specified, use yaml as default | ||||
| 	case "yaml", "": | ||||
| 		o.OutputFormat = "yaml" | ||||
| 		return nil | ||||
| 	default: | ||||
| 		return cmdutil.UsageError(cmd, "Unexpected -o output mode: %s, the flag 'output' must be one of yaml|json", format) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										21
									
								
								test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								test/fixtures/pkg/kubectl/cmd/apply/rc-lastapplied.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| apiVersion: v1 | ||||
| kind: ReplicationController | ||||
| metadata: | ||||
|   annotations: | ||||
|     kubectl.kubernetes.io/last-applied-configuration: | | ||||
|       {"test":1234} | ||||
|   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 | ||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Submit Queue
					Kubernetes Submit Queue