diff options
-rw-r--r-- | libpod/container_exec.go | 5 | ||||
-rw-r--r-- | libpod/options.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/compat/exec.go | 3 | ||||
-rw-r--r-- | pkg/bindings/containers/exec.go | 71 | ||||
-rw-r--r-- | pkg/bindings/test/containers_test.go | 2 | ||||
-rw-r--r-- | pkg/bindings/test/exec_test.go | 77 | ||||
-rw-r--r-- | pkg/bindings/test/pods_test.go | 4 |
7 files changed, 160 insertions, 4 deletions
diff --git a/libpod/container_exec.go b/libpod/container_exec.go index 795eb7453..912c2c226 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -231,6 +231,11 @@ func (c *Container) ExecStartAndAttach(sessionID string, streams *AttachStreams) } } + // Verify that we are in a good state to continue + if !c.ensureState(define.ContainerStateRunning) { + return errors.Wrapf(define.ErrCtrStateInvalid, "can only start exec sessions when their container is running") + } + session, ok := c.state.ExecSessions[sessionID] if !ok { return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID) diff --git a/libpod/options.go b/libpod/options.go index 9b61d7947..74f9c485e 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -21,6 +21,8 @@ import ( var ( // NameRegex is a regular expression to validate container/pod names. + // This must NOT be changed from outside of Libpod. It should be a + // constant, but Go won't let us do that. NameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$") // RegexError is thrown in presence of an invalid container/pod name. RegexError = errors.Wrapf(define.ErrInvalidArg, "names must match [a-zA-Z0-9][a-zA-Z0-9_.-]*") diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index 1fa50d23b..ec1a8ac96 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -12,6 +12,7 @@ import ( "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/gorilla/mux" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // ExecCreateHandler creates an exec session for a given container. @@ -88,6 +89,8 @@ func ExecInspectHandler(w http.ResponseWriter, r *http.Request) { return } + logrus.Debugf("Inspecting exec session %s of container %s", sessionID, sessionCtr.ID()) + session, err := sessionCtr.ExecSession(sessionID) if err != nil { utils.InternalServerError(w, errors.Wrapf(err, "error retrieving exec session %s from container %s", sessionID, sessionCtr.ID())) diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go new file mode 100644 index 000000000..48f9ed697 --- /dev/null +++ b/pkg/bindings/containers/exec.go @@ -0,0 +1,71 @@ +package containers + +import ( + "context" + "net/http" + "strings" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// ExecCreate creates a new exec session in an existing container. +// The exec session will not be started; that is done with ExecStart. +// Returns ID of new exec session, or an error if one occurred. +func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreateConfig) (string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + + if config == nil { + return "", errors.Errorf("must provide a configuration for exec session") + } + + requestJSON, err := json.Marshal(config) + if err != nil { + return "", errors.Wrapf(err, "error marshalling exec config to JSON") + } + jsonReader := strings.NewReader(string(requestJSON)) + + resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nameOrID) + if err != nil { + return "", err + } + + respStruct := new(handlers.ExecCreateResponse) + if err := resp.Process(respStruct); err != nil { + return "", err + } + + return respStruct.ID, nil +} + +// ExecInspect inspects an existing exec session, returning detailed information +// about it. +func ExecInspect(ctx context.Context, sessionID string) (*define.InspectExecSession, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + logrus.Debugf("Inspecting session ID %q", sessionID) + + resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, sessionID) + if err != nil { + return nil, err + } + + respStruct := new(define.InspectExecSession) + if err := resp.Process(respStruct); err != nil { + return nil, err + } + + return respStruct, nil +} diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index f5465c803..55c739865 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -1,12 +1,12 @@ package test_bindings import ( - "github.com/containers/libpod/libpod/define" "net/http" "strconv" "strings" "time" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/specgen" diff --git a/pkg/bindings/test/exec_test.go b/pkg/bindings/test/exec_test.go new file mode 100644 index 000000000..1ef2197b6 --- /dev/null +++ b/pkg/bindings/test/exec_test.go @@ -0,0 +1,77 @@ +package test_bindings + +import ( + "time" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/bindings/containers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman containers exec", func() { + var ( + bt *bindingTest + s *gexec.Session + ) + + BeforeEach(func() { + bt = newBindingTest() + bt.RestoreImagesFromCache() + s = bt.startAPIService() + time.Sleep(1 * time.Second) + err := bt.NewConnection() + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + }) + + It("Podman exec create makes an exec session", func() { + name := "testCtr" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + execConfig := new(handlers.ExecCreateConfig) + execConfig.Cmd = []string{"echo", "hello world"} + + sessionID, err := containers.ExecCreate(bt.conn, name, execConfig) + Expect(err).To(BeNil()) + Expect(sessionID).To(Not(Equal(""))) + + inspectOut, err := containers.ExecInspect(bt.conn, sessionID) + Expect(err).To(BeNil()) + Expect(inspectOut.ContainerID).To(Equal(cid)) + Expect(inspectOut.ProcessConfig.Entrypoint).To(Equal("echo")) + Expect(len(inspectOut.ProcessConfig.Arguments)).To(Equal(1)) + Expect(inspectOut.ProcessConfig.Arguments[0]).To(Equal("hello world")) + }) + + It("Podman exec create with bad command fails", func() { + name := "testCtr" + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + execConfig := new(handlers.ExecCreateConfig) + + _, err = containers.ExecCreate(bt.conn, name, execConfig) + Expect(err).To(Not(BeNil())) + }) + + It("Podman exec create with invalid container fails", func() { + execConfig := new(handlers.ExecCreateConfig) + execConfig.Cmd = []string{"echo", "hello world"} + + _, err := containers.ExecCreate(bt.conn, "doesnotexist", execConfig) + Expect(err).To(Not(BeNil())) + }) + + It("Podman exec inspect on invalid session fails", func() { + _, err := containers.ExecInspect(bt.conn, "0000000000000000000000000000000000000000000000000000000000000000") + Expect(err).To(Not(BeNil())) + }) +}) diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index e94048a9c..a1d6ee184 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -79,9 +79,7 @@ var _ = Describe("Podman pods", func() { // The test validates the list pod endpoint with passing filters as the params. It("List pods with filters", func() { - var ( - newpod2 string = "newpod2" - ) + newpod2 := "newpod2" bt.Podcreate(&newpod2) _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod) Expect(err).To(BeNil()) |