Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
c641834b
Commit
c641834b
authored
Sep 21, 2016
by
ymqytw
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
support editing before creating resource
parent
192e18e2
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
117 additions
and
30 deletions
+117
-30
test-cmd.sh
hack/make-rules/test-cmd.sh
+19
-0
cmd.go
pkg/kubectl/cmd/cmd.go
+1
-1
create.go
pkg/kubectl/cmd/create.go
+17
-5
create_test.go
pkg/kubectl/cmd/create_test.go
+8
-4
edit.go
pkg/kubectl/cmd/edit.go
+72
-20
No files found.
hack/make-rules/test-cmd.sh
View file @
c641834b
...
...
@@ -720,6 +720,25 @@ runTests() {
# Pre-condition: no POD exists
create_and_use_new_namespace
kube::test::get_object_assert pods
"{{range.items}}{{
$id_field
}}:{{end}}"
''
## kubectl create --edit can update the image field of a POD. tmp-editor.sh is a fake editor
TEMP
=
$(
mktemp
/tmp/tmp-editor-XXXXXXXX.sh
)
echo
-e
"#!/bin/bash
\n
$SED
-i
\"
s/gcr.io
\/
google_containers
\/
serve_hostname/nginx/g
\"
\$
1"
>
${
TEMP
}
chmod
+x
${
TEMP
}
# Command
EDITOR
=
${
TEMP
}
kubectl create
--edit
-f
test
/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml
"
${
kube_flags
[@]
}
"
# Post-condition: valid-pod POD is created and has image gcr.io/google_containers/serve_hostname
kube::test::get_object_assert pods
"{{range.items}}{{
$id_field
}}:{{end}}"
'valid-pod:'
kube::test::get_object_assert pods
"{{range.items}}{{
$image_field
}}:{{end}}"
'nginx:'
# Clean up
rm
${
TEMP
}
kubectl delete pods/valid-pod
"
${
kube_flags
[@]
}
"
## kubectl create --edit won't create anything if user makes no changes
[
"
$(
EDITOR
=
cat
kubectl create
--edit
-f
test
/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml
-o
json 2>&1 |
grep
'Edit cancelled'
)
"
]
## Create valid-pod POD
# Pre-condition: no POD exists
kube::test::get_object_assert pods
"{{range.items}}{{
$id_field
}}:{{end}}"
''
# Command
kubectl create
-f
test
/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml
"
${
kube_flags
[@]
}
"
# Post-condition: valid-pod POD is created
...
...
pkg/kubectl/cmd/cmd.go
View file @
c641834b
...
...
@@ -221,7 +221,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
{
Message
:
"Basic Commands (Beginner):"
,
Commands
:
[]
*
cobra
.
Command
{
NewCmdCreate
(
f
,
out
),
NewCmdCreate
(
f
,
out
,
err
),
NewCmdExposeService
(
f
,
out
),
NewCmdRun
(
f
,
in
,
out
,
err
),
set
.
NewCmdSet
(
f
,
out
,
err
),
...
...
pkg/kubectl/cmd/create.go
View file @
c641834b
...
...
@@ -19,6 +19,7 @@ package cmd
import
(
"fmt"
"io"
gruntime
"runtime"
"github.com/spf13/cobra"
...
...
@@ -41,10 +42,13 @@ var (
kubectl create -f ./pod.json
# Create a pod based on the JSON passed into stdin.
cat pod.json | kubectl create -f -`
)
cat pod.json | kubectl create -f -
# Edit the data in docker-registry.yaml in JSON using the v1 API format then create the resource using the edited data.
kubectl create -f docker-registry.yaml --edit --output-version=v1 -o json`
)
)
func
NewCmdCreate
(
f
cmdutil
.
Factory
,
out
io
.
Writer
)
*
cobra
.
Command
{
func
NewCmdCreate
(
f
cmdutil
.
Factory
,
out
,
errOut
io
.
Writer
)
*
cobra
.
Command
{
options
:=
&
resource
.
FilenameOptions
{}
cmd
:=
&
cobra
.
Command
{
...
...
@@ -58,8 +62,7 @@ func NewCmdCreate(f cmdutil.Factory, out io.Writer) *cobra.Command {
return
}
cmdutil
.
CheckErr
(
ValidateArgs
(
cmd
,
args
))
cmdutil
.
CheckErr
(
cmdutil
.
ValidateOutputArgs
(
cmd
))
cmdutil
.
CheckErr
(
RunCreate
(
f
,
cmd
,
out
,
options
))
cmdutil
.
CheckErr
(
RunCreate
(
f
,
cmd
,
out
,
errOut
,
options
))
},
}
...
...
@@ -68,6 +71,8 @@ func NewCmdCreate(f cmdutil.Factory, out io.Writer) *cobra.Command {
cmd
.
MarkFlagRequired
(
"filename"
)
cmdutil
.
AddValidateFlags
(
cmd
)
cmdutil
.
AddPrinterFlags
(
cmd
)
cmd
.
Flags
()
.
Bool
(
"edit"
,
false
,
"Edit the API resource before creating"
)
cmd
.
Flags
()
.
Bool
(
"windows-line-endings"
,
gruntime
.
GOOS
==
"windows"
,
"Only relevant if --edit=true. Use Windows line-endings (default Unix line-endings)"
)
cmdutil
.
AddApplyAnnotationFlags
(
cmd
)
cmdutil
.
AddRecordFlag
(
cmd
)
cmdutil
.
AddDryRunFlag
(
cmd
)
...
...
@@ -91,7 +96,10 @@ func ValidateArgs(cmd *cobra.Command, args []string) error {
return
nil
}
func
RunCreate
(
f
cmdutil
.
Factory
,
cmd
*
cobra
.
Command
,
out
io
.
Writer
,
options
*
resource
.
FilenameOptions
)
error
{
func
RunCreate
(
f
cmdutil
.
Factory
,
cmd
*
cobra
.
Command
,
out
,
errOut
io
.
Writer
,
options
*
resource
.
FilenameOptions
)
error
{
if
cmdutil
.
GetFlagBool
(
cmd
,
"edit"
)
{
return
RunEditOnCreate
(
f
,
out
,
errOut
,
cmd
,
options
)
}
schema
,
err
:=
f
.
Validator
(
cmdutil
.
GetFlagBool
(
cmd
,
"validate"
),
cmdutil
.
GetFlagString
(
cmd
,
"schema-cache-dir"
))
if
err
!=
nil
{
return
err
...
...
@@ -159,6 +167,10 @@ func RunCreate(f cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *re
return
nil
}
func
RunEditOnCreate
(
f
cmdutil
.
Factory
,
out
,
errOut
io
.
Writer
,
cmd
*
cobra
.
Command
,
options
*
resource
.
FilenameOptions
)
error
{
return
runEdit
(
f
,
out
,
errOut
,
cmd
,
[]
string
{},
options
,
EditBeforeCreateMode
)
}
// createAndRefresh creates an object from input info and refreshes info with that object
func
createAndRefresh
(
info
*
resource
.
Info
)
error
{
obj
,
err
:=
resource
.
NewHelper
(
info
.
Client
,
info
.
Mapping
)
.
Create
(
info
.
Namespace
,
true
,
info
.
Object
)
...
...
pkg/kubectl/cmd/create_test.go
View file @
c641834b
...
...
@@ -29,9 +29,10 @@ import (
func
TestExtraArgsFail
(
t
*
testing
.
T
)
{
initTestErrorHandler
(
t
)
buf
:=
bytes
.
NewBuffer
([]
byte
{})
errBuf
:=
bytes
.
NewBuffer
([]
byte
{})
f
,
_
,
_
,
_
:=
cmdtesting
.
NewAPIFactory
()
c
:=
NewCmdCreate
(
f
,
buf
)
c
:=
NewCmdCreate
(
f
,
buf
,
errBuf
)
if
ValidateArgs
(
c
,
[]
string
{
"rc"
})
==
nil
{
t
.
Errorf
(
"unexpected non-error"
)
}
...
...
@@ -59,8 +60,9 @@ func TestCreateObject(t *testing.T) {
}
tf
.
Namespace
=
"test"
buf
:=
bytes
.
NewBuffer
([]
byte
{})
errBuf
:=
bytes
.
NewBuffer
([]
byte
{})
cmd
:=
NewCmdCreate
(
f
,
buf
)
cmd
:=
NewCmdCreate
(
f
,
buf
,
errBuf
)
cmd
.
Flags
()
.
Set
(
"filename"
,
"../../../examples/guestbook/legacy/redis-master-controller.yaml"
)
cmd
.
Flags
()
.
Set
(
"output"
,
"name"
)
cmd
.
Run
(
cmd
,
[]
string
{})
...
...
@@ -94,8 +96,9 @@ func TestCreateMultipleObject(t *testing.T) {
}
tf
.
Namespace
=
"test"
buf
:=
bytes
.
NewBuffer
([]
byte
{})
errBuf
:=
bytes
.
NewBuffer
([]
byte
{})
cmd
:=
NewCmdCreate
(
f
,
buf
)
cmd
:=
NewCmdCreate
(
f
,
buf
,
errBuf
)
cmd
.
Flags
()
.
Set
(
"filename"
,
"../../../examples/guestbook/legacy/redis-master-controller.yaml"
)
cmd
.
Flags
()
.
Set
(
"filename"
,
"../../../examples/guestbook/frontend-service.yaml"
)
cmd
.
Flags
()
.
Set
(
"output"
,
"name"
)
...
...
@@ -129,8 +132,9 @@ func TestCreateDirectory(t *testing.T) {
}
tf
.
Namespace
=
"test"
buf
:=
bytes
.
NewBuffer
([]
byte
{})
errBuf
:=
bytes
.
NewBuffer
([]
byte
{})
cmd
:=
NewCmdCreate
(
f
,
buf
)
cmd
:=
NewCmdCreate
(
f
,
buf
,
errBuf
)
cmd
.
Flags
()
.
Set
(
"filename"
,
"../../../examples/guestbook/legacy"
)
cmd
.
Flags
()
.
Set
(
"output"
,
"name"
)
cmd
.
Run
(
cmd
,
[]
string
{})
...
...
pkg/kubectl/cmd/edit.go
View file @
c641834b
...
...
@@ -118,12 +118,16 @@ func NewCmdEdit(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
}
func
RunEdit
(
f
cmdutil
.
Factory
,
out
,
errOut
io
.
Writer
,
cmd
*
cobra
.
Command
,
args
[]
string
,
options
*
resource
.
FilenameOptions
)
error
{
return
runEdit
(
f
,
out
,
errOut
,
cmd
,
args
,
options
,
NormalEditMode
)
}
func
runEdit
(
f
cmdutil
.
Factory
,
out
,
errOut
io
.
Writer
,
cmd
*
cobra
.
Command
,
args
[]
string
,
options
*
resource
.
FilenameOptions
,
editMode
EditMode
)
error
{
o
,
err
:=
getPrinter
(
cmd
)
if
err
!=
nil
{
return
err
}
mapper
,
resourceMapper
,
r
,
cmdNamespace
,
err
:=
getMapperAndResult
(
f
,
args
,
options
)
mapper
,
resourceMapper
,
r
,
cmdNamespace
,
err
:=
getMapperAndResult
(
f
,
args
,
options
,
editMode
)
if
err
!=
nil
{
return
err
}
...
...
@@ -192,14 +196,15 @@ func RunEdit(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
if
err
!=
nil
{
return
preservedFile
(
err
,
results
.
file
,
errOut
)
}
if
bytes
.
Equal
(
stripComments
(
editedDiff
),
stripComments
(
edited
))
{
// Ugly hack right here. We will hit this either (1) when we try to
// save the same changes we tried to save in the previous iteration
// which means our changes are invalid or (2) when we exit the second
// time. The second case is more usual so we can probably live with it.
// TODO: A less hacky fix would be welcome :)
fmt
.
Fprintln
(
errOut
,
"Edit cancelled, no valid changes were saved."
)
return
nil
if
editMode
==
NormalEditMode
||
containsError
{
if
bytes
.
Equal
(
stripComments
(
editedDiff
),
stripComments
(
edited
))
{
// Ugly hack right here. We will hit this either (1) when we try to
// save the same changes we tried to save in the previous iteration
// which means our changes are invalid or (2) when we exit the second
// time. The second case is more usual so we can probably live with it.
// TODO: A less hacky fix would be welcome :)
return
preservedFile
(
fmt
.
Errorf
(
"%s"
,
"Edit cancelled, no valid changes were saved."
),
file
,
errOut
)
}
}
// cleanup any file from the previous pass
...
...
@@ -229,6 +234,7 @@ func RunEdit(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
fmt
.
Fprintln
(
errOut
,
"Edit cancelled, no changes made."
)
return
nil
}
lines
,
err
:=
hasLines
(
bytes
.
NewBuffer
(
edited
))
if
err
!=
nil
{
return
preservedFile
(
err
,
file
,
errOut
)
...
...
@@ -271,7 +277,14 @@ func RunEdit(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args
meta
.
SetList
(
updates
.
Object
,
mutatedObjects
)
}
err
=
visitToPatch
(
originalObj
,
updates
,
mapper
,
resourceMapper
,
encoder
,
out
,
errOut
,
defaultVersion
,
&
results
,
file
)
switch
editMode
{
case
NormalEditMode
:
err
=
visitToPatch
(
originalObj
,
updates
,
mapper
,
resourceMapper
,
encoder
,
out
,
errOut
,
defaultVersion
,
&
results
,
file
)
case
EditBeforeCreateMode
:
err
=
visitToCreate
(
updates
,
mapper
,
resourceMapper
,
out
,
errOut
,
defaultVersion
,
&
results
,
file
)
default
:
err
=
fmt
.
Errorf
(
"Not supported edit mode %q"
,
editMode
)
}
if
err
!=
nil
{
return
preservedFile
(
err
,
results
.
file
,
errOut
)
}
...
...
@@ -315,7 +328,8 @@ func getPrinter(cmd *cobra.Command) (*editPrinterOptions, error) {
ext
:
".json"
,
addHeader
:
false
,
},
nil
case
"yaml"
:
// If flag -o is not specified, use yaml as default
case
"yaml"
,
""
:
return
&
editPrinterOptions
{
printer
:
&
kubectl
.
YAMLPrinter
{},
ext
:
".yaml"
,
...
...
@@ -326,13 +340,24 @@ func getPrinter(cmd *cobra.Command) (*editPrinterOptions, error) {
}
}
func
getMapperAndResult
(
f
cmdutil
.
Factory
,
args
[]
string
,
options
*
resource
.
FilenameOptions
)
(
meta
.
RESTMapper
,
*
resource
.
Mapper
,
*
resource
.
Result
,
string
,
error
)
{
func
getMapperAndResult
(
f
cmdutil
.
Factory
,
args
[]
string
,
options
*
resource
.
FilenameOptions
,
editMode
EditMode
)
(
meta
.
RESTMapper
,
*
resource
.
Mapper
,
*
resource
.
Result
,
string
,
error
)
{
cmdNamespace
,
enforceNamespace
,
err
:=
f
.
DefaultNamespace
()
if
err
!=
nil
{
return
nil
,
nil
,
nil
,
""
,
err
}
mapper
,
typer
:=
f
.
Object
()
var
mapper
meta
.
RESTMapper
var
typer
runtime
.
ObjectTyper
switch
editMode
{
case
NormalEditMode
:
mapper
,
typer
=
f
.
Object
()
case
EditBeforeCreateMode
:
mapper
,
typer
,
err
=
f
.
UnstructuredObject
()
default
:
return
nil
,
nil
,
nil
,
""
,
fmt
.
Errorf
(
"Not supported edit mode %q"
,
editMode
)
}
if
err
!=
nil
{
return
nil
,
nil
,
nil
,
""
,
err
}
resourceMapper
:=
&
resource
.
Mapper
{
ObjectTyper
:
typer
,
RESTMapper
:
mapper
,
...
...
@@ -345,14 +370,21 @@ func getMapperAndResult(f cmdutil.Factory, args []string, options *resource.File
// compare two different GroupVersions).
Decoder
:
f
.
Decoder
(
false
),
}
r
:=
resource
.
NewBuilder
(
mapper
,
typer
,
resource
.
ClientMapperFunc
(
f
.
ClientForMapping
),
f
.
Decoder
(
true
))
.
NamespaceParam
(
cmdNamespace
)
.
DefaultNamespace
()
.
var
b
*
resource
.
Builder
switch
editMode
{
case
NormalEditMode
:
b
=
resource
.
NewBuilder
(
mapper
,
typer
,
resource
.
ClientMapperFunc
(
f
.
ClientForMapping
),
f
.
Decoder
(
true
))
.
ResourceTypeOrNameArgs
(
true
,
args
...
)
.
Latest
()
case
EditBeforeCreateMode
:
b
=
resource
.
NewBuilder
(
mapper
,
typer
,
resource
.
ClientMapperFunc
(
f
.
UnstructuredClientForMapping
),
runtime
.
UnstructuredJSONScheme
)
default
:
return
nil
,
nil
,
nil
,
""
,
fmt
.
Errorf
(
"Not supported edit mode %q"
,
editMode
)
}
r
:=
b
.
NamespaceParam
(
cmdNamespace
)
.
DefaultNamespace
()
.
FilenameParam
(
enforceNamespace
,
options
)
.
ResourceTypeOrNameArgs
(
true
,
args
...
)
.
ContinueOnError
()
.
Flatten
()
.
Latest
()
.
Do
()
err
=
r
.
Err
()
if
err
!=
nil
{
...
...
@@ -428,7 +460,7 @@ func visitToPatch(originalObj runtime.Object, updates *resource.Info, mapper met
if
err
!=
nil
{
glog
.
V
(
4
)
.
Infof
(
"Unable to calculate diff, no merge is possible: %v"
,
err
)
if
strategicpatch
.
IsPreconditionFailed
(
err
)
{
return
preservedFile
(
nil
,
file
,
errOut
)
return
fmt
.
Errorf
(
"%s"
,
"At least one of apiVersion, kind and name was changed"
)
}
return
err
}
...
...
@@ -446,6 +478,19 @@ func visitToPatch(originalObj runtime.Object, updates *resource.Info, mapper met
return
err
}
func
visitToCreate
(
updates
*
resource
.
Info
,
mapper
meta
.
RESTMapper
,
resourceMapper
*
resource
.
Mapper
,
out
,
errOut
io
.
Writer
,
defaultVersion
unversioned
.
GroupVersion
,
results
*
editResults
,
file
string
)
error
{
createVisitor
:=
resource
.
NewFlattenListVisitor
(
updates
,
resourceMapper
)
err
:=
createVisitor
.
Visit
(
func
(
info
*
resource
.
Info
,
incomingErr
error
)
error
{
results
.
version
=
defaultVersion
if
err
:=
createAndRefresh
(
info
);
err
!=
nil
{
return
err
}
cmdutil
.
PrintSuccess
(
mapper
,
false
,
out
,
info
.
Mapping
.
Resource
,
info
.
Name
,
false
,
"created"
)
return
nil
})
return
err
}
func
visitAnnotation
(
cmd
*
cobra
.
Command
,
f
cmdutil
.
Factory
,
updates
*
resource
.
Info
,
resourceMapper
*
resource
.
Mapper
,
encoder
runtime
.
Encoder
)
([]
runtime
.
Object
,
error
)
{
mutatedObjects
:=
[]
runtime
.
Object
{}
annotationVisitor
:=
resource
.
NewFlattenListVisitor
(
updates
,
resourceMapper
)
...
...
@@ -468,6 +513,13 @@ func visitAnnotation(cmd *cobra.Command, f cmdutil.Factory, updates *resource.In
return
mutatedObjects
,
err
}
type
EditMode
string
const
(
NormalEditMode
EditMode
=
"normal_mode"
EditBeforeCreateMode
EditMode
=
"edit_before_create_mode"
)
// editReason preserves a message about the reason this file must be edited again
type
editReason
struct
{
head
string
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment