summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml11
-rw-r--r--cmd/podman/containers/wait.go3
-rwxr-xr-xcontrib/cirrus/logcollector.sh1
-rw-r--r--libpod/container_api.go109
-rw-r--r--libpod/container_internal.go6
-rw-r--r--libpod/define/errors.go4
-rw-r--r--libpod/kube.go21
-rw-r--r--libpod/networking_linux.go2
-rw-r--r--nix/nixpkgs.json8
-rw-r--r--pkg/api/handlers/compat/containers.go29
-rw-r--r--pkg/api/handlers/libpod/containers.go13
-rw-r--r--pkg/api/handlers/utils/containers.go231
-rw-r--r--pkg/api/handlers/utils/handler.go4
-rw-r--r--pkg/bindings/containers/types.go2
-rw-r--r--pkg/bindings/containers/types_wait_options.go10
-rw-r--r--pkg/bindings/test/attach_test.go2
-rw-r--r--pkg/bindings/test/common_test.go2
-rw-r--r--pkg/bindings/test/containers_test.go4
-rw-r--r--pkg/domain/entities/containers.go2
-rw-r--r--pkg/domain/infra/abi/containers.go6
-rw-r--r--test/apiv2/01-basic.at4
-rw-r--r--test/apiv2/26-containersWait.at47
-rw-r--r--test/e2e/common_test.go2
-rw-r--r--test/e2e/generate_kube_test.go60
-rw-r--r--test/e2e/network_connect_disconnect_test.go20
-rw-r--r--test/e2e/wait_test.go4
26 files changed, 473 insertions, 134 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index eda03bf23..a23595712 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -433,7 +433,6 @@ apiv2_test_task:
path: ./*.html
type: text/html
package_versions_script: '$SCRIPT_BASE/logcollector.sh packages'
- ginkgo_node_logs_script: '$SCRIPT_BASE/logcollector.sh ginkgo'
df_script: '$SCRIPT_BASE/logcollector.sh df'
audit_log_script: '$SCRIPT_BASE/logcollector.sh audit'
journal_script: '$SCRIPT_BASE/logcollector.sh journal'
@@ -478,7 +477,11 @@ local_integration_test_task: &local_integration_test_task
gopath_cache: *ro_gopath_cache
setup_script: *setup
main_script: *main
- always: *logs_artifacts
+ always: &int_logs_artifacts
+ <<: *logs_artifacts
+ ginkgo_node_logs_artifacts:
+ path: ./test/e2e/ginkgo-node-*.log
+ type: text/plain
# Nearly identical to `local_integration_test` except all operations
@@ -521,7 +524,7 @@ container_integration_test_task:
gopath_cache: *ro_gopath_cache
setup_script: *setup
main_script: *main
- always: *logs_artifacts
+ always: *int_logs_artifacts
# Execute most integration tests as a regular (non-root) user.
@@ -542,7 +545,7 @@ rootless_integration_test_task:
gopath_cache: *ro_gopath_cache
setup_script: *setup
main_script: *main
- always: *logs_artifacts
+ always: *int_logs_artifacts
# Always run subsequent to integration tests. While parallelism is lost
diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go
index 14d660678..7a531b98a 100644
--- a/cmd/podman/containers/wait.go
+++ b/cmd/podman/containers/wait.go
@@ -95,10 +95,11 @@ func wait(cmd *cobra.Command, args []string) error {
return errors.New("--latest and containers cannot be used together")
}
- waitOptions.Condition, err = define.StringToContainerStatus(waitCondition)
+ cond, err := define.StringToContainerStatus(waitCondition)
if err != nil {
return err
}
+ waitOptions.Condition = []define.ContainerStatus{cond}
responses, err := registry.ContainerEngine().ContainerWait(context.Background(), args, waitOptions)
if err != nil {
diff --git a/contrib/cirrus/logcollector.sh b/contrib/cirrus/logcollector.sh
index 323015cef..38a15ded1 100755
--- a/contrib/cirrus/logcollector.sh
+++ b/contrib/cirrus/logcollector.sh
@@ -31,7 +31,6 @@ case $1 in
esac
;;
df) showrun df -lhTx tmpfs ;;
- ginkgo) showrun cat $CIRRUS_WORKING_DIR/test/e2e/ginkgo-node-*.log ;;
journal) showrun journalctl -b ;;
podman) showrun ./bin/podman system info ;;
server)
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 951227a4f..2473acec0 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -5,6 +5,7 @@ import (
"io/ioutil"
"net/http"
"os"
+ "sync"
"time"
"github.com/containers/podman/v2/libpod/define"
@@ -478,13 +479,13 @@ func (c *Container) RemoveArtifact(name string) error {
}
// Wait blocks until the container exits and returns its exit code.
-func (c *Container) Wait() (int32, error) {
- return c.WaitWithInterval(DefaultWaitInterval)
+func (c *Container) Wait(ctx context.Context) (int32, error) {
+ return c.WaitWithInterval(ctx, DefaultWaitInterval)
}
// WaitWithInterval blocks until the container to exit and returns its exit
// code. The argument is the interval at which checks the container's status.
-func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) {
+func (c *Container) WaitWithInterval(ctx context.Context, waitTimeout time.Duration) (int32, error) {
if !c.valid {
return -1, define.ErrCtrRemoved
}
@@ -495,41 +496,111 @@ func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) {
}
chWait := make(chan error, 1)
- defer close(chWait)
+ go func() {
+ <-ctx.Done()
+ chWait <- define.ErrCanceled
+ }()
for {
- // ignore errors here, it is only used to avoid waiting
+ // ignore errors here (with exception of cancellation), it is only used to avoid waiting
// too long.
- _, _ = WaitForFile(exitFile, chWait, waitTimeout)
+ _, e := WaitForFile(exitFile, chWait, waitTimeout)
+ if e == define.ErrCanceled {
+ return -1, define.ErrCanceled
+ }
- stopped, err := c.isStopped()
+ stopped, code, err := c.isStopped()
if err != nil {
return -1, err
}
if stopped {
- return c.state.ExitCode, nil
+ return code, nil
}
}
}
-func (c *Container) WaitForConditionWithInterval(waitTimeout time.Duration, condition define.ContainerStatus) (int32, error) {
+type waitResult struct {
+ code int32
+ err error
+}
+
+func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeout time.Duration, conditions ...define.ContainerStatus) (int32, error) {
if !c.valid {
return -1, define.ErrCtrRemoved
}
- if condition == define.ContainerStateStopped || condition == define.ContainerStateExited {
- return c.WaitWithInterval(waitTimeout)
+
+ if len(conditions) == 0 {
+ panic("at least one condition should be passed")
}
- for {
- state, err := c.State()
- if err != nil {
- return -1, err
+
+ ctx, cancelFn := context.WithCancel(ctx)
+ defer cancelFn()
+
+ resultChan := make(chan waitResult)
+ waitForExit := false
+ wantedStates := make(map[define.ContainerStatus]bool, len(conditions))
+
+ for _, condition := range conditions {
+ if condition == define.ContainerStateStopped || condition == define.ContainerStateExited {
+ waitForExit = true
+ continue
}
- if state == condition {
- break
+ wantedStates[condition] = true
+ }
+
+ trySend := func(code int32, err error) {
+ select {
+ case resultChan <- waitResult{code, err}:
+ case <-ctx.Done():
}
- time.Sleep(waitTimeout)
}
- return -1, nil
+
+ var wg sync.WaitGroup
+
+ if waitForExit {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ code, err := c.WaitWithInterval(ctx, waitTimeout)
+ trySend(code, err)
+ }()
+ }
+
+ if len(wantedStates) > 0 {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ for {
+ state, err := c.State()
+ if err != nil {
+ trySend(-1, err)
+ return
+ }
+ if _, found := wantedStates[state]; found {
+ trySend(-1, nil)
+ return
+ }
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(waitTimeout):
+ continue
+ }
+ }
+ }()
+ }
+
+ var result waitResult
+ select {
+ case result = <-resultChan:
+ cancelFn()
+ case <-ctx.Done():
+ result = waitResult{-1, define.ErrCanceled}
+ }
+ wg.Wait()
+ return result.code, result.err
}
// Cleanup unmounts all mount points in container and cleans up container storage
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index b9ea50783..5a61f7fe6 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -754,17 +754,17 @@ func (c *Container) getArtifactPath(name string) string {
}
// Used with Wait() to determine if a container has exited
-func (c *Container) isStopped() (bool, error) {
+func (c *Container) isStopped() (bool, int32, error) {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
}
err := c.syncContainer()
if err != nil {
- return true, err
+ return true, -1, err
}
- return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping), nil
+ return !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused, define.ContainerStateStopping), c.state.ExitCode, nil
}
// save container state to the database
diff --git a/libpod/define/errors.go b/libpod/define/errors.go
index d37bc397e..2e85454b2 100644
--- a/libpod/define/errors.go
+++ b/libpod/define/errors.go
@@ -198,4 +198,8 @@ var (
// ErrSecurityAttribute indicates that an error processing security attributes
// for the container
ErrSecurityAttribute = fmt.Errorf("%w: unable to process security attribute", ErrOCIRuntime)
+
+ // ErrCanceled indicates that an operation has been cancelled by a user.
+ // Useful for potentially long running tasks.
+ ErrCanceled = errors.New("cancelled by user")
)
diff --git a/libpod/kube.go b/libpod/kube.go
index bf314b9a3..f9ead027d 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -353,22 +353,21 @@ func containerToV1Container(c *Container) (v1.Container, []v1.Volume, *v1.PodDNS
return kubeContainer, kubeVolumes, nil, err
}
- containerCommands := c.Command()
- kubeContainer.Name = removeUnderscores(c.Name())
+ // Handle command and arguments.
+ if ep := c.Entrypoint(); len(ep) > 0 {
+ // If we have an entrypoint, set the container's command as
+ // arguments.
+ kubeContainer.Command = ep
+ kubeContainer.Args = c.Command()
+ } else {
+ kubeContainer.Command = c.Command()
+ }
+ kubeContainer.Name = removeUnderscores(c.Name())
_, image := c.Image()
kubeContainer.Image = image
kubeContainer.Stdin = c.Stdin()
- // prepend the entrypoint of the container to command
- if ep := c.Entrypoint(); len(c.Entrypoint()) > 0 {
- ep = append(ep, containerCommands...)
- containerCommands = ep
- }
- kubeContainer.Command = containerCommands
- // TODO need to figure out how we handle command vs entry point. Kube appears to prefer entrypoint.
- // right now we just take the container's command
- //container.Args = args
kubeContainer.WorkingDir = c.WorkingDir()
kubeContainer.Ports = ports
// This should not be applicable
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 01e4102d1..55d338e7d 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -1180,7 +1180,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro
// update network status if container is not running
networkStatus := c.state.NetworkStatus
// clip out the index of the network
- tmpNetworkStatus := make([]*cnitypes.Result, len(networkStatus)-1)
+ tmpNetworkStatus := make([]*cnitypes.Result, 0, len(networkStatus)-1)
for k, v := range networkStatus {
if index != k {
tmpNetworkStatus = append(tmpNetworkStatus, v)
diff --git a/nix/nixpkgs.json b/nix/nixpkgs.json
index 0cfb251f2..459fb28f8 100644
--- a/nix/nixpkgs.json
+++ b/nix/nixpkgs.json
@@ -1,9 +1,9 @@
{
"url": "https://github.com/nixos/nixpkgs",
- "rev": "ce7b327a52d1b82f82ae061754545b1c54b06c66",
- "date": "2021-01-25T11:28:05+01:00",
- "path": "/nix/store/dpsa6a1sy8hwhwjkklc52brs9z1k5fx9-nixpkgs",
- "sha256": "1rc4if8nmy9lrig0ddihdwpzg2s8y36vf20hfywb8hph5hpsg4vj",
+ "rev": "30c2fb65feaf1068b1c413a0b75470afd351c291",
+ "date": "2021-01-28T21:27:34-05:00",
+ "path": "/nix/store/zk71rlw37vg9hqc5j0vqi9x8qzb2ir0m-nixpkgs",
+ "sha256": "0b1y1lgzbagpgh9cvi9szkm162laifz0q2ss4pibns3j3gqpf5gl",
"fetchSubmodules": false,
"deepClone": false,
"leaveDotGit": false
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 86508f938..9c0893a80 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -23,10 +23,8 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
"github.com/docker/go-units"
- "github.com/gorilla/mux"
"github.com/gorilla/schema"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
func RemoveContainer(w http.ResponseWriter, r *http.Request) {
@@ -233,8 +231,11 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
return
}
if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL {
- if _, err := utils.WaitContainer(w, r); err != nil {
-
+ opts := entities.WaitOptions{
+ Condition: []define.ContainerStatus{define.ContainerStateExited, define.ContainerStateStopped},
+ Interval: time.Millisecond * 250,
+ }
+ if _, err := containerEngine.ContainerWait(r.Context(), []string{name}, opts); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
}
@@ -245,26 +246,8 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
}
func WaitContainer(w http.ResponseWriter, r *http.Request) {
- var msg string
// /{version}/containers/(name)/wait
- exitCode, err := utils.WaitContainer(w, r)
- if err != nil {
- if errors.Cause(err) == define.ErrNoSuchCtr {
- logrus.Warnf("container not found %q: %v", utils.GetName(r), err)
- return
- }
- logrus.Warnf("failed to wait on container %q: %v", mux.Vars(r)["name"], err)
- return
- }
-
- utils.WriteResponse(w, http.StatusOK, handlers.ContainerWaitOKBody{
- StatusCode: int(exitCode),
- Error: struct {
- Message string
- }{
- Message: msg,
- },
- })
+ utils.WaitContainerDocker(w, r)
}
func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error) {
diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go
index f6e348cef..619cbfd8b 100644
--- a/pkg/api/handlers/libpod/containers.go
+++ b/pkg/api/handlers/libpod/containers.go
@@ -4,7 +4,6 @@ import (
"io/ioutil"
"net/http"
"os"
- "strconv"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/define"
@@ -146,17 +145,7 @@ func GetContainer(w http.ResponseWriter, r *http.Request) {
}
func WaitContainer(w http.ResponseWriter, r *http.Request) {
- exitCode, err := utils.WaitContainer(w, r)
- if err != nil {
- name := utils.GetName(r)
- if errors.Cause(err) == define.ErrNoSuchCtr {
- utils.ContainerNotFound(w, name, err)
- return
- }
- logrus.Warnf("failed to wait on container %q: %v", name, err)
- return
- }
- utils.WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode)))
+ utils.WaitContainerLibpod(w, r)
}
func UnmountContainer(w http.ResponseWriter, r *http.Request) {
diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go
index fac237f87..518309a03 100644
--- a/pkg/api/handlers/utils/containers.go
+++ b/pkg/api/handlers/utils/containers.go
@@ -1,67 +1,230 @@
package utils
import (
+ "context"
+ "fmt"
"net/http"
+ "strconv"
"time"
- "github.com/containers/podman/v2/libpod"
- "github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi"
+
+ "github.com/containers/podman/v2/pkg/api/handlers"
+ "github.com/sirupsen/logrus"
+
+ "github.com/containers/podman/v2/libpod/define"
+
+ "github.com/containers/podman/v2/libpod"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
-func WaitContainer(w http.ResponseWriter, r *http.Request) (int32, error) {
+type waitQueryDocker struct {
+ Condition string `schema:"condition"`
+}
+
+type waitQueryLibpod struct {
+ Interval string `schema:"interval"`
+ Condition []define.ContainerStatus `schema:"condition"`
+}
+
+func WaitContainerDocker(w http.ResponseWriter, r *http.Request) {
+ var err error
+ ctx := r.Context()
+
+ query := waitQueryDocker{}
+
+ decoder := ctx.Value("decoder").(*schema.Decoder)
+ if err = decoder.Decode(&query, r.URL.Query()); err != nil {
+ Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ interval := time.Nanosecond
+
+ condition := "not-running"
+ if _, found := r.URL.Query()["condition"]; found {
+ condition = query.Condition
+ if !isValidDockerCondition(query.Condition) {
+ BadRequest(w, "condition", condition, errors.New("not a valid docker condition"))
+ return
+ }
+ }
+
+ name := GetName(r)
+
+ exists, err := containerExists(ctx, name)
+
+ if err != nil {
+ InternalServerError(w, err)
+ return
+ }
+ if !exists {
+ ContainerNotFound(w, name, define.ErrNoSuchCtr)
+ return
+ }
+
+ // In docker compatibility mode we have to send headers in advance,
+ // otherwise docker client would freeze.
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(200)
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+
+ exitCode, err := waitDockerCondition(ctx, name, interval, condition)
+ msg := ""
+ if err != nil {
+ logrus.Errorf("error while waiting on condtion: %q", err)
+ msg = err.Error()
+ }
+ responseData := handlers.ContainerWaitOKBody{
+ StatusCode: int(exitCode),
+ Error: struct {
+ Message string
+ }{
+ Message: msg,
+ },
+ }
+ enc := json.NewEncoder(w)
+ enc.SetEscapeHTML(true)
+ err = enc.Encode(&responseData)
+ if err != nil {
+ logrus.Errorf("unable to write json: %q", err)
+ }
+}
+
+func WaitContainerLibpod(w http.ResponseWriter, r *http.Request) {
var (
- err error
- interval time.Duration
+ err error
+ interval = time.Millisecond * 250
+ conditions = []define.ContainerStatus{define.ContainerStateStopped, define.ContainerStateExited}
)
- runtime := r.Context().Value("runtime").(*libpod.Runtime)
- // Now use the ABI implementation to prevent us from having duplicate
- // code.
- containerEngine := abi.ContainerEngine{Libpod: runtime}
decoder := r.Context().Value("decoder").(*schema.Decoder)
- query := struct {
- Interval string `schema:"interval"`
- Condition define.ContainerStatus `schema:"condition"`
- }{
- // Override golang default values for types
- }
+ query := waitQueryLibpod{}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
- return 0, err
- }
- options := entities.WaitOptions{
- Condition: define.ContainerStateStopped,
}
- name := GetName(r)
+
if _, found := r.URL.Query()["interval"]; found {
interval, err = time.ParseDuration(query.Interval)
if err != nil {
InternalServerError(w, err)
- return 0, err
+ return
}
- } else {
- interval, err = time.ParseDuration("250ms")
- if err != nil {
+ }
+
+ if _, found := r.URL.Query()["condition"]; found {
+ if len(query.Condition) > 0 {
+ conditions = query.Condition
+ }
+ }
+
+ name := GetName(r)
+
+ waitFn := createContainerWaitFn(r.Context(), name, interval)
+
+ exitCode, err := waitFn(conditions...)
+ if err != nil {
+ if errors.Cause(err) == define.ErrNoSuchCtr {
+ ContainerNotFound(w, name, err)
+ return
+ } else {
InternalServerError(w, err)
- return 0, err
+ return
}
}
- options.Interval = interval
+ WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode)))
+}
- if _, found := r.URL.Query()["condition"]; found {
- options.Condition = query.Condition
+type containerWaitFn func(conditions ...define.ContainerStatus) (int32, error)
+
+func createContainerWaitFn(ctx context.Context, containerName string, interval time.Duration) containerWaitFn {
+
+ runtime := ctx.Value("runtime").(*libpod.Runtime)
+ var containerEngine entities.ContainerEngine = &abi.ContainerEngine{Libpod: runtime}
+
+ return func(conditions ...define.ContainerStatus) (int32, error) {
+ opts := entities.WaitOptions{
+ Condition: conditions,
+ Interval: interval,
+ }
+ ctrWaitReport, err := containerEngine.ContainerWait(ctx, []string{containerName}, opts)
+ if err != nil {
+ return -1, err
+ }
+ if len(ctrWaitReport) != 1 {
+ return -1, fmt.Errorf("the ContainerWait() function returned unexpected count of reports: %d", len(ctrWaitReport))
+ }
+ return ctrWaitReport[0].ExitCode, ctrWaitReport[0].Error
}
+}
- report, err := containerEngine.ContainerWait(r.Context(), []string{name}, options)
+func isValidDockerCondition(cond string) bool {
+ switch cond {
+ case "next-exit", "removed", "not-running", "":
+ return true
+ }
+ return false
+}
+
+func waitDockerCondition(ctx context.Context, containerName string, interval time.Duration, dockerCondition string) (int32, error) {
+
+ containerWait := createContainerWaitFn(ctx, containerName, interval)
+
+ var err error
+ var code int32
+ switch dockerCondition {
+ case "next-exit":
+ code, err = waitNextExit(containerWait)
+ case "removed":
+ code, err = waitRemoved(containerWait)
+ case "not-running", "":
+ code, err = waitNotRunning(containerWait)
+ default:
+ panic("not a valid docker condition")
+ }
+ return code, err
+}
+
+var notRunningStates = []define.ContainerStatus{
+ define.ContainerStateCreated,
+ define.ContainerStateRemoving,
+ define.ContainerStateStopped,
+ define.ContainerStateExited,
+ define.ContainerStateConfigured,
+}
+
+func waitRemoved(ctrWait containerWaitFn) (int32, error) {
+ code, err := ctrWait(define.ContainerStateUnknown)
+ if err != nil && errors.Cause(err) == define.ErrNoSuchCtr {
+ return code, nil
+ } else {
+ return code, err
+ }
+}
+
+func waitNextExit(ctrWait containerWaitFn) (int32, error) {
+ _, err := ctrWait(define.ContainerStateRunning)
if err != nil {
- return 0, err
+ return -1, err
}
- if len(report) == 0 {
- InternalServerError(w, errors.New("No reports returned"))
- return 0, err
+ return ctrWait(notRunningStates...)
+}
+
+func waitNotRunning(ctrWait containerWaitFn) (int32, error) {
+ return ctrWait(notRunningStates...)
+}
+
+func containerExists(ctx context.Context, name string) (bool, error) {
+ runtime := ctx.Value("runtime").(*libpod.Runtime)
+ var containerEngine entities.ContainerEngine = &abi.ContainerEngine{Libpod: runtime}
+
+ var ctrExistsOpts entities.ContainerExistsOptions
+ ctrExistRep, err := containerEngine.ContainerExists(ctx, name, ctrExistsOpts)
+ if err != nil {
+ return false, err
}
- return report[0].ExitCode, report[0].Error
+ return ctrExistRep.Value, nil
}
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
index ebbe7f24f..b3c674788 100644
--- a/pkg/api/handlers/utils/handler.go
+++ b/pkg/api/handlers/utils/handler.go
@@ -44,8 +44,8 @@ var (
// clients to shop for the Version they wish to support
APIVersion = map[VersionTree]map[VersionLevel]semver.Version{
LibpodTree: {
- CurrentAPIVersion: semver.MustParse("2.0.0"),
- MinimalAPIVersion: semver.MustParse("2.0.0"),
+ CurrentAPIVersion: semver.MustParse("3.0.0"),
+ MinimalAPIVersion: semver.MustParse("3.0.0"),
},
CompatTree: {
CurrentAPIVersion: semver.MustParse("1.40.0"),
diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go
index 771cde72c..4889b444a 100644
--- a/pkg/bindings/containers/types.go
+++ b/pkg/bindings/containers/types.go
@@ -176,7 +176,7 @@ type UnpauseOptions struct{}
//go:generate go run ../generator/generator.go WaitOptions
// WaitOptions are optional options for waiting on containers
type WaitOptions struct {
- Condition *define.ContainerStatus
+ Condition []define.ContainerStatus
Interval *string
}
diff --git a/pkg/bindings/containers/types_wait_options.go b/pkg/bindings/containers/types_wait_options.go
index 005cc38cb..a3f1e3b8c 100644
--- a/pkg/bindings/containers/types_wait_options.go
+++ b/pkg/bindings/containers/types_wait_options.go
@@ -76,19 +76,19 @@ func (o *WaitOptions) ToParams() (url.Values, error) {
}
// WithCondition
-func (o *WaitOptions) WithCondition(value define.ContainerStatus) *WaitOptions {
- v := &value
+func (o *WaitOptions) WithCondition(value []define.ContainerStatus) *WaitOptions {
+ v := value
o.Condition = v
return o
}
// GetCondition
-func (o *WaitOptions) GetCondition() define.ContainerStatus {
- var condition define.ContainerStatus
+func (o *WaitOptions) GetCondition() []define.ContainerStatus {
+ var condition []define.ContainerStatus
if o.Condition == nil {
return condition
}
- return *o.Condition
+ return o.Condition
}
// WithInterval
diff --git a/pkg/bindings/test/attach_test.go b/pkg/bindings/test/attach_test.go
index 9a46f6309..771b2d528 100644
--- a/pkg/bindings/test/attach_test.go
+++ b/pkg/bindings/test/attach_test.go
@@ -75,7 +75,7 @@ var _ = Describe("Podman containers attach", func() {
Expect(err).ShouldNot(HaveOccurred())
wait := define.ContainerStateRunning
- _, err = containers.Wait(bt.conn, ctnr.ID, new(containers.WaitOptions).WithCondition(wait))
+ _, err = containers.Wait(bt.conn, ctnr.ID, new(containers.WaitOptions).WithCondition([]define.ContainerStatus{wait}))
Expect(err).ShouldNot(HaveOccurred())
tickTock := time.NewTimer(2 * time.Second)
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
index c2b1347d2..8fbc631d8 100644
--- a/pkg/bindings/test/common_test.go
+++ b/pkg/bindings/test/common_test.go
@@ -207,7 +207,7 @@ func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, po
return "", err
}
wait := define.ContainerStateRunning
- _, err = containers.Wait(b.conn, ctr.ID, new(containers.WaitOptions).WithCondition(wait))
+ _, err = containers.Wait(b.conn, ctr.ID, new(containers.WaitOptions).WithCondition([]define.ContainerStatus{wait}))
return ctr.ID, err
}
diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go
index 9b9f98047..14eb1ffc6 100644
--- a/pkg/bindings/test/containers_test.go
+++ b/pkg/bindings/test/containers_test.go
@@ -281,7 +281,7 @@ var _ = Describe("Podman containers ", func() {
_, err := bt.RunTopContainer(&name, nil, nil)
Expect(err).To(BeNil())
go func() {
- exitCode, err = containers.Wait(bt.conn, name, new(containers.WaitOptions).WithCondition(pause))
+ exitCode, err = containers.Wait(bt.conn, name, new(containers.WaitOptions).WithCondition([]define.ContainerStatus{pause}))
errChan <- err
close(errChan)
}()
@@ -295,7 +295,7 @@ var _ = Describe("Podman containers ", func() {
go func() {
defer GinkgoRecover()
- _, waitErr := containers.Wait(bt.conn, name, new(containers.WaitOptions).WithCondition(running))
+ _, waitErr := containers.Wait(bt.conn, name, new(containers.WaitOptions).WithCondition([]define.ContainerStatus{running}))
unpauseErrChan <- waitErr
close(unpauseErrChan)
}()
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 63be5578f..2d50d6826 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -51,7 +51,7 @@ type ContainerRunlabelReport struct {
}
type WaitOptions struct {
- Condition define.ContainerStatus
+ Condition []define.ContainerStatus
Interval time.Duration
Latest bool
}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index d0599a595..7a672d863 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -100,7 +100,7 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin
responses := make([]entities.WaitReport, 0, len(ctrs))
for _, c := range ctrs {
response := entities.WaitReport{Id: c.ID()}
- exitCode, err := c.WaitForConditionWithInterval(options.Interval, options.Condition)
+ exitCode, err := c.WaitForConditionWithInterval(ctx, options.Interval, options.Condition...)
if err != nil {
response.Error = err
} else {
@@ -728,7 +728,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID())
}
- if ecode, err := ctr.Wait(); err != nil {
+ if ecode, err := ctr.Wait(ctx); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
// Check events
event, err := ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited)
@@ -867,7 +867,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
return &report, err
}
- if ecode, err := ctr.Wait(); err != nil {
+ if ecode, err := ctr.Wait(ctx); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
// Check events
event, err := ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited)
diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at
index f550d5fc3..1ddf49c6f 100644
--- a/test/apiv2/01-basic.at
+++ b/test/apiv2/01-basic.at
@@ -18,8 +18,8 @@ t HEAD libpod/_ping 200
for i in /version version; do
t GET $i 200 \
.Components[0].Name="Podman Engine" \
- .Components[0].Details.APIVersion=2.0.0 \
- .Components[0].Details.MinAPIVersion=2.0.0 \
+ .Components[0].Details.APIVersion=3.0.0 \
+ .Components[0].Details.MinAPIVersion=3.0.0 \
.Components[0].Details.Os=linux \
.ApiVersion=1.40 \
.MinAPIVersion=1.24 \
diff --git a/test/apiv2/26-containersWait.at b/test/apiv2/26-containersWait.at
new file mode 100644
index 000000000..3f530c3f0
--- /dev/null
+++ b/test/apiv2/26-containersWait.at
@@ -0,0 +1,47 @@
+# -*- sh -*-
+#
+# test more container-related endpoints
+#
+
+red='\e[31m'
+nc='\e[0m'
+
+podman pull "${IMAGE}" &>/dev/null
+
+# Ensure clean slate
+podman rm -a -f &>/dev/null
+
+CTR="WaitTestingCtr"
+
+t POST "containers/nonExistent/wait?condition=next-exit" '' 404
+
+podman create --name "${CTR}" --entrypoint '["sleep", "0.5"]' "${IMAGE}"
+
+t POST "containers/${CTR}/wait?condition=non-existent-cond" '' 400
+
+t POST "containers/${CTR}/wait?condition=not-running" '' 200
+
+t POST "containers/${CTR}/wait?condition=next-exit" '' 200 &
+child_pid=$!
+podman start "${CTR}"
+wait "${child_pid}"
+
+
+# check if headers are sent in advance before body
+WAIT_TEST_ERROR=""
+curl -I -X POST "http://$HOST:$PORT/containers/${CTR}/wait?condition=next-exit" &> "/dev/null" &
+child_pid=$!
+sleep 0.5
+if kill -2 "${child_pid}" 2> "/dev/null"; then
+ echo -e "${red}NOK: Failed to get response headers immediately.${nc}" 1>&2;
+ WAIT_TEST_ERROR="1"
+fi
+
+t POST "containers/${CTR}/wait?condition=removed" '' 200 &
+child_pid=$!
+podman container rm "${CTR}"
+wait "${child_pid}"
+
+if [[ "${WAIT_TEST_ERROR}" ]] ; then
+ exit 1;
+fi
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index 61c0cb4fe..54d801e12 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -441,7 +441,7 @@ func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers
err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755)
Expect(err).To(BeNil())
session := p.Podman([]string{"build", "--layers=" + layers, "-t", imageName, "--file", dockerfilePath, p.TempDir})
- session.Wait(120)
+ session.Wait(240)
Expect(session).Should(Exit(0), fmt.Sprintf("BuildImage session output: %q", session.OutputToString()))
}
diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go
index dba366a1e..bcfab0f68 100644
--- a/test/e2e/generate_kube_test.go
+++ b/test/e2e/generate_kube_test.go
@@ -1,6 +1,7 @@
package integration
import (
+ "io/ioutil"
"os"
"path/filepath"
"strconv"
@@ -639,4 +640,63 @@ var _ = Describe("Podman generate kube", func() {
Expect(pod.Spec.DNSConfig.Options[0].Name).To(Equal("color"))
Expect(*pod.Spec.DNSConfig.Options[0].Value).To(Equal("blue"))
})
+
+ It("podman generate kube - set entrypoint as command", func() {
+ session := podmanTest.Podman([]string{"create", "--pod", "new:testpod", "--entrypoint", "/bin/sleep", ALPINE, "10s"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", "testpod"})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ // Now make sure that the container's command is set to the
+ // entrypoint and it's arguments to "10s".
+ pod := new(v1.Pod)
+ err := yaml.Unmarshal(kube.Out.Contents(), pod)
+ Expect(err).To(BeNil())
+
+ containers := pod.Spec.Containers
+ Expect(len(containers)).To(Equal(1))
+
+ Expect(containers[0].Command).To(Equal([]string{"/bin/sleep"}))
+ Expect(containers[0].Args).To(Equal([]string{"10s"}))
+ })
+
+ It("podman generate kube - use entrypoint from image", func() {
+ // Build an image with an entrypoint.
+ containerfile := `FROM quay.io/libpod/alpine:latest
+ENTRYPOINT /bin/sleep`
+
+ targetPath, err := CreateTempDirInTempDir()
+ Expect(err).To(BeNil())
+ containerfilePath := filepath.Join(targetPath, "Containerfile")
+ err = ioutil.WriteFile(containerfilePath, []byte(containerfile), 0644)
+ Expect(err).To(BeNil())
+
+ image := "generatekube:test"
+ session := podmanTest.Podman([]string{"build", "-f", containerfilePath, "-t", image})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ session = podmanTest.Podman([]string{"create", "--pod", "new:testpod", image, "10s"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ kube := podmanTest.Podman([]string{"generate", "kube", "testpod"})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube.ExitCode()).To(Equal(0))
+
+ // Now make sure that the container's command is set to the
+ // entrypoint and it's arguments to "10s".
+ pod := new(v1.Pod)
+ err = yaml.Unmarshal(kube.Out.Contents(), pod)
+ Expect(err).To(BeNil())
+
+ containers := pod.Spec.Containers
+ Expect(len(containers)).To(Equal(1))
+
+ Expect(containers[0].Command).To(Equal([]string{"/bin/sh", "-c", "/bin/sleep"}))
+ Expect(containers[0].Args).To(Equal([]string{"10s"}))
+ })
})
diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go
index dd94bd7ca..cc23b10c1 100644
--- a/test/e2e/network_connect_disconnect_test.go
+++ b/test/e2e/network_connect_disconnect_test.go
@@ -74,6 +74,11 @@ var _ = Describe("Podman network connect and disconnect", func() {
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).To(BeZero())
+ inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(BeZero())
+ Expect(inspect.OutputToString()).To(Equal("0"))
+
exec = podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth0"})
exec.WaitWithDefaultTimeout()
Expect(exec.ExitCode()).ToNot(BeZero())
@@ -146,6 +151,11 @@ var _ = Describe("Podman network connect and disconnect", func() {
connect.WaitWithDefaultTimeout()
Expect(connect.ExitCode()).To(BeZero())
+ inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(BeZero())
+ Expect(inspect.OutputToString()).To(Equal("2"))
+
exec = podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth1"})
exec.WaitWithDefaultTimeout()
Expect(exec.ExitCode()).To(BeZero())
@@ -167,6 +177,11 @@ var _ = Describe("Podman network connect and disconnect", func() {
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).To(BeZero())
+ inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(BeZero())
+ Expect(inspect.OutputToString()).To(Equal("2"))
+
start := podmanTest.Podman([]string{"start", "test"})
start.WaitWithDefaultTimeout()
Expect(start.ExitCode()).To(BeZero())
@@ -202,6 +217,11 @@ var _ = Describe("Podman network connect and disconnect", func() {
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).To(BeZero())
+ inspect := podmanTest.Podman([]string{"container", "inspect", "test", "--format", "{{len .NetworkSettings.Networks}}"})
+ inspect.WaitWithDefaultTimeout()
+ Expect(inspect.ExitCode()).To(BeZero())
+ Expect(inspect.OutputToString()).To(Equal("1"))
+
start := podmanTest.Podman([]string{"start", "test"})
start.WaitWithDefaultTimeout()
Expect(start.ExitCode()).To(BeZero())
diff --git a/test/e2e/wait_test.go b/test/e2e/wait_test.go
index aa8a1f245..4f1e74977 100644
--- a/test/e2e/wait_test.go
+++ b/test/e2e/wait_test.go
@@ -34,7 +34,7 @@ var _ = Describe("Podman wait", func() {
It("podman wait on bogus container", func() {
session := podmanTest.Podman([]string{"wait", "1234"})
- session.Wait()
+ session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(125))
})
@@ -45,7 +45,7 @@ var _ = Describe("Podman wait", func() {
cid := session.OutputToString()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"wait", cid})
- session.Wait()
+ session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
})