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
cfe41dc0
Commit
cfe41dc0
authored
Jul 24, 2015
by
Mike Danese
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #11729 from smarterclayton/make_exec_flexible
Exec should be easier to reuse as a command
parents
7b0437a2
0b5410f3
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
139 additions
and
72 deletions
+139
-72
exec.go
pkg/kubectl/cmd/exec.go
+91
-46
exec_test.go
pkg/kubectl/cmd/exec_test.go
+48
-26
No files found.
pkg/kubectl/cmd/exec.go
View file @
cfe41dc0
...
...
@@ -17,6 +17,7 @@ limitations under the License.
package
cmd
import
(
"fmt"
"io"
"os"
"os/signal"
...
...
@@ -44,96 +45,144 @@ $ kubectl exec 123456-7890 -c ruby-container -i -t -- bash -il`
)
func
NewCmdExec
(
f
*
cmdutil
.
Factory
,
cmdIn
io
.
Reader
,
cmdOut
,
cmdErr
io
.
Writer
)
*
cobra
.
Command
{
params
:=
&
execParams
{}
options
:=
&
ExecOptions
{
In
:
cmdIn
,
Out
:
cmdOut
,
Err
:
cmdErr
,
Executor
:
&
DefaultRemoteExecutor
{},
}
cmd
:=
&
cobra
.
Command
{
Use
:
"exec POD -c CONTAINER -- COMMAND [args...]"
,
Short
:
"Execute a command in a container."
,
Long
:
"Execute a command in a container."
,
Example
:
exec_example
,
Run
:
func
(
cmd
*
cobra
.
Command
,
args
[]
string
)
{
err
:=
RunExec
(
f
,
cmd
,
cmdIn
,
cmdOut
,
cmdErr
,
params
,
args
,
&
defaultRemoteExecutor
{})
cmdutil
.
CheckErr
(
err
)
cmdutil
.
CheckErr
(
options
.
Complete
(
f
,
cmd
,
args
))
cmdutil
.
CheckErr
(
options
.
Validate
())
cmdutil
.
CheckErr
(
options
.
Run
())
},
}
cmd
.
Flags
()
.
StringVarP
(
&
params
.
p
odName
,
"pod"
,
"p"
,
""
,
"Pod name"
)
cmd
.
Flags
()
.
StringVarP
(
&
options
.
P
odName
,
"pod"
,
"p"
,
""
,
"Pod name"
)
// TODO support UID
cmd
.
Flags
()
.
StringVarP
(
&
params
.
c
ontainerName
,
"container"
,
"c"
,
""
,
"Container name"
)
cmd
.
Flags
()
.
BoolVarP
(
&
params
.
s
tdin
,
"stdin"
,
"i"
,
false
,
"Pass stdin to the container"
)
cmd
.
Flags
()
.
BoolVarP
(
&
params
.
tty
,
"tty"
,
"t"
,
false
,
"Stdin is a TTY"
)
cmd
.
Flags
()
.
StringVarP
(
&
options
.
C
ontainerName
,
"container"
,
"c"
,
""
,
"Container name"
)
cmd
.
Flags
()
.
BoolVarP
(
&
options
.
S
tdin
,
"stdin"
,
"i"
,
false
,
"Pass stdin to the container"
)
cmd
.
Flags
()
.
BoolVarP
(
&
options
.
TTY
,
"tty"
,
"t"
,
false
,
"Stdin is a TTY"
)
return
cmd
}
type
remoteExecutor
interface
{
// RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing
type
RemoteExecutor
interface
{
Execute
(
req
*
client
.
Request
,
config
*
client
.
Config
,
command
[]
string
,
stdin
io
.
Reader
,
stdout
,
stderr
io
.
Writer
,
tty
bool
)
error
}
type
defaultRemoteExecutor
struct
{}
// DefaultRemoteExecutor is the standard implementation of remote command execution
type
DefaultRemoteExecutor
struct
{}
func
(
*
d
efaultRemoteExecutor
)
Execute
(
req
*
client
.
Request
,
config
*
client
.
Config
,
command
[]
string
,
stdin
io
.
Reader
,
stdout
,
stderr
io
.
Writer
,
tty
bool
)
error
{
func
(
*
D
efaultRemoteExecutor
)
Execute
(
req
*
client
.
Request
,
config
*
client
.
Config
,
command
[]
string
,
stdin
io
.
Reader
,
stdout
,
stderr
io
.
Writer
,
tty
bool
)
error
{
executor
:=
remotecommand
.
New
(
req
,
config
,
command
,
stdin
,
stdout
,
stderr
,
tty
)
return
executor
.
Execute
()
}
type
execParams
struct
{
podName
string
containerName
string
stdin
bool
tty
bool
// ExecOptions declare the arguments accepted by the Exec command
type
ExecOptions
struct
{
Namespace
string
PodName
string
ContainerName
string
Stdin
bool
TTY
bool
Command
[]
string
In
io
.
Reader
Out
io
.
Writer
Err
io
.
Writer
Executor
RemoteExecutor
Client
*
client
.
Client
Config
*
client
.
Config
}
func
extractPodAndContainer
(
cmd
*
cobra
.
Command
,
argsIn
[]
string
,
p
*
execParams
)
(
podName
string
,
containerName
string
,
args
[]
string
,
err
error
)
{
if
len
(
p
.
podName
)
==
0
&&
len
(
argsIn
)
==
0
{
return
""
,
""
,
nil
,
cmdutil
.
UsageError
(
cmd
,
"POD is required for exec"
)
// Complete verifies command line arguments and loads data from the command environment
func
(
p
*
ExecOptions
)
Complete
(
f
*
cmdutil
.
Factory
,
cmd
*
cobra
.
Command
,
argsIn
[]
string
)
error
{
if
len
(
p
.
PodName
)
==
0
&&
len
(
argsIn
)
==
0
{
return
cmdutil
.
UsageError
(
cmd
,
"POD is required for exec"
)
}
if
len
(
p
.
p
odName
)
!=
0
{
if
len
(
p
.
P
odName
)
!=
0
{
printDeprecationWarning
(
"exec POD"
,
"-p POD"
)
podName
=
p
.
podName
if
len
(
argsIn
)
<
1
{
return
""
,
""
,
nil
,
cmdutil
.
UsageError
(
cmd
,
"COMMAND is required for exec"
)
return
cmdutil
.
UsageError
(
cmd
,
"COMMAND is required for exec"
)
}
args
=
argsIn
p
.
Command
=
argsIn
}
else
{
podName
=
argsIn
[
0
]
args
=
argsIn
[
1
:
]
if
len
(
args
)
<
1
{
return
""
,
""
,
nil
,
cmdutil
.
UsageError
(
cmd
,
"COMMAND is required for exec"
)
p
.
P
odName
=
argsIn
[
0
]
p
.
Command
=
argsIn
[
1
:
]
if
len
(
p
.
Command
)
<
1
{
return
cmdutil
.
UsageError
(
cmd
,
"COMMAND is required for exec"
)
}
}
return
podName
,
p
.
containerName
,
args
,
nil
}
func
RunExec
(
f
*
cmdutil
.
Factory
,
cmd
*
cobra
.
Command
,
cmdIn
io
.
Reader
,
cmdOut
,
cmdErr
io
.
Writer
,
p
*
execParams
,
argsIn
[]
string
,
re
remoteExecutor
)
error
{
podName
,
containerName
,
args
,
err
:=
extractPodAndContainer
(
cmd
,
argsIn
,
p
)
namespace
,
_
,
err
:=
f
.
DefaultNamespace
()
if
err
!=
nil
{
return
err
}
p
.
Namespace
=
namespace
config
,
err
:=
f
.
ClientConfig
()
if
err
!=
nil
{
return
err
}
p
.
Config
=
config
client
,
err
:=
f
.
Client
()
if
err
!=
nil
{
return
err
}
p
.
Client
=
client
pod
,
err
:=
client
.
Pods
(
namespace
)
.
Get
(
podName
)
return
nil
}
// Validate checks that the provided exec options are specified.
func
(
p
*
ExecOptions
)
Validate
()
error
{
if
len
(
p
.
PodName
)
==
0
{
return
fmt
.
Errorf
(
"pod name must be specified"
)
}
if
len
(
p
.
Command
)
==
0
{
return
fmt
.
Errorf
(
"you must specify at least one command for the container"
)
}
if
p
.
Out
==
nil
||
p
.
Err
==
nil
{
return
fmt
.
Errorf
(
"both output and error output must be provided"
)
}
if
p
.
Executor
==
nil
||
p
.
Client
==
nil
||
p
.
Config
==
nil
{
return
fmt
.
Errorf
(
"client, client config, and executor must be provided"
)
}
return
nil
}
// Run executes a validated remote execution against a pod.
func
(
p
*
ExecOptions
)
Run
()
error
{
pod
,
err
:=
p
.
Client
.
Pods
(
p
.
Namespace
)
.
Get
(
p
.
PodName
)
if
err
!=
nil
{
return
err
}
if
pod
.
Status
.
Phase
!=
api
.
PodRunning
{
glog
.
Fatalf
(
"Unable to execute command because pod %s is not running. Current status=%v"
,
p
odName
,
pod
.
Status
.
Phase
)
return
fmt
.
Errorf
(
"pod %s is not running and cannot execute commands; current phase is %s"
,
p
.
P
odName
,
pod
.
Status
.
Phase
)
}
containerName
:=
p
.
ContainerName
if
len
(
containerName
)
==
0
{
glog
.
V
(
4
)
.
Infof
(
"defaulting container name to %s"
,
pod
.
Spec
.
Containers
[
0
]
.
Name
)
containerName
=
pod
.
Spec
.
Containers
[
0
]
.
Name
}
// TODO: refactor with terminal helpers from the edit utility once that is merged
var
stdin
io
.
Reader
tty
:=
p
.
tty
if
p
.
s
tdin
{
stdin
=
cmd
In
tty
:=
p
.
TTY
if
p
.
S
tdin
{
stdin
=
p
.
In
if
tty
{
if
file
,
ok
:=
cmdI
n
.
(
*
os
.
File
);
ok
{
if
file
,
ok
:=
stdi
n
.
(
*
os
.
File
);
ok
{
inFd
:=
file
.
Fd
()
if
term
.
IsTerminal
(
inFd
)
{
oldState
,
err
:=
term
.
SetRawTerminal
(
inFd
)
...
...
@@ -155,26 +204,22 @@ func RunExec(f *cmdutil.Factory, cmd *cobra.Command, cmdIn io.Reader, cmdOut, cm
os
.
Exit
(
0
)
}()
}
else
{
glog
.
Warning
(
"Stdin
is not a terminal"
)
fmt
.
Fprintln
(
p
.
Err
,
"STDIN
is not a terminal"
)
}
}
else
{
tty
=
false
glog
.
Warning
(
"Unable to use a TTY
"
)
fmt
.
Fprintln
(
p
.
Err
,
"Unable to use a TTY - input is not the right kind of file
"
)
}
}
}
config
,
err
:=
f
.
ClientConfig
()
if
err
!=
nil
{
return
err
}
req
:=
client
.
RESTClient
.
Post
()
.
// TODO: consider abstracting into a client invocation or client helper
req
:=
p
.
Client
.
RESTClient
.
Post
()
.
Resource
(
"pods"
)
.
Name
(
pod
.
Name
)
.
Namespace
(
n
amespace
)
.
Namespace
(
pod
.
N
amespace
)
.
SubResource
(
"exec"
)
.
Param
(
"container"
,
containerName
)
return
re
.
Execute
(
req
,
config
,
args
,
stdin
,
cmdOut
,
cmd
Err
,
tty
)
return
p
.
Executor
.
Execute
(
req
,
p
.
Config
,
p
.
Command
,
stdin
,
p
.
Out
,
p
.
Err
,
tty
)
}
pkg/kubectl/cmd/exec_test.go
View file @
cfe41dc0
...
...
@@ -44,7 +44,7 @@ func (f *fakeRemoteExecutor) Execute(req *client.Request, config *client.Config,
func
TestPodAndContainer
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
args
[]
string
p
*
execParam
s
p
*
ExecOption
s
name
string
expectError
bool
expectedPod
string
...
...
@@ -52,42 +52,42 @@ func TestPodAndContainer(t *testing.T) {
expectedArgs
[]
string
}{
{
p
:
&
execParam
s
{},
p
:
&
ExecOption
s
{},
expectError
:
true
,
name
:
"empty"
,
},
{
p
:
&
execParams
{
p
odName
:
"foo"
},
p
:
&
ExecOptions
{
P
odName
:
"foo"
},
expectError
:
true
,
name
:
"no cmd"
,
},
{
p
:
&
execParams
{
podName
:
"foo"
,
c
ontainerName
:
"bar"
},
p
:
&
ExecOptions
{
PodName
:
"foo"
,
C
ontainerName
:
"bar"
},
expectError
:
true
,
name
:
"no cmd, w/ container"
,
},
{
p
:
&
execParams
{
p
odName
:
"foo"
},
p
:
&
ExecOptions
{
P
odName
:
"foo"
},
args
:
[]
string
{
"cmd"
},
expectedPod
:
"foo"
,
expectedArgs
:
[]
string
{
"cmd"
},
name
:
"pod in flags"
,
},
{
p
:
&
execParam
s
{},
p
:
&
ExecOption
s
{},
args
:
[]
string
{
"foo"
},
expectError
:
true
,
name
:
"no cmd, w/o flags"
,
},
{
p
:
&
execParam
s
{},
p
:
&
ExecOption
s
{},
args
:
[]
string
{
"foo"
,
"cmd"
},
expectedPod
:
"foo"
,
expectedArgs
:
[]
string
{
"cmd"
},
name
:
"cmd, w/o flags"
,
},
{
p
:
&
execParams
{
c
ontainerName
:
"bar"
},
p
:
&
ExecOptions
{
C
ontainerName
:
"bar"
},
args
:
[]
string
{
"foo"
,
"cmd"
},
expectedPod
:
"foo"
,
expectedContainer
:
"bar"
,
...
...
@@ -96,22 +96,34 @@ func TestPodAndContainer(t *testing.T) {
},
}
for
_
,
test
:=
range
tests
{
cmd
:=
&
cobra
.
Command
{}
podName
,
containerName
,
args
,
err
:=
extractPodAndContainer
(
cmd
,
test
.
args
,
test
.
p
)
if
podName
!=
test
.
expectedPod
{
t
.
Errorf
(
"expected: %s, got: %s (%s)"
,
test
.
expectedPod
,
podName
,
test
.
name
)
}
if
containerName
!=
test
.
expectedContainer
{
t
.
Errorf
(
"expected: %s, got: %s (%s)"
,
test
.
expectedContainer
,
containerName
,
test
.
name
)
f
,
tf
,
codec
:=
NewAPIFactory
()
tf
.
Client
=
&
client
.
FakeRESTClient
{
Codec
:
codec
,
Client
:
client
.
HTTPClientFunc
(
func
(
req
*
http
.
Request
)
(
*
http
.
Response
,
error
)
{
return
nil
,
nil
}),
}
tf
.
Namespace
=
"test"
tf
.
ClientConfig
=
&
client
.
Config
{}
cmd
:=
&
cobra
.
Command
{}
options
:=
test
.
p
err
:=
options
.
Complete
(
f
,
cmd
,
test
.
args
)
if
test
.
expectError
&&
err
==
nil
{
t
.
Errorf
(
"unexpected non-error (%s)"
,
test
.
name
)
}
if
!
test
.
expectError
&&
err
!=
nil
{
t
.
Errorf
(
"unexpected error: %v (%s)"
,
err
,
test
.
name
)
}
if
!
reflect
.
DeepEqual
(
test
.
expectedArgs
,
args
)
{
t
.
Errorf
(
"expected: %v, got %v (%s)"
,
test
.
expectedArgs
,
args
,
test
.
name
)
if
err
!=
nil
{
continue
}
if
options
.
PodName
!=
test
.
expectedPod
{
t
.
Errorf
(
"expected: %s, got: %s (%s)"
,
test
.
expectedPod
,
options
.
PodName
,
test
.
name
)
}
if
options
.
ContainerName
!=
test
.
expectedContainer
{
t
.
Errorf
(
"expected: %s, got: %s (%s)"
,
test
.
expectedContainer
,
options
.
ContainerName
,
test
.
name
)
}
if
!
reflect
.
DeepEqual
(
test
.
expectedArgs
,
options
.
Command
)
{
t
.
Errorf
(
"expected: %v, got %v (%s)"
,
test
.
expectedArgs
,
options
.
Command
,
test
.
name
)
}
}
}
...
...
@@ -150,8 +162,8 @@ func TestExec(t *testing.T) {
return
&
http
.
Response
{
StatusCode
:
200
,
Body
:
body
},
nil
default
:
// Ensures no GET is performed when deleting by name
t
.
Errorf
(
"%s: unexpected request: %
#v
\n
%#v"
,
test
.
name
,
req
.
URL
,
req
)
return
nil
,
nil
t
.
Errorf
(
"%s: unexpected request: %
s %#v
\n
%#v"
,
test
.
name
,
req
.
Method
,
req
.
URL
,
req
)
return
nil
,
fmt
.
Errorf
(
"unexpected request"
)
}
}),
}
...
...
@@ -164,20 +176,30 @@ func TestExec(t *testing.T) {
if
test
.
execErr
{
ex
.
execErr
=
fmt
.
Errorf
(
"exec error"
)
}
params
:=
&
execParams
{
podName
:
"foo"
,
containerName
:
"bar"
,
params
:=
&
ExecOptions
{
PodName
:
"foo"
,
ContainerName
:
"bar"
,
In
:
bufIn
,
Out
:
bufOut
,
Err
:
bufErr
,
Executor
:
ex
,
}
cmd
:=
&
cobra
.
Command
{}
err
:=
RunExec
(
f
,
cmd
,
bufIn
,
bufOut
,
bufErr
,
params
,
[]
string
{
"test"
,
"command"
},
ex
)
if
err
:=
params
.
Complete
(
f
,
cmd
,
[]
string
{
"test"
,
"command"
});
err
!=
nil
{
t
.
Fatal
(
err
)
}
err
:=
params
.
Run
()
if
test
.
execErr
&&
err
!=
ex
.
execErr
{
t
.
Errorf
(
"%s: Unexpected exec error: %v"
,
test
.
name
,
err
)
}
if
!
test
.
execErr
&&
ex
.
req
.
URL
()
.
Path
!=
test
.
execPath
{
t
.
Errorf
(
"%s: Did not get expected path for exec request"
,
test
.
name
)
continue
}
if
!
test
.
execErr
&&
err
!=
nil
{
t
.
Errorf
(
"%s: Unexpected error: %v"
,
test
.
name
,
err
)
continue
}
if
!
test
.
execErr
&&
ex
.
req
.
URL
()
.
Path
!=
test
.
execPath
{
t
.
Errorf
(
"%s: Did not get expected path for exec request"
,
test
.
name
)
continue
}
}
}
...
...
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