mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	Merge pull request #41694 from shiywang/apply-set1
Automatic merge from submit-queue (batch tested with PRs 42044, 41694, 41927, 42050, 41987) Add apply set-last-applied subcommand implement part of https://github.com/kubernetes/community/pull/287, will rebase after https://github.com/kubernetes/kubernetes/pull/41699 got merged, EDIT: since bug output format has been confirmed, will update the behavior of output format soon cc @kubernetes/sig-cli-pr-reviews @AdoHe @pwittrock ```release-note Support kubectl apply set-last-applied command to update the applied-applied-configuration annotation ```
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-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
 | 
			
		||||
@@ -101,6 +102,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_auth.md
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								docs/man/man1/kubectl-apply-set-last-applied.1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/man/man1/kubectl-apply-set-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.
 | 
			
		||||
@@ -124,6 +124,7 @@ core-kubeconfig
 | 
			
		||||
cors-allowed-origins
 | 
			
		||||
cpu-cfs-quota
 | 
			
		||||
cpu-percent
 | 
			
		||||
create-annotation
 | 
			
		||||
current-release-pr
 | 
			
		||||
current-replicas
 | 
			
		||||
daemonset-lookup-cache-size
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										241
									
								
								pkg/kubectl/cmd/apply_set_last_applied.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								pkg/kubectl/cmd/apply_set_last_applied.go
									
									
									
									
									
										Normal file
									
								
							@@ -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 <file>' 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
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								test/fixtures/pkg/kubectl/cmd/apply/patch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/fixtures/pkg/kubectl/cmd/apply/patch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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}]}]}}}}
 | 
			
		||||
							
								
								
									
										18
									
								
								test/fixtures/pkg/kubectl/cmd/apply/rc-no-annotation.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								test/fixtures/pkg/kubectl/cmd/apply/rc-no-annotation.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										18
									
								
								test/fixtures/pkg/kubectl/cmd/apply/rc-noexist.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								test/fixtures/pkg/kubectl/cmd/apply/rc-noexist.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										33
									
								
								test/fixtures/pkg/kubectl/cmd/apply/rc.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								test/fixtures/pkg/kubectl/cmd/apply/rc.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
              }
 | 
			
		||||
            ]
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								test/fixtures/pkg/kubectl/cmd/apply/testdir/rc.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								test/fixtures/pkg/kubectl/cmd/apply/testdir/rc.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
							
								
								
									
										18
									
								
								test/fixtures/pkg/kubectl/cmd/apply/testdir/rc1.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								test/fixtures/pkg/kubectl/cmd/apply/testdir/rc1.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
		Reference in New Issue
	
	Block a user