Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add local option to annotate #34074

Merged
merged 1 commit into from
Oct 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions hack/make-rules/test-cmd.sh
Expand Up @@ -677,6 +677,25 @@ runTests() {
# Cleanup
kubectl delete pod pod-with-precision "${kube_flags[@]}"

### Annotate POD YAML file locally without effecting the live pod.
kubectl create -f hack/testdata/pod.yaml "${kube_flags[@]}"
# Command
kubectl annotate -f hack/testdata/pod.yaml annotatekey=annotatevalue "${kube_flags[@]}"

# Pre-condition: annotationkey is annotationvalue
kube::test::get_object_assert 'pod test-pod' "{{${annotations_field}.annotatekey}}" 'annotatevalue'

# Command
output_message=$(kubectl annotate --local -f hack/testdata/pod.yaml annotatekey=localvalue -o yaml "${kube_flags[@]}")
echo $output_message

# Post-condition: annotationkey is still annotationvalue in the live pod, but command output is the new value
kube::test::get_object_assert 'pod test-pod' "{{${annotations_field}.annotatekey}}" 'annotatevalue'
kube::test::if_has_string "${output_message}" "localvalue"

# Cleanup
kubectl delete -f hack/testdata/pod.yaml "${kube_flags[@]}"

### Create valid-pod POD
# Pre-condition: no POD exists
create_and_use_new_namespace
Expand Down
91 changes: 51 additions & 40 deletions pkg/kubectl/cmd/annotate.go
Expand Up @@ -45,6 +45,7 @@ type AnnotateOptions struct {
selector string

overwrite bool
local bool
all bool
resourceVersion string

Expand Down Expand Up @@ -125,6 +126,7 @@ func NewCmdAnnotate(f *cmdutil.Factory, out io.Writer) *cobra.Command {
cmdutil.AddInclude3rdPartyFlags(cmd)
cmd.Flags().StringVarP(&options.selector, "selector", "l", "", "Selector (label query) to filter on")
cmd.Flags().BoolVar(&options.overwrite, "overwrite", false, "If true, allow annotations to be overwritten, otherwise reject annotation updates that overwrite existing annotations.")
cmd.Flags().BoolVar(&options.local, "local", false, "If true, annotation will NOT contact api-server but run locally.")
cmd.Flags().BoolVar(&options.all, "all", false, "select all resources in the namespace of the specified resource types")
cmd.Flags().StringVar(&options.resourceVersion, "resource-version", "", "If non-empty, the annotation update will only succeed if this is the current resource-version for the object. Only valid when specifying a single resource.")
usage := "identifying the resource to update the annotation"
Expand Down Expand Up @@ -167,10 +169,12 @@ func (o *AnnotateOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
SelectorParam(o.selector).
ResourceTypeOrNameArgs(o.all, o.resources...).
Flatten().
Latest()
Flatten()
if !o.local {
o.builder = o.builder.SelectorParam(o.selector).
ResourceTypeOrNameArgs(o.all, o.resources...).
Latest()
}

o.f = f
o.out = out
Expand Down Expand Up @@ -207,49 +211,56 @@ func (o AnnotateOptions) RunAnnotate() error {
return err
}

var outputObj runtime.Object
obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping)
if err != nil {
return err
}
name, namespace := info.Name, info.Namespace
oldData, err := json.Marshal(obj)
if err != nil {
return err
}
// If we should record change-cause, add it to new annotations
if cmdutil.ContainsChangeCause(info) || o.recordChangeCause {
o.newAnnotations[kubectl.ChangeCauseAnnotation] = o.changeCause
}
if err := o.updateAnnotations(obj); err != nil {
return err
}
newData, err := json.Marshal(obj)
if err != nil {
return err
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
createdPatch := err == nil
if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err)
}
if o.local {
if err := o.updateAnnotations(obj); err != nil {
return err
}
outputObj = obj
} else {
name, namespace := info.Name, info.Namespace
oldData, err := json.Marshal(obj)
if err != nil {
return err
}
// If we should record change-cause, add it to new annotations
if cmdutil.ContainsChangeCause(info) || o.recordChangeCause {
o.newAnnotations[kubectl.ChangeCauseAnnotation] = o.changeCause
}
if err := o.updateAnnotations(obj); err != nil {
return err
}
newData, err := json.Marshal(obj)
if err != nil {
return err
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
createdPatch := err == nil
if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err)
}

mapping := info.ResourceMapping()
client, err := o.f.ClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
mapping := info.ResourceMapping()
client, err := o.f.ClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)

var outputObj runtime.Object
if createdPatch {
outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes)
} else {
outputObj, err = helper.Replace(namespace, name, false, obj)
}
if err != nil {
return err
}
if createdPatch {
outputObj, err = helper.Patch(namespace, name, api.StrategicMergePatchType, patchBytes)
} else {
outputObj, err = helper.Replace(namespace, name, false, obj)
}
if err != nil {
return err
}

}
mapper, _ := o.f.Object()
outputFormat := cmdutil.GetFlagString(o.cmd, "output")
if outputFormat != "" {
Expand Down
28 changes: 28 additions & 0 deletions pkg/kubectl/cmd/annotate_test.go
Expand Up @@ -517,6 +517,34 @@ func TestAnnotateObjectFromFile(t *testing.T) {
}
}

func TestAnnotateLocal(t *testing.T) {
f, tf, _, ns := NewAPIFactory()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
}),
}
tf.Namespace = "test"
tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: testapi.Default.GroupVersion()}}

buf := bytes.NewBuffer([]byte{})
cmd := NewCmdAnnotate(f, buf)
options := &AnnotateOptions{local: true}
options.Filenames = []string{"../../../examples/storage/cassandra/cassandra-controller.yaml"}
args := []string{"a=b"}
if err := options.Complete(f, buf, cmd, args); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.Validate(args); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err := options.RunAnnotate(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}

func TestAnnotateMultipleObjects(t *testing.T) {
pods, _, _ := testData()

Expand Down