Skip to content

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 28 commits into from
Jul 19, 2017
Merged
Show file tree
Hide file tree
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 Sep 22, 2014
89df886
Fix the exec test, on os x false and true have different paths, but t…
brendandburns Oct 7, 2014
a239f43
Add support for git volumes.
brendandburns Oct 21, 2014
3bd48a7
Clean up error logs.
thockin Nov 20, 2014
89cef67
Make copyright ownership statement generic
eparis May 1, 2015
44277a3
pkg/util/exec: allow mocking of LookPath
mcluseau Sep 11, 2015
ad86408
Print unhandeled command in FakeExec
Nov 6, 2015
c932095
Handle executable not found in exec
Oct 29, 2015
db0e323
Merge pull request #16950 from swagiaal/handle-exec-not-found
Nov 17, 2015
39529cf
Improve error reporting a little in ingress e2e.
brendandburns Jan 6, 2016
9dd79d6
improve iptables-restore implementation #27559
Jun 16, 2016
919f3e4
Remove "All rights reserved" from all the headers.
david-mcmahon Jun 3, 2016
fed5729
Use Go canonical import paths
dims Jul 16, 2016
d79ecb5
Merge pull request #27562 from 7ing/ipt
Aug 2, 2016
a66ca68
pkg/util/exec: don't specify paths for echo
tmrts Aug 8, 2016
9589c6e
Rectify kubectl error output
sttts Aug 8, 2016
6122caf
Add return code support to kubectl-exec and -run
sttts Aug 8, 2016
966490d
autogenerated
mikedanese Oct 19, 2016
34f72a5
autoupdate BUILD files
mikedanese Dec 7, 2016
d98787a
autogenerated
mikedanese Dec 29, 2016
7799517
Enable auto-generating sources rules
ixdy Jan 3, 2017
3be53a2
Add support for attacher/detacher interface in Flex volume
chakri-nelluri Feb 21, 2017
0171d94
Extend Iptables interface with SaveInto
wojtek-t May 18, 2017
de2161e
Remove Save() from iptables interface
wojtek-t May 22, 2017
fbf977e
preserve git history for pkg/util/exec and drop BUILD
Jul 8, 2017
c704b66
move kubernetes/pkg/util/exec to utils/exec
Jul 8, 2017
c7bfd26
update pkg info and structure.
Jul 8, 2017
1641070
address comments
Jul 19, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions exec/doc.go
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"
202 changes: 202 additions & 0 deletions exec/exec.go
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

Copy link
Member

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

var _ Cmd = &cmdWrapper{}

Copy link
Member Author

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?

Copy link
Member

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 if fake wasn't a exec.Cmd, but that's probably more subtle that necessary. Thanks for the changes!

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
}
103 changes: 103 additions & 0 deletions exec/exec_test.go
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)
}
}
Loading