mirror of https://github.com/k3s-io/k3s
add `kubectl apply edit-last-applied` subcommand
parent
286bcc6f5c
commit
4597658cb9
|
@ -13,6 +13,7 @@ docs/man/man1/kube-scheduler.1
|
|||
docs/man/man1/kubectl-annotate.1
|
||||
docs/man/man1/kubectl-api-versions.1
|
||||
docs/man/man1/kubectl-apiversions.1
|
||||
docs/man/man1/kubectl-apply-edit-last-applied.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
|
||||
|
@ -111,6 +112,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_edit-last-applied.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
|
||||
|
|
|
@ -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.
|
|
@ -12,6 +12,7 @@ go_library(
|
|||
"annotate.go",
|
||||
"apiversions.go",
|
||||
"apply.go",
|
||||
"apply_edit_last_applied.go",
|
||||
"apply_set_last_applied.go",
|
||||
"apply_view_last_applied.go",
|
||||
"attach.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))
|
||||
cmd.AddCommand(NewCmdApplyEditLastApplied(f, out, errOut))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
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 (
|
||||
"io"
|
||||
gruntime "runtime"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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/cmd/util/editor"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
)
|
||||
|
||||
var (
|
||||
applyEditLastAppliedLong = templates.LongDesc(`
|
||||
Edit the latest last-applied-configuration annotations of resources from the default editor.
|
||||
|
||||
The edit-last-applied command allows you to directly edit any API resource you can retrieve via the
|
||||
command line tools. It will open the editor defined by your KUBE_EDITOR, or EDITOR
|
||||
environment variables, or fall back to 'vi' for Linux or 'notepad' for Windows.
|
||||
You can edit multiple objects, although changes are applied one at a time. The command
|
||||
accepts filenames as well as command line arguments, although the files you point to must
|
||||
be previously saved versions of resources.
|
||||
|
||||
The default format is YAML. To edit in JSON, specify "-o json".
|
||||
|
||||
The flag --windows-line-endings can be used to force Windows line endings,
|
||||
otherwise the default for your operating system will be used.
|
||||
|
||||
In the event an error occurs while updating, a temporary file will be created on disk
|
||||
that contains your unapplied changes. The most common error when updating a resource
|
||||
is another editor changing the resource on the server. When this occurs, you will have
|
||||
to apply your changes to the newer version of the resource, or update your temporary
|
||||
saved copy to include the latest resource version.`)
|
||||
|
||||
applyEditLastAppliedExample = templates.Examples(`
|
||||
# Edit the last-applied-configuration annotations by type/name in YAML.
|
||||
kubectl apply edit-last-applied deployment/nginx
|
||||
|
||||
# Edit the last-applied-configuration annotations by file in JSON.
|
||||
kubectl apply edit-last-applied -f deploy.yaml -o json`)
|
||||
)
|
||||
|
||||
func NewCmdApplyEditLastApplied(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
|
||||
options := &editor.EditOptions{
|
||||
EditMode: editor.ApplyEditMode,
|
||||
}
|
||||
|
||||
// retrieve a list of handled resources from printer as valid args
|
||||
validArgs, argAliases := []string{}, []string{}
|
||||
p, err := f.Printer(nil, printers.PrintOptions{
|
||||
ColumnLabels: []string{},
|
||||
})
|
||||
cmdutil.CheckErr(err)
|
||||
if p != nil {
|
||||
validArgs = p.HandledResources()
|
||||
argAliases = kubectl.ResourceAliases(validArgs)
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "edit-last-applied (RESOURCE/NAME | -f FILENAME)",
|
||||
Short: "Edit latest last-applied-configuration annotations of a resource/object",
|
||||
Long: applyEditLastAppliedLong,
|
||||
Example: applyEditLastAppliedExample,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
options.ChangeCause = f.Command(cmd, false)
|
||||
if err := options.Complete(f, out, errOut, args); err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
}
|
||||
if err := options.Run(); err != nil {
|
||||
cmdutil.CheckErr(err)
|
||||
}
|
||||
},
|
||||
ValidArgs: validArgs,
|
||||
ArgAliases: argAliases,
|
||||
}
|
||||
|
||||
usage := "to use to edit the resource"
|
||||
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
|
||||
cmd.Flags().StringVarP(&options.Output, "output", "o", "yaml", "Output format. One of: yaml|json.")
|
||||
cmd.Flags().BoolVar(&options.WindowsLineEndings, "windows-line-endings", gruntime.GOOS == "windows", "Use Windows line-endings (default Unix line-endings)")
|
||||
cmdutil.AddRecordVarFlag(cmd, &options.Record)
|
||||
|
||||
return cmd
|
||||
}
|
|
@ -35,6 +35,7 @@ import (
|
|||
"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/cmd/util/editor"
|
||||
"k8s.io/kubernetes/pkg/kubectl/resource"
|
||||
"k8s.io/kubernetes/pkg/util/i18n"
|
||||
)
|
||||
|
@ -52,12 +53,17 @@ type SetLastAppliedOptions struct {
|
|||
CreateAnnotation bool
|
||||
Output string
|
||||
Codec runtime.Encoder
|
||||
PatchBufferList [][]byte
|
||||
PatchBufferList []PatchBuffer
|
||||
Factory cmdutil.Factory
|
||||
Out io.Writer
|
||||
ErrOut io.Writer
|
||||
}
|
||||
|
||||
type PatchBuffer struct {
|
||||
Patch []byte
|
||||
PatchType types.PatchType
|
||||
}
|
||||
|
||||
var (
|
||||
applySetLastAppliedLong = templates.LongDesc(i18n.T(`
|
||||
Set the latest last-applied-configuration annotations by setting it to match the contents of a file.
|
||||
|
@ -137,8 +143,7 @@ func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command)
|
|||
return err
|
||||
}
|
||||
|
||||
var diffBuf, patchBuf []byte
|
||||
patchBuf, diffBuf, err = o.getPatch(info)
|
||||
patchBuf, diffBuf, patchType, err := editor.GetApplyPatch(info.VersionedObject, o.Codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -161,7 +166,8 @@ func (o *SetLastAppliedOptions) Validate(f cmdutil.Factory, cmd *cobra.Command)
|
|||
|
||||
//only add to PatchBufferList when changed
|
||||
if !bytes.Equal(cmdutil.StripComments(oringalBuf), cmdutil.StripComments(diffBuf)) {
|
||||
o.PatchBufferList = append(o.PatchBufferList, patchBuf)
|
||||
p := PatchBuffer{Patch: patchBuf, PatchType: patchType}
|
||||
o.PatchBufferList = append(o.PatchBufferList, p)
|
||||
o.InfoList = append(o.InfoList, info)
|
||||
} else {
|
||||
fmt.Fprintf(o.Out, "set-last-applied %s: no changes required.\n", info.Name)
|
||||
|
@ -185,7 +191,7 @@ func (o *SetLastAppliedOptions) RunSetLastApplied(f cmdutil.Factory, cmd *cobra.
|
|||
return err
|
||||
}
|
||||
helper := resource.NewHelper(client, mapping)
|
||||
patchedObj, err := helper.Patch(o.Namespace, info.Name, types.MergePatchType, patch)
|
||||
patchedObj, err := helper.Patch(o.Namespace, info.Name, patch.PatchType, patch.Patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -197,7 +203,7 @@ func (o *SetLastAppliedOptions) RunSetLastApplied(f cmdutil.Factory, cmd *cobra.
|
|||
cmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, o.DryRun, "configured")
|
||||
|
||||
} else {
|
||||
err := o.formatPrinter(o.Output, patch)
|
||||
err := o.formatPrinter(o.Output, patch.Patch, o.Out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -207,7 +213,7 @@ func (o *SetLastAppliedOptions) RunSetLastApplied(f cmdutil.Factory, cmd *cobra.
|
|||
return nil
|
||||
}
|
||||
|
||||
func (o *SetLastAppliedOptions) formatPrinter(output string, buf []byte) error {
|
||||
func (o *SetLastAppliedOptions) formatPrinter(output string, buf []byte, w io.Writer) error {
|
||||
yamlOutput, err := yaml.JSONToYAML(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -219,9 +225,9 @@ func (o *SetLastAppliedOptions) formatPrinter(output string, buf []byte) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(o.Out, string(jsonBuffer.Bytes()))
|
||||
fmt.Fprintf(w, string(jsonBuffer.Bytes()))
|
||||
case "yaml":
|
||||
fmt.Fprintf(o.Out, string(yamlOutput))
|
||||
fmt.Fprintf(w, string(yamlOutput))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -238,6 +238,8 @@ func TestEdit(t *testing.T) {
|
|||
case "create":
|
||||
cmd = NewCmdCreate(f, buf, errBuf)
|
||||
cmd.Flags().Set("edit", "true")
|
||||
case "edit-last-applied":
|
||||
cmd = NewCmdApplyEditLastApplied(f, buf, errBuf)
|
||||
default:
|
||||
t.Errorf("%s: unexpected mode %s", name, testcase.Mode)
|
||||
continue
|
||||
|
|
0
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/0.request
vendored
Executable file
0
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/0.request
vendored
Executable file
21
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/0.response
vendored
Executable file
21
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/0.response
vendored
Executable file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"kind": "ConfigMap",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "cm1",
|
||||
"namespace": "myproject",
|
||||
"selfLink": "/api/v1/namespaces/myproject/configmaps/cm1",
|
||||
"uid": "cc08a131-3d6f-11e7-8ef0-c85b76034b7b",
|
||||
"resourceVersion": "3518",
|
||||
"creationTimestamp": "2017-05-20T15:20:03Z",
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"baz\":\"qux\",\"foo\":\"changed-value\",\"new-data\":\"new-value\",\"new-data2\":\"new-value\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"cm1\",\"namespace\":\"myproject\"}}\n"
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"baz": "qux",
|
||||
"foo": "changed-value",
|
||||
"new-data": "new-value",
|
||||
"new-data2": "new-value"
|
||||
}
|
||||
}
|
0
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/1.request
vendored
Executable file
0
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/1.request
vendored
Executable file
35
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/1.response
vendored
Executable file
35
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/1.response
vendored
Executable file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "svc1",
|
||||
"namespace": "myproject",
|
||||
"selfLink": "/api/v1/namespaces/myproject/services/svc1",
|
||||
"uid": "d8b96f0b-3d6f-11e7-8ef0-c85b76034b7b",
|
||||
"resourceVersion": "3525",
|
||||
"creationTimestamp": "2017-05-20T15:20:24Z",
|
||||
"labels": {
|
||||
"app": "svc1",
|
||||
"new-label": "foo"
|
||||
},
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"ports": [
|
||||
{
|
||||
"name": "80",
|
||||
"protocol": "TCP",
|
||||
"port": 81,
|
||||
"targetPort": 81
|
||||
}
|
||||
],
|
||||
"clusterIP": "172.30.32.183",
|
||||
"type": "ClusterIP",
|
||||
"sessionAffinity": "None"
|
||||
},
|
||||
"status": {
|
||||
"loadBalancer": {}
|
||||
}
|
||||
}
|
38
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/2.edited
vendored
Executable file
38
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/2.edited
vendored
Executable file
|
@ -0,0 +1,38 @@
|
|||
# Please edit the 'last-applied-configuration' annotations below.
|
||||
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
|
||||
#
|
||||
apiVersion: v1
|
||||
items:
|
||||
- apiVersion: v1
|
||||
data:
|
||||
baz: qux
|
||||
foo: changed-value
|
||||
new-data: new-value
|
||||
new-data2: new-value
|
||||
new-data3: newivalue
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations: {}
|
||||
name: cm1
|
||||
namespace: myproject
|
||||
- kind: Service
|
||||
metadata:
|
||||
annotations: {}
|
||||
labels:
|
||||
app: svc1
|
||||
new-label: foo
|
||||
new-label2: foo2
|
||||
name: svc1
|
||||
namespace: myproject
|
||||
spec:
|
||||
ports:
|
||||
- name: "80"
|
||||
port: 82
|
||||
protocol: TCP
|
||||
targetPort: 81
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
status:
|
||||
loadBalancer: {}
|
||||
kind: List
|
||||
metadata: {}
|
36
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/2.original
vendored
Executable file
36
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/2.original
vendored
Executable file
|
@ -0,0 +1,36 @@
|
|||
# Please edit the 'last-applied-configuration' annotations below.
|
||||
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
|
||||
#
|
||||
apiVersion: v1
|
||||
items:
|
||||
- apiVersion: v1
|
||||
data:
|
||||
baz: qux
|
||||
foo: changed-value
|
||||
new-data: new-value
|
||||
new-data2: new-value
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations: {}
|
||||
name: cm1
|
||||
namespace: myproject
|
||||
- kind: Service
|
||||
metadata:
|
||||
annotations: {}
|
||||
labels:
|
||||
app: svc1
|
||||
new-label: foo
|
||||
name: svc1
|
||||
namespace: myproject
|
||||
spec:
|
||||
ports:
|
||||
- name: "80"
|
||||
port: 81
|
||||
protocol: TCP
|
||||
targetPort: 81
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
status:
|
||||
loadBalancer: {}
|
||||
kind: List
|
||||
metadata: {}
|
7
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/3.request
vendored
Executable file
7
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/3.request
vendored
Executable file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io~1last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"baz\":\"qux\",\"foo\":\"changed-value\",\"new-data\":\"new-value\",\"new-data2\":\"new-value\",\"new-data3\":\"newivalue\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"cm1\",\"namespace\":\"myproject\"}}\n"
|
||||
}
|
||||
}
|
||||
}
|
21
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/3.response
vendored
Executable file
21
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/3.response
vendored
Executable file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"kind": "ConfigMap",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "cm1",
|
||||
"namespace": "myproject",
|
||||
"selfLink": "/api/v1/namespaces/myproject/configmaps/cm1",
|
||||
"uid": "cc08a131-3d6f-11e7-8ef0-c85b76034b7b",
|
||||
"resourceVersion": "3554",
|
||||
"creationTimestamp": "2017-05-20T15:20:03Z",
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"data\":{\"baz\":\"qux\",\"foo\":\"changed-value\",\"new-data\":\"new-value\",\"new-data2\":\"new-value\",\"new-data3\":\"newivalue\"},\"kind\":\"ConfigMap\",\"metadata\":{\"annotations\":{},\"name\":\"cm1\",\"namespace\":\"myproject\"}}\n"
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"baz": "qux",
|
||||
"foo": "changed-value",
|
||||
"new-data": "new-value",
|
||||
"new-data2": "new-value"
|
||||
}
|
||||
}
|
7
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/4.request
vendored
Executable file
7
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/4.request
vendored
Executable file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io~1last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
}
|
||||
}
|
||||
}
|
35
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/4.response
vendored
Executable file
35
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/4.response
vendored
Executable file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "svc1",
|
||||
"namespace": "myproject",
|
||||
"selfLink": "/api/v1/namespaces/myproject/services/svc1",
|
||||
"uid": "d8b96f0b-3d6f-11e7-8ef0-c85b76034b7b",
|
||||
"resourceVersion": "3555",
|
||||
"creationTimestamp": "2017-05-20T15:20:24Z",
|
||||
"labels": {
|
||||
"app": "svc1",
|
||||
"new-label": "foo"
|
||||
},
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label2\":\"foo2\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":82,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"ports": [
|
||||
{
|
||||
"name": "80",
|
||||
"protocol": "TCP",
|
||||
"port": 81,
|
||||
"targetPort": 81
|
||||
}
|
||||
],
|
||||
"clusterIP": "172.30.32.183",
|
||||
"type": "ClusterIP",
|
||||
"sessionAffinity": "None"
|
||||
},
|
||||
"status": {
|
||||
"loadBalancer": {}
|
||||
}
|
||||
}
|
40
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/test.yaml
vendored
Executable file
40
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-list/test.yaml
vendored
Executable file
|
@ -0,0 +1,40 @@
|
|||
description: add a testcase description
|
||||
mode: edit-last-applied
|
||||
args:
|
||||
- configmaps/cm1
|
||||
- service/svc1
|
||||
namespace: "myproject"
|
||||
expectedStdout:
|
||||
- configmap "cm1" edited
|
||||
- service "svc1" edited
|
||||
expectedExitCode: 0
|
||||
steps:
|
||||
- type: request
|
||||
expectedMethod: GET
|
||||
expectedPath: /api/v1/namespaces/myproject/configmaps/cm1
|
||||
expectedInput: 0.request
|
||||
resultingStatusCode: 200
|
||||
resultingOutput: 0.response
|
||||
- type: request
|
||||
expectedMethod: GET
|
||||
expectedPath: /api/v1/namespaces/myproject/services/svc1
|
||||
expectedInput: 1.request
|
||||
resultingStatusCode: 200
|
||||
resultingOutput: 1.response
|
||||
- type: edit
|
||||
expectedInput: 2.original
|
||||
resultingOutput: 2.edited
|
||||
- type: request
|
||||
expectedMethod: PATCH
|
||||
expectedPath: /api/v1/namespaces/myproject/configmaps/cm1
|
||||
expectedContentType: application/merge-patch+json
|
||||
expectedInput: 3.request
|
||||
resultingStatusCode: 200
|
||||
resultingOutput: 3.response
|
||||
- type: request
|
||||
expectedMethod: PATCH
|
||||
expectedPath: /api/v1/namespaces/myproject/services/svc1
|
||||
expectedContentType: application/merge-patch+json
|
||||
expectedInput: 4.request
|
||||
resultingStatusCode: 200
|
||||
resultingOutput: 4.response
|
0
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/0.request
vendored
Executable file
0
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/0.request
vendored
Executable file
35
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/0.response
vendored
Executable file
35
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/0.response
vendored
Executable file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "svc1",
|
||||
"namespace": "myproject",
|
||||
"selfLink": "/api/v1/namespaces/myproject/services/svc1",
|
||||
"uid": "1e16d988-3d72-11e7-8ef0-c85b76034b7b",
|
||||
"resourceVersion": "3731",
|
||||
"creationTimestamp": "2017-05-20T15:36:39Z",
|
||||
"labels": {
|
||||
"app": "svc1",
|
||||
"new-label": "foo"
|
||||
},
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"ports": [
|
||||
{
|
||||
"name": "80",
|
||||
"protocol": "TCP",
|
||||
"port": 81,
|
||||
"targetPort": 81
|
||||
}
|
||||
],
|
||||
"clusterIP": "172.30.105.209",
|
||||
"type": "ClusterIP",
|
||||
"sessionAffinity": "None"
|
||||
},
|
||||
"status": {
|
||||
"loadBalancer": {}
|
||||
}
|
||||
}
|
21
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/1.edited
vendored
Executable file
21
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/1.edited
vendored
Executable file
|
@ -0,0 +1,21 @@
|
|||
# Please edit the 'last-applied-configuration' annotations below.
|
||||
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
|
||||
#
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations: {}
|
||||
labels:
|
||||
app: svc1
|
||||
new-label: foo
|
||||
name: svc1
|
||||
namespace: myproject
|
||||
spec
|
||||
ports:
|
||||
name: "80"
|
||||
port: 81
|
||||
protocol: TCP
|
||||
targetPort: 81
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
status:
|
||||
loadBalancer: {
|
21
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/1.original
vendored
Executable file
21
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/1.original
vendored
Executable file
|
@ -0,0 +1,21 @@
|
|||
# Please edit the 'last-applied-configuration' annotations below.
|
||||
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
|
||||
#
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations: {}
|
||||
labels:
|
||||
app: svc1
|
||||
new-label: foo
|
||||
name: svc1
|
||||
namespace: myproject
|
||||
spec:
|
||||
ports:
|
||||
- name: "80"
|
||||
port: 81
|
||||
protocol: TCP
|
||||
targetPort: 81
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
status:
|
||||
loadBalancer: {}
|
24
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/2.edited
vendored
Executable file
24
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/2.edited
vendored
Executable file
|
@ -0,0 +1,24 @@
|
|||
# Please edit the 'last-applied-configuration' annotations below.
|
||||
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
|
||||
#
|
||||
# The edited file had a syntax error: error converting YAML to JSON: yaml: line 12: could not find expected ':'
|
||||
#
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations: {}
|
||||
labels:
|
||||
app: svc1
|
||||
new-label: foo
|
||||
new-label1: foo1
|
||||
name: svc1
|
||||
namespace: myproject
|
||||
spec:
|
||||
ports:
|
||||
- name: "80"
|
||||
port: 81
|
||||
protocol: TCP
|
||||
targetPort: 81
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
status:
|
||||
loadBalancer: {}
|
23
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/2.original
vendored
Executable file
23
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/2.original
vendored
Executable file
|
@ -0,0 +1,23 @@
|
|||
# Please edit the 'last-applied-configuration' annotations below.
|
||||
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
|
||||
#
|
||||
# The edited file had a syntax error: error converting YAML to JSON: yaml: line 12: could not find expected ':'
|
||||
#
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations: {}
|
||||
labels:
|
||||
app: svc1
|
||||
new-label: foo
|
||||
name: svc1
|
||||
namespace: myproject
|
||||
spec
|
||||
ports:
|
||||
name: "80"
|
||||
port: 81
|
||||
protocol: TCP
|
||||
targetPort: 81
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
status:
|
||||
loadBalancer: {
|
7
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/3.request
vendored
Executable file
7
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/3.request
vendored
Executable file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io~1last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label1\":\"foo1\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
}
|
||||
}
|
||||
}
|
35
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/3.response
vendored
Executable file
35
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/3.response
vendored
Executable file
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "svc1",
|
||||
"namespace": "myproject",
|
||||
"selfLink": "/api/v1/namespaces/myproject/services/svc1",
|
||||
"uid": "1e16d988-3d72-11e7-8ef0-c85b76034b7b",
|
||||
"resourceVersion": "3857",
|
||||
"creationTimestamp": "2017-05-20T15:36:39Z",
|
||||
"labels": {
|
||||
"app": "svc1",
|
||||
"new-label": "foo"
|
||||
},
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"labels\":{\"app\":\"svc1\",\"new-label\":\"foo\",\"new-label1\":\"foo1\"},\"name\":\"svc1\",\"namespace\":\"myproject\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":81}],\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"ports": [
|
||||
{
|
||||
"name": "80",
|
||||
"protocol": "TCP",
|
||||
"port": 81,
|
||||
"targetPort": 81
|
||||
}
|
||||
],
|
||||
"clusterIP": "172.30.105.209",
|
||||
"type": "ClusterIP",
|
||||
"sessionAffinity": "None"
|
||||
},
|
||||
"status": {
|
||||
"loadBalancer": {}
|
||||
}
|
||||
}
|
28
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/test.yaml
vendored
Executable file
28
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied-syntax-error/test.yaml
vendored
Executable file
|
@ -0,0 +1,28 @@
|
|||
description: edit with a syntax error, then re-edit and save
|
||||
mode: edit-last-applied
|
||||
args:
|
||||
- service/svc1
|
||||
namespace: myproject
|
||||
expectedStdout:
|
||||
- "service \"svc1\" edited"
|
||||
expectedExitCode: 0
|
||||
steps:
|
||||
- type: request
|
||||
expectedMethod: GET
|
||||
expectedPath: /api/v1/namespaces/myproject/services/svc1
|
||||
expectedInput: 0.request
|
||||
resultingStatusCode: 200
|
||||
resultingOutput: 0.response
|
||||
- type: edit
|
||||
expectedInput: 1.original
|
||||
resultingOutput: 1.edited
|
||||
- type: edit
|
||||
expectedInput: 2.original
|
||||
resultingOutput: 2.edited
|
||||
- type: request
|
||||
expectedMethod: PATCH
|
||||
expectedPath: /api/v1/namespaces/myproject/services/svc1
|
||||
expectedContentType: application/merge-patch+json
|
||||
expectedInput: 3.request
|
||||
resultingStatusCode: 200
|
||||
resultingOutput: 3.response
|
38
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/0.response
vendored
Executable file
38
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/0.response
vendored
Executable file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "svc1",
|
||||
"namespace": "myproject",
|
||||
"selfLink": "/api/v1/namespaces/myproject/services/svc1",
|
||||
"uid": "bc66b442-3d6a-11e7-8ef0-c85b76034b7b",
|
||||
"resourceVersion": "3036",
|
||||
"creationTimestamp": "2017-05-20T14:43:49Z",
|
||||
"labels": {
|
||||
"app": "svc1",
|
||||
"new-label": "new-value"
|
||||
},
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-01T21:14:09Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"myproject\",\"resourceVersion\":\"20820\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"ports": [
|
||||
{
|
||||
"name": "80",
|
||||
"protocol": "TCP",
|
||||
"port": 81,
|
||||
"targetPort": 80
|
||||
}
|
||||
],
|
||||
"selector": {
|
||||
"app": "svc1"
|
||||
},
|
||||
"clusterIP": "172.30.136.24",
|
||||
"type": "ClusterIP",
|
||||
"sessionAffinity": "None"
|
||||
},
|
||||
"status": {
|
||||
"loadBalancer": {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
# Please edit the 'last-applied-configuration' annotations below.
|
||||
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
|
||||
#
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations: {}
|
||||
creationTimestamp: 2017-02-01T21:14:09Z
|
||||
labels:
|
||||
app: svc1
|
||||
new-label: new-value
|
||||
name: svc1
|
||||
namespace: myproject
|
||||
resourceVersion: "20820"
|
||||
spec:
|
||||
ports:
|
||||
- name: "80"
|
||||
port: 81
|
||||
protocol: TCP
|
||||
targetPort: 92
|
||||
selector:
|
||||
app: svc1
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
status:
|
||||
loadBalancer: {}
|
26
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/1.original
vendored
Executable file
26
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/1.original
vendored
Executable file
|
@ -0,0 +1,26 @@
|
|||
# Please edit the 'last-applied-configuration' annotations below.
|
||||
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
|
||||
#
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
annotations: {}
|
||||
creationTimestamp: 2017-02-01T21:14:09Z
|
||||
labels:
|
||||
app: svc1
|
||||
new-label: new-value
|
||||
name: svc1
|
||||
namespace: myproject
|
||||
resourceVersion: "20820"
|
||||
spec:
|
||||
ports:
|
||||
- name: "80"
|
||||
port: 81
|
||||
protocol: TCP
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: svc1
|
||||
sessionAffinity: None
|
||||
type: ClusterIP
|
||||
status:
|
||||
loadBalancer: {}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io~1last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-01T21:14:09Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"myproject\",\"resourceVersion\":\"20820\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":92}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
}
|
||||
}
|
||||
}
|
38
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/2.response
vendored
Executable file
38
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/2.response
vendored
Executable file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"kind": "Service",
|
||||
"apiVersion": "v1",
|
||||
"metadata": {
|
||||
"name": "svc1",
|
||||
"namespace": "myproject",
|
||||
"selfLink": "/api/v1/namespaces/myproject/services/svc1",
|
||||
"uid": "bc66b442-3d6a-11e7-8ef0-c85b76034b7b",
|
||||
"resourceVersion": "3093",
|
||||
"creationTimestamp": "2017-05-20T14:43:49Z",
|
||||
"labels": {
|
||||
"app": "svc1",
|
||||
"new-label": "new-value"
|
||||
},
|
||||
"annotations": {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"creationTimestamp\":\"2017-02-01T21:14:09Z\",\"labels\":{\"app\":\"svc1\",\"new-label\":\"new-value\"},\"name\":\"svc1\",\"namespace\":\"myproject\",\"resourceVersion\":\"20820\"},\"spec\":{\"ports\":[{\"name\":\"80\",\"port\":81,\"protocol\":\"TCP\",\"targetPort\":92}],\"selector\":{\"app\":\"svc1\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"ports": [
|
||||
{
|
||||
"name": "80",
|
||||
"protocol": "TCP",
|
||||
"port": 81,
|
||||
"targetPort": 80
|
||||
}
|
||||
],
|
||||
"selector": {
|
||||
"app": "svc1"
|
||||
},
|
||||
"clusterIP": "172.30.136.24",
|
||||
"type": "ClusterIP",
|
||||
"sessionAffinity": "None"
|
||||
},
|
||||
"status": {
|
||||
"loadBalancer": {}
|
||||
}
|
||||
}
|
27
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/test.yaml
vendored
Executable file
27
pkg/kubectl/cmd/testdata/edit/testcase-apply-edit-last-applied/test.yaml
vendored
Executable file
|
@ -0,0 +1,27 @@
|
|||
description: add a testcase description
|
||||
mode: edit-last-applied
|
||||
args:
|
||||
- service
|
||||
- svc1
|
||||
outputFormat: yaml
|
||||
namespace: myproject
|
||||
expectedStdout:
|
||||
- service "svc1" edited
|
||||
expectedExitCode: 0
|
||||
steps:
|
||||
- type: request
|
||||
expectedMethod: GET
|
||||
expectedPath: /api/v1/namespaces/myproject/services/svc1
|
||||
expectedInput: 0.request
|
||||
resultingStatusCode: 200
|
||||
resultingOutput: 0.response
|
||||
- type: edit
|
||||
expectedInput: 1.original
|
||||
resultingOutput: 1.edited
|
||||
- type: request
|
||||
expectedMethod: PATCH
|
||||
expectedPath: /api/v1/namespaces/myproject/services/svc1
|
||||
expectedContentType: application/merge-patch+json
|
||||
expectedInput: 2.request
|
||||
resultingStatusCode: 200
|
||||
resultingOutput: 2.response
|
|
@ -19,6 +19,7 @@ package editor
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -29,7 +30,7 @@ import (
|
|||
"github.com/evanphx/json-patch"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
apierrors "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"
|
||||
|
@ -85,7 +86,7 @@ type editPrinterOptions struct {
|
|||
|
||||
// Complete completes all the required options
|
||||
func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []string) error {
|
||||
if o.EditMode != NormalEditMode && o.EditMode != EditBeforeCreateMode {
|
||||
if o.EditMode != NormalEditMode && o.EditMode != EditBeforeCreateMode && o.EditMode != ApplyEditMode {
|
||||
return fmt.Errorf("unsupported edit mode %q", o.EditMode)
|
||||
}
|
||||
if o.Output != "" {
|
||||
|
@ -104,8 +105,8 @@ func (o *EditOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []
|
|||
return err
|
||||
}
|
||||
b := resource.NewBuilder(mapper, f.CategoryExpander(), typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme)
|
||||
if o.EditMode == NormalEditMode {
|
||||
// when do normal edit we need to always retrieve the latest resource from server
|
||||
if o.EditMode == NormalEditMode || o.EditMode == ApplyEditMode {
|
||||
// when do normal edit or apply edit we need to always retrieve the latest resource from server
|
||||
b = b.ResourceTypeOrNameArgs(true, args...).Latest()
|
||||
}
|
||||
r := b.NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||
|
@ -158,7 +159,6 @@ func (o *EditOptions) Run() error {
|
|||
)
|
||||
|
||||
containsError := false
|
||||
|
||||
// loop until we succeed or cancel editing
|
||||
for {
|
||||
// get the object we're going to serialize as input to the editor
|
||||
|
@ -188,7 +188,7 @@ func (o *EditOptions) Run() error {
|
|||
}
|
||||
|
||||
if o.editPrinterOptions.addHeader {
|
||||
results.header.writeTo(w)
|
||||
results.header.writeTo(w, o.EditMode)
|
||||
}
|
||||
|
||||
if !containsError {
|
||||
|
@ -230,7 +230,7 @@ func (o *EditOptions) Run() error {
|
|||
file: file,
|
||||
}
|
||||
containsError = true
|
||||
fmt.Fprintln(o.ErrOut, results.addError(errors.NewInvalid(api.Kind(""), "", field.ErrorList{field.Invalid(nil, "The edited file failed validation", fmt.Sprintf("%v", err))}), infos[0]))
|
||||
fmt.Fprintln(o.ErrOut, results.addError(apierrors.NewInvalid(api.Kind(""), "", field.ErrorList{field.Invalid(nil, "The edited file failed validation", fmt.Sprintf("%v", err))}), infos[0]))
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -280,6 +280,8 @@ func (o *EditOptions) Run() error {
|
|||
switch o.EditMode {
|
||||
case NormalEditMode:
|
||||
err = o.visitToPatch(infos, updatedVisitor, &results)
|
||||
case ApplyEditMode:
|
||||
err = o.visitToApplyEditPatch(infos, updatedVisitor)
|
||||
case EditBeforeCreateMode:
|
||||
err = o.visitToCreate(updatedVisitor)
|
||||
default:
|
||||
|
@ -326,6 +328,31 @@ func (o *EditOptions) Run() error {
|
|||
return err
|
||||
}
|
||||
return editFn(infos)
|
||||
case ApplyEditMode:
|
||||
infos, err := o.OriginalResult.Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var annotationInfos []*resource.Info
|
||||
for i := range infos {
|
||||
data, err := kubectl.GetOriginalConfiguration(infos[i].Mapping, infos[i].Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if data == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
tempInfos, err := o.updatedResultGetter(data).Infos()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
annotationInfos = append(annotationInfos, tempInfos[0])
|
||||
}
|
||||
if len(annotationInfos) == 0 {
|
||||
return errors.New("no last-applied-configuration annotation found on resources, to create the annotation, use command `kubectl apply set-last-applied --create-annotation`")
|
||||
}
|
||||
return editFn(annotationInfos)
|
||||
// If doing an edit before created, we don't want a list and instead want the normal behavior as kubectl create.
|
||||
case EditBeforeCreateMode:
|
||||
return o.OriginalResult.Visit(func(info *resource.Info, err error) error {
|
||||
|
@ -336,6 +363,110 @@ func (o *EditOptions) Run() error {
|
|||
}
|
||||
}
|
||||
|
||||
func (o *EditOptions) visitToApplyEditPatch(originalInfos []*resource.Info, patchVisitor resource.Visitor) error {
|
||||
err := patchVisitor.Visit(func(info *resource.Info, incomingErr error) error {
|
||||
editObjUID, err := meta.NewAccessor().UID(info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var originalInfo *resource.Info
|
||||
for _, i := range originalInfos {
|
||||
originalObjUID, err := meta.NewAccessor().UID(i.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if editObjUID == originalObjUID {
|
||||
originalInfo = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if originalInfo == nil {
|
||||
return fmt.Errorf("no original object found for %#v", info.Object)
|
||||
}
|
||||
|
||||
originalJS, err := encodeToJson(o.Encoder, originalInfo.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
editedJS, err := encodeToJson(o.Encoder, info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(originalJS, editedJS) {
|
||||
cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "skipped")
|
||||
return nil
|
||||
} else {
|
||||
err := o.annotationPatch(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmdutil.PrintSuccess(o.Mapper, false, o.Out, info.Mapping.Resource, info.Name, false, "edited")
|
||||
return nil
|
||||
}
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *EditOptions) annotationPatch(update *resource.Info) error {
|
||||
patch, _, patchType, err := GetApplyPatch(update.Object, o.Encoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapping := update.ResourceMapping()
|
||||
client, err := o.f.UnstructuredClientForMapping(mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
helper := resource.NewHelper(client, mapping)
|
||||
_, err = helper.Patch(o.CmdNamespace, update.Name, patchType, patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetApplyPatch(obj runtime.Object, codec runtime.Encoder) ([]byte, []byte, types.PatchType, error) {
|
||||
beforeJSON, err := encodeToJson(codec, obj)
|
||||
if err != nil {
|
||||
return nil, []byte(""), types.MergePatchType, err
|
||||
}
|
||||
objCopy, err := api.Scheme.Copy(obj)
|
||||
if err != nil {
|
||||
return nil, beforeJSON, types.MergePatchType, err
|
||||
}
|
||||
accessor := meta.NewAccessor()
|
||||
annotations, err := accessor.Annotations(objCopy)
|
||||
if err != nil {
|
||||
return nil, beforeJSON, types.MergePatchType, err
|
||||
}
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
}
|
||||
annotations[api.LastAppliedConfigAnnotation] = string(beforeJSON)
|
||||
accessor.SetAnnotations(objCopy, annotations)
|
||||
afterJSON, err := encodeToJson(codec, objCopy)
|
||||
if err != nil {
|
||||
return nil, beforeJSON, types.MergePatchType, err
|
||||
}
|
||||
patch, err := jsonpatch.CreateMergePatch(beforeJSON, afterJSON)
|
||||
return patch, beforeJSON, types.MergePatchType, err
|
||||
}
|
||||
|
||||
func encodeToJson(codec runtime.Encoder, obj runtime.Object) ([]byte, error) {
|
||||
serialization, err := runtime.Encode(codec, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
js, err := yaml.ToJSON(serialization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return js, nil
|
||||
}
|
||||
|
||||
func getPrinter(format string) *editPrinterOptions {
|
||||
switch format {
|
||||
case "json":
|
||||
|
@ -386,22 +517,12 @@ func (o *EditOptions) visitToPatch(
|
|||
return fmt.Errorf("no original object found for %#v", info.Object)
|
||||
}
|
||||
|
||||
originalSerialization, err := runtime.Encode(o.Encoder, originalInfo.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
editedSerialization, err := runtime.Encode(o.Encoder, info.Object)
|
||||
originalJS, err := encodeToJson(o.Encoder, originalInfo.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// compute the patch on a per-item basis
|
||||
// use strategic merge to create a patch
|
||||
originalJS, err := yaml.ToJSON(originalSerialization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
editedJS, err := yaml.ToJSON(editedSerialization)
|
||||
editedJS, err := encodeToJson(o.Encoder, info.Object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -501,6 +622,7 @@ type EditMode string
|
|||
const (
|
||||
NormalEditMode EditMode = "normal_mode"
|
||||
EditBeforeCreateMode EditMode = "edit_before_create_mode"
|
||||
ApplyEditMode EditMode = "edit_last_applied_mode"
|
||||
)
|
||||
|
||||
// editReason preserves a message about the reason this file must be edited again
|
||||
|
@ -515,12 +637,20 @@ type editHeader struct {
|
|||
}
|
||||
|
||||
// writeTo outputs the current header information into a stream
|
||||
func (h *editHeader) writeTo(w io.Writer) error {
|
||||
fmt.Fprint(w, `# Please edit the object below. Lines beginning with a '#' will be ignored,
|
||||
func (h *editHeader) writeTo(w io.Writer, editMode EditMode) error {
|
||||
if editMode == ApplyEditMode {
|
||||
fmt.Fprint(w, `# Please edit the 'last-applied-configuration' annotations below.
|
||||
# Lines beginning with a '#' will be ignored, and an empty file will abort the edit.
|
||||
#
|
||||
`)
|
||||
} else {
|
||||
fmt.Fprint(w, `# Please edit the object below. Lines beginning with a '#' will be ignored,
|
||||
# and an empty file will abort the edit. If an error occurs while saving this file will be
|
||||
# reopened with the relevant failures.
|
||||
#
|
||||
`)
|
||||
}
|
||||
|
||||
for _, r := range h.reasons {
|
||||
if len(r.other) > 0 {
|
||||
fmt.Fprintf(w, "# %s:\n", r.head)
|
||||
|
@ -552,12 +682,12 @@ type editResults struct {
|
|||
|
||||
func (r *editResults) addError(err error, info *resource.Info) string {
|
||||
switch {
|
||||
case errors.IsInvalid(err):
|
||||
case apierrors.IsInvalid(err):
|
||||
r.edit = append(r.edit, info)
|
||||
reason := editReason{
|
||||
head: fmt.Sprintf("%s %q was not valid", info.Mapping.Resource, info.Name),
|
||||
}
|
||||
if err, ok := err.(errors.APIStatus); ok {
|
||||
if err, ok := err.(apierrors.APIStatus); ok {
|
||||
if details := err.Status().Details; details != nil {
|
||||
for _, cause := range details.Causes {
|
||||
reason.other = append(reason.other, fmt.Sprintf("%s: %s", cause.Field, cause.Message))
|
||||
|
@ -566,7 +696,7 @@ func (r *editResults) addError(err error, info *resource.Info) string {
|
|||
}
|
||||
r.header.reasons = append(r.header.reasons, reason)
|
||||
return fmt.Sprintf("error: %s %q is invalid", info.Mapping.Resource, info.Name)
|
||||
case errors.IsNotFound(err):
|
||||
case apierrors.IsNotFound(err):
|
||||
r.notfound++
|
||||
return fmt.Sprintf("error: %s %q could not be found on the server", info.Mapping.Resource, info.Name)
|
||||
default:
|
||||
|
|
|
@ -436,7 +436,7 @@ func AddApplyAnnotationFlags(cmd *cobra.Command) {
|
|||
}
|
||||
|
||||
func AddApplyAnnotationVarFlags(cmd *cobra.Command, applyAnnotation *bool) {
|
||||
cmd.Flags().BoolVar(applyAnnotation, ApplyAnnotationsFlag, false, "If true, the configuration of current object will be saved in its annotation. This is useful when you want to perform kubectl apply on this object in the future.")
|
||||
cmd.Flags().BoolVar(applyAnnotation, ApplyAnnotationsFlag, false, "If true, the configuration of current object will be saved in its annotation. Otherwise, the annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.")
|
||||
}
|
||||
|
||||
// AddGeneratorFlags adds flags common to resource generation commands
|
||||
|
|
Loading…
Reference in New Issue