-
Notifications
You must be signed in to change notification settings - Fork 203
cp exec util from kubernetes/kubernetes #5
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
Merged
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
83db8cb
Add a util/exec interface for testing execs.
thockin 89df886
Fix the exec test, on os x false and true have different paths, but t…
brendandburns a239f43
Add support for git volumes.
brendandburns 3bd48a7
Clean up error logs.
thockin 89cef67
Make copyright ownership statement generic
eparis 44277a3
pkg/util/exec: allow mocking of LookPath
mcluseau ad86408
Print unhandeled command in FakeExec
c932095
Handle executable not found in exec
db0e323
Merge pull request #16950 from swagiaal/handle-exec-not-found
39529cf
Improve error reporting a little in ingress e2e.
brendandburns 9dd79d6
improve iptables-restore implementation #27559
919f3e4
Remove "All rights reserved" from all the headers.
david-mcmahon fed5729
Use Go canonical import paths
dims d79ecb5
Merge pull request #27562 from 7ing/ipt
a66ca68
pkg/util/exec: don't specify paths for echo
tmrts 9589c6e
Rectify kubectl error output
sttts 6122caf
Add return code support to kubectl-exec and -run
sttts 966490d
autogenerated
mikedanese 34f72a5
autoupdate BUILD files
mikedanese d98787a
autogenerated
mikedanese 7799517
Enable auto-generating sources rules
ixdy 3be53a2
Add support for attacher/detacher interface in Flex volume
chakri-nelluri 0171d94
Extend Iptables interface with SaveInto
wojtek-t de2161e
Remove Save() from iptables interface
wojtek-t fbf977e
preserve git history for pkg/util/exec and drop BUILD
c704b66
move kubernetes/pkg/util/exec to utils/exec
c7bfd26
update pkg info and structure.
1641070
address comments
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* | ||
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 exec provides an injectable interface and implementations for running commands. | ||
package exec // import "k8s.io/utils/exec" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
/* | ||
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 exec | ||
|
||
import ( | ||
"io" | ||
osexec "os/exec" | ||
"syscall" | ||
"time" | ||
) | ||
|
||
// ErrExecutableNotFound is returned if the executable is not found. | ||
var ErrExecutableNotFound = osexec.ErrNotFound | ||
|
||
// Interface is an interface that presents a subset of the os/exec API. Use this | ||
// when you want to inject fakeable/mockable exec behavior. | ||
type Interface interface { | ||
// Command returns a Cmd instance which can be used to run a single command. | ||
// This follows the pattern of package os/exec. | ||
Command(cmd string, args ...string) Cmd | ||
|
||
// LookPath wraps os/exec.LookPath | ||
LookPath(file string) (string, error) | ||
} | ||
|
||
// Cmd is an interface that presents an API that is very similar to Cmd from os/exec. | ||
// As more functionality is needed, this can grow. Since Cmd is a struct, we will have | ||
// to replace fields with get/set method pairs. | ||
type Cmd interface { | ||
// Run runs the command to the completion. | ||
Run() error | ||
// CombinedOutput runs the command and returns its combined standard output | ||
// and standard error. This follows the pattern of package os/exec. | ||
CombinedOutput() ([]byte, error) | ||
// Output runs the command and returns standard output, but not standard err | ||
Output() ([]byte, error) | ||
SetDir(dir string) | ||
SetStdin(in io.Reader) | ||
SetStdout(out io.Writer) | ||
SetStderr(out io.Writer) | ||
// Stops the command by sending SIGTERM. It is not guaranteed the | ||
// process will stop before this function returns. If the process is not | ||
// responding, an internal timer function will send a SIGKILL to force | ||
// terminate after 10 seconds. | ||
Stop() | ||
} | ||
|
||
// ExitError is an interface that presents an API similar to os.ProcessState, which is | ||
// what ExitError from os/exec is. This is designed to make testing a bit easier and | ||
// probably loses some of the cross-platform properties of the underlying library. | ||
type ExitError interface { | ||
String() string | ||
Error() string | ||
Exited() bool | ||
ExitStatus() int | ||
} | ||
|
||
// Implements Interface in terms of really exec()ing. | ||
type executor struct{} | ||
|
||
// New returns a new Interface which will os/exec to run commands. | ||
func New() Interface { | ||
return &executor{} | ||
} | ||
|
||
// Command is part of the Interface interface. | ||
func (executor *executor) Command(cmd string, args ...string) Cmd { | ||
return (*cmdWrapper)(osexec.Command(cmd, args...)) | ||
} | ||
|
||
// LookPath is part of the Interface interface | ||
func (executor *executor) LookPath(file string) (string, error) { | ||
return osexec.LookPath(file) | ||
} | ||
|
||
// Wraps exec.Cmd so we can capture errors. | ||
type cmdWrapper osexec.Cmd | ||
|
||
var _ Cmd = &cmdWrapper{} | ||
|
||
func (cmd *cmdWrapper) SetDir(dir string) { | ||
cmd.Dir = dir | ||
} | ||
|
||
func (cmd *cmdWrapper) SetStdin(in io.Reader) { | ||
cmd.Stdin = in | ||
} | ||
|
||
func (cmd *cmdWrapper) SetStdout(out io.Writer) { | ||
cmd.Stdout = out | ||
} | ||
|
||
func (cmd *cmdWrapper) SetStderr(out io.Writer) { | ||
cmd.Stderr = out | ||
} | ||
|
||
// Run is part of the Cmd interface. | ||
func (cmd *cmdWrapper) Run() error { | ||
return (*osexec.Cmd)(cmd).Run() | ||
} | ||
|
||
// CombinedOutput is part of the Cmd interface. | ||
func (cmd *cmdWrapper) CombinedOutput() ([]byte, error) { | ||
out, err := (*osexec.Cmd)(cmd).CombinedOutput() | ||
if err != nil { | ||
return out, handleError(err) | ||
} | ||
return out, nil | ||
} | ||
|
||
func (cmd *cmdWrapper) Output() ([]byte, error) { | ||
out, err := (*osexec.Cmd)(cmd).Output() | ||
if err != nil { | ||
return out, handleError(err) | ||
} | ||
return out, nil | ||
} | ||
|
||
// Stop is part of the Cmd interface. | ||
func (cmd *cmdWrapper) Stop() { | ||
c := (*osexec.Cmd)(cmd) | ||
if c.ProcessState.Exited() { | ||
return | ||
} | ||
c.Process.Signal(syscall.SIGTERM) | ||
time.AfterFunc(10*time.Second, func() { | ||
if c.ProcessState.Exited() { | ||
return | ||
} | ||
c.Process.Signal(syscall.SIGKILL) | ||
}) | ||
} | ||
|
||
func handleError(err error) error { | ||
if ee, ok := err.(*osexec.ExitError); ok { | ||
// Force a compile fail if exitErrorWrapper can't convert to ExitError. | ||
var x ExitError = &ExitErrorWrapper{ee} | ||
return x | ||
} | ||
if ee, ok := err.(*osexec.Error); ok { | ||
if ee.Err == osexec.ErrNotFound { | ||
return ErrExecutableNotFound | ||
} | ||
} | ||
return err | ||
} | ||
|
||
// ExitErrorWrapper is an implementation of ExitError in terms of os/exec ExitError. | ||
// Note: standard exec.ExitError is type *os.ProcessState, which already implements Exited(). | ||
type ExitErrorWrapper struct { | ||
*osexec.ExitError | ||
} | ||
|
||
var _ ExitError = ExitErrorWrapper{} | ||
|
||
// ExitStatus is part of the ExitError interface. | ||
func (eew ExitErrorWrapper) ExitStatus() int { | ||
ws, ok := eew.Sys().(syscall.WaitStatus) | ||
if !ok { | ||
panic("can't call ExitStatus() on a non-WaitStatus exitErrorWrapper") | ||
} | ||
return ws.ExitStatus() | ||
} | ||
|
||
// CodeExitError is an implementation of ExitError consisting of an error object | ||
// and an exit code (the upper bits of os.exec.ExitStatus). | ||
type CodeExitError struct { | ||
Err error | ||
Code int | ||
} | ||
|
||
var _ ExitError = CodeExitError{} | ||
|
||
func (e CodeExitError) Error() string { | ||
return e.Err.Error() | ||
} | ||
|
||
func (e CodeExitError) String() string { | ||
return e.Err.Error() | ||
} | ||
|
||
func (e CodeExitError) Exited() bool { | ||
return true | ||
} | ||
|
||
func (e CodeExitError) ExitStatus() int { | ||
return e.Code | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 exec | ||
|
||
import ( | ||
osexec "os/exec" | ||
"testing" | ||
) | ||
|
||
func TestExecutorNoArgs(t *testing.T) { | ||
ex := New() | ||
|
||
cmd := ex.Command("true") | ||
out, err := cmd.CombinedOutput() | ||
if err != nil { | ||
t.Errorf("expected success, got %v", err) | ||
} | ||
if len(out) != 0 { | ||
t.Errorf("expected no output, got %q", string(out)) | ||
} | ||
|
||
cmd = ex.Command("false") | ||
out, err = cmd.CombinedOutput() | ||
if err == nil { | ||
t.Errorf("expected failure, got nil error") | ||
} | ||
if len(out) != 0 { | ||
t.Errorf("expected no output, got %q", string(out)) | ||
} | ||
ee, ok := err.(ExitError) | ||
if !ok { | ||
t.Errorf("expected an ExitError, got %+v", err) | ||
} | ||
if ee.Exited() { | ||
if code := ee.ExitStatus(); code != 1 { | ||
t.Errorf("expected exit status 1, got %d", code) | ||
} | ||
} | ||
|
||
cmd = ex.Command("/does/not/exist") | ||
out, err = cmd.CombinedOutput() | ||
if err == nil { | ||
t.Errorf("expected failure, got nil error") | ||
} | ||
if ee, ok := err.(ExitError); ok { | ||
t.Errorf("expected non-ExitError, got %+v", ee) | ||
} | ||
} | ||
|
||
func TestExecutorWithArgs(t *testing.T) { | ||
ex := New() | ||
|
||
cmd := ex.Command("echo", "stdout") | ||
out, err := cmd.CombinedOutput() | ||
if err != nil { | ||
t.Errorf("expected success, got %+v", err) | ||
} | ||
if string(out) != "stdout\n" { | ||
t.Errorf("unexpected output: %q", string(out)) | ||
} | ||
|
||
cmd = ex.Command("/bin/sh", "-c", "echo stderr > /dev/stderr") | ||
out, err = cmd.CombinedOutput() | ||
if err != nil { | ||
t.Errorf("expected success, got %+v", err) | ||
} | ||
if string(out) != "stderr\n" { | ||
t.Errorf("unexpected output: %q", string(out)) | ||
} | ||
} | ||
|
||
func TestLookPath(t *testing.T) { | ||
ex := New() | ||
|
||
shExpected, _ := osexec.LookPath("sh") | ||
sh, _ := ex.LookPath("sh") | ||
if sh != shExpected { | ||
t.Errorf("unexpected result for LookPath: got %s, expected %s", sh, shExpected) | ||
} | ||
} | ||
|
||
func TestExecutableNotFound(t *testing.T) { | ||
exec := New() | ||
cmd := exec.Command("fake_executable_name") | ||
_, err := cmd.CombinedOutput() | ||
if err != ErrExecutableNotFound { | ||
t.Errorf("Expected error ErrExecutableNotFound but got %v", err) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to add
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. It's good to have a check to make sure it has implemented the interface.
Also add the check in
testing/fake_exec.go
. Does it make sense?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I think it does. I didn't mention it because
func InitFakeCmd(fake *FakeCmd, cmd string, args ...string) exec.Cmd
would fail iffake
wasn't aexec.Cmd
, but that's probably more subtle that necessary. Thanks for the changes!