From 64153ace05955e90e2c11dea3b64d86d23d12eb3 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Thu, 22 Jun 2023 10:02:26 +0200 Subject: [PATCH 1/3] podman wait: update man page While reading the code I found the man page to be lacking some information that I found worth mentioning and clarifying. In particular, how the command behaves with respect to exit codes and when more than one condition is specified. Signed-off-by: Valentin Rothberg --- docs/source/markdown/podman-wait.1.md.in | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/source/markdown/podman-wait.1.md.in b/docs/source/markdown/podman-wait.1.md.in index a946a9bcd5..ab4c7c0fb7 100644 --- a/docs/source/markdown/podman-wait.1.md.in +++ b/docs/source/markdown/podman-wait.1.md.in @@ -11,8 +11,10 @@ podman\-wait - Wait on one or more containers to stop and print their exit codes ## DESCRIPTION Waits on one or more containers to stop. The container can be referred to by its name or ID. In the case of multiple containers, Podman waits on each consecutively. -After all specified containers are stopped, the containers' return codes are printed -separated by newline in the same order as they were given to the command. +After all conditions are satisfied, the containers' return codes are printed +separated by newline in the same order as they were given to the command. An +exit code of -1 is emitted for all conditions other than "stopped" and +"exited". NOTE: there is an inherent race condition when waiting for containers with a restart policy of `always` or `on-failure`, such as those created by `podman @@ -22,7 +24,7 @@ with different exit codes, but `podman wait` can only display and detect one. ## OPTIONS #### **--condition**=*state* -Condition to wait on (default "stopped") +Container state or condition to wait for. Can be specified multiple times where at least one condition must match for the command to return. Supported values are "created", "exited", "initialized", "paused", "removing", "running", "stopped", "stopping". The default condition is "stopped". #### **--help**, **-h** From 811867249b3e0dddc0a73ac41b93941201b7051a Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Thu, 22 Jun 2023 08:33:16 +0200 Subject: [PATCH 2/3] container wait API: use string slice instead of state slice Massage the internal APIs to use a string slice instead of a state slice for passing wait conditions. This paves the way for waiting on non-state conditions such as "healthy". Signed-off-by: Valentin Rothberg --- cmd/podman/containers/exec.go | 8 ++------ cmd/podman/containers/wait.go | 16 +++------------- libpod/container_api.go | 8 ++++++-- pkg/api/handlers/compat/containers.go | 4 ++-- pkg/api/handlers/utils/containers.go | 8 ++++++-- pkg/domain/entities/containers.go | 20 +++++++++++++++----- pkg/domain/infra/abi/containers.go | 9 ++++++--- pkg/domain/infra/tunnel/containers.go | 11 ++++++++++- 8 files changed, 50 insertions(+), 34 deletions(-) diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index 9b36d147e1..b3f6b04039 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -185,15 +185,11 @@ func execWait(ctr string, seconds int32) error { ctx, cancel := context.WithTimeout(registry.Context(), maxDuration) defer cancel() - cond, err := define.StringToContainerStatus("running") - if err != nil { - return err - } - waitOptions.Condition = append(waitOptions.Condition, cond) + waitOptions.Conditions = []string{define.ContainerStateRunning.String()} startTime := time.Now() for time.Since(startTime) < maxDuration { - _, err = registry.ContainerEngine().ContainerWait(ctx, []string{ctr}, waitOptions) + _, err := registry.ContainerEngine().ContainerWait(ctx, []string{ctr}, waitOptions) if err == nil { return nil } diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go index 0b6a5dc5f1..da038db250 100644 --- a/cmd/podman/containers/wait.go +++ b/cmd/podman/containers/wait.go @@ -11,7 +11,6 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/cmd/podman/validate" - "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -41,9 +40,8 @@ var ( ) var ( - waitOptions = entities.WaitOptions{} - waitConditions []string - waitInterval string + waitOptions = entities.WaitOptions{} + waitInterval string ) func waitFlags(cmd *cobra.Command) { @@ -56,7 +54,7 @@ func waitFlags(cmd *cobra.Command) { flags.BoolVarP(&waitOptions.Ignore, "ignore", "", false, "Ignore if a container does not exist") conditionFlagName := "condition" - flags.StringSliceVar(&waitConditions, conditionFlagName, []string{}, "Condition to wait on") + flags.StringSliceVar(&waitOptions.Conditions, conditionFlagName, []string{}, "Condition to wait on") _ = cmd.RegisterFlagCompletionFunc(conditionFlagName, common.AutocompleteWaitCondition) } @@ -95,14 +93,6 @@ func wait(cmd *cobra.Command, args []string) error { return errors.New("--latest and containers cannot be used together") } - for _, condition := range waitConditions { - cond, err := define.StringToContainerStatus(condition) - if err != nil { - return err - } - waitOptions.Condition = append(waitOptions.Condition, cond) - } - responses, err := registry.ContainerEngine().ContainerWait(context.Background(), args, waitOptions) if err != nil { return err diff --git a/libpod/container_api.go b/libpod/container_api.go index e230cc39f3..c05bb8475b 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -683,7 +683,7 @@ type waitResult struct { err error } -func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeout time.Duration, conditions ...define.ContainerStatus) (int32, error) { +func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeout time.Duration, conditions ...string) (int32, error) { if !c.valid { return -1, define.ErrCtrRemoved } @@ -699,7 +699,11 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou waitForExit := false wantedStates := make(map[define.ContainerStatus]bool, len(conditions)) - for _, condition := range conditions { + for _, rawCondition := range conditions { + condition, err := define.StringToContainerStatus(rawCondition) + if err != nil { + return -1, err + } switch condition { case define.ContainerStateExited, define.ContainerStateStopped: waitForExit = true diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 042fb9e893..4a213ac734 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -256,8 +256,8 @@ func KillContainer(w http.ResponseWriter, r *http.Request) { } if sig == 0 || sig == syscall.SIGKILL { opts := entities.WaitOptions{ - Condition: []define.ContainerStatus{define.ContainerStateExited, define.ContainerStateStopped}, - Interval: time.Millisecond * 250, + Conditions: []string{define.ContainerStateExited.String(), define.ContainerStateStopped.String()}, + Interval: time.Millisecond * 250, } if _, err := containerEngine.ContainerWait(r.Context(), []string{name}, opts); err != nil { utils.Error(w, http.StatusInternalServerError, err) diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index c937d61a03..90159ef96f 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -140,9 +140,13 @@ func createContainerWaitFn(ctx context.Context, containerName string, interval t var containerEngine entities.ContainerEngine = &abi.ContainerEngine{Libpod: runtime} return func(conditions ...define.ContainerStatus) (int32, error) { + var rawConditions []string + for _, con := range conditions { + rawConditions = append(rawConditions, con.String()) + } opts := entities.WaitOptions{ - Condition: conditions, - Interval: interval, + Conditions: rawConditions, + Interval: interval, } ctrWaitReport, err := containerEngine.ContainerWait(ctx, []string{containerName}, opts) if err != nil { diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 940e6b0889..596a444cbe 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -49,15 +49,25 @@ type ContainerRunlabelOptions struct { // ContainerRunlabelReport contains the results from executing container-runlabel. type ContainerRunlabelReport struct{} +// WaitOptions are arguments for waiting for a container. type WaitOptions struct { - Condition []define.ContainerStatus - Interval time.Duration - Ignore bool - Latest bool + // Conditions to wait on. Includes container statuses such as + // "running" or "stopped" and health-related values such "healthy". + Conditions []string + // Time interval to wait before polling for completion. + Interval time.Duration + // Ignore errors when a specified container is missing and mark its + // return code as -1. + Ignore bool + // Use the latest created container. + Latest bool } +// WaitReport is the result of waiting a container. type WaitReport struct { - Error error + // Error while waiting. + Error error + // ExitCode of the container. ExitCode int32 } diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 46bf1d0b61..62102b45d8 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -185,10 +185,13 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin } response := entities.WaitReport{} - if options.Condition == nil { - options.Condition = []define.ContainerStatus{define.ContainerStateStopped, define.ContainerStateExited} + var conditions []string + if len(options.Conditions) == 0 { + conditions = []string{define.ContainerStateStopped.String(), define.ContainerStateExited.String()} + } else { + conditions = options.Conditions } - exitCode, err := c.WaitForConditionWithInterval(ctx, options.Interval, options.Condition...) + exitCode, err := c.WaitForConditionWithInterval(ctx, options.Interval, conditions...) if err != nil { response.Error = err } else { diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 663956fc2a..711b114008 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -38,8 +38,17 @@ func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string, } func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, opts entities.WaitOptions) ([]entities.WaitReport, error) { + conditions := make([]define.ContainerStatus, 0, len(opts.Conditions)) + for _, condition := range opts.Conditions { + cond, err := define.StringToContainerStatus(condition) + if err != nil { + return nil, err + } + conditions = append(conditions, cond) + } + responses := make([]entities.WaitReport, 0, len(namesOrIds)) - options := new(containers.WaitOptions).WithCondition(opts.Condition).WithInterval(opts.Interval.String()) + options := new(containers.WaitOptions).WithCondition(conditions).WithInterval(opts.Interval.String()) for _, n := range namesOrIds { response := entities.WaitReport{} exitCode, err := containers.Wait(ic.ClientCtx, n, options) From 1398cbce8ac186559770707db36890043408b533 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Thu, 22 Jun 2023 10:47:24 +0200 Subject: [PATCH 3/3] container wait: support health states Support two new wait conditions, "healthy" and "unhealthy". This further paves the way for integrating sdnotify with health checks which is currently being tracked in #6160. Fixes: #13627 Signed-off-by: Valentin Rothberg --- cmd/podman/common/completion.go | 4 +- docs/source/markdown/podman-wait.1.md.in | 2 +- libpod/container_api.go | 54 +++++++++++++------ pkg/api/handlers/utils/containers.go | 20 ++++--- pkg/api/server/register_containers.go | 9 ++-- pkg/bindings/containers/containers.go | 3 ++ pkg/bindings/containers/types.go | 8 ++- pkg/bindings/containers/types_wait_options.go | 31 ++++++++--- pkg/domain/infra/tunnel/containers.go | 11 +--- test/system/220-healthcheck.bats | 42 ++++++++++++++- 10 files changed, 137 insertions(+), 47 deletions(-) diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index ac487708bc..45bf00a4ba 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -1474,7 +1474,9 @@ func AutocompleteImageSaveFormat(cmd *cobra.Command, args []string, toComplete s // AutocompleteWaitCondition - Autocomplete wait condition options. // -> "unknown", "configured", "created", "running", "stopped", "paused", "exited", "removing" func AutocompleteWaitCondition(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - states := []string{"unknown", "configured", "created", "running", "stopped", "paused", "exited", "removing"} + states := []string{"unknown", "configured", "created", "exited", + "healthy", "initialized", "paused", "removing", "running", + "stopped", "stopping", "unhealthy"} return states, cobra.ShellCompDirectiveNoFileComp } diff --git a/docs/source/markdown/podman-wait.1.md.in b/docs/source/markdown/podman-wait.1.md.in index ab4c7c0fb7..3ffacb5e49 100644 --- a/docs/source/markdown/podman-wait.1.md.in +++ b/docs/source/markdown/podman-wait.1.md.in @@ -24,7 +24,7 @@ with different exit codes, but `podman wait` can only display and detect one. ## OPTIONS #### **--condition**=*state* -Container state or condition to wait for. Can be specified multiple times where at least one condition must match for the command to return. Supported values are "created", "exited", "initialized", "paused", "removing", "running", "stopped", "stopping". The default condition is "stopped". +Container state or condition to wait for. Can be specified multiple times where at least one condition must match for the command to return. Supported values are "configured", "created", "exited", "healthy", "initialized", "paused", "removing", "running", "stopped", "stopping", "unhealthy". The default condition is "stopped". #### **--help**, **-h** diff --git a/libpod/container_api.go b/libpod/container_api.go index c05bb8475b..aa29d95d82 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -698,17 +698,26 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou resultChan := make(chan waitResult) waitForExit := false wantedStates := make(map[define.ContainerStatus]bool, len(conditions)) + wantedHealthStates := make(map[string]bool) for _, rawCondition := range conditions { - condition, err := define.StringToContainerStatus(rawCondition) - if err != nil { - return -1, err - } - switch condition { - case define.ContainerStateExited, define.ContainerStateStopped: - waitForExit = true + switch rawCondition { + case define.HealthCheckHealthy, define.HealthCheckUnhealthy: + if !c.HasHealthCheck() { + return -1, fmt.Errorf("cannot use condition %q: container %s has no healthcheck", rawCondition, c.ID()) + } + wantedHealthStates[rawCondition] = true default: - wantedStates[condition] = true + condition, err := define.StringToContainerStatus(rawCondition) + if err != nil { + return -1, err + } + switch condition { + case define.ContainerStateExited, define.ContainerStateStopped: + waitForExit = true + default: + wantedStates[condition] = true + } } } @@ -731,20 +740,33 @@ func (c *Container) WaitForConditionWithInterval(ctx context.Context, waitTimeou }() } - if len(wantedStates) > 0 { + if len(wantedStates) > 0 || len(wantedHealthStates) > 0 { wg.Add(1) go func() { defer wg.Done() for { - state, err := c.State() - if err != nil { - trySend(-1, err) - return + if len(wantedStates) > 0 { + state, err := c.State() + if err != nil { + trySend(-1, err) + return + } + if _, found := wantedStates[state]; found { + trySend(-1, nil) + return + } } - if _, found := wantedStates[state]; found { - trySend(-1, nil) - return + if len(wantedHealthStates) > 0 { + status, err := c.HealthCheckStatus() + if err != nil { + trySend(-1, err) + return + } + if _, found := wantedHealthStates[status]; found { + trySend(-1, nil) + return + } } select { case <-ctx.Done(): diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index 90159ef96f..6491dc4029 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -28,8 +28,8 @@ type waitQueryDocker struct { } type waitQueryLibpod struct { - Interval string `schema:"interval"` - Condition []define.ContainerStatus `schema:"condition"` + Interval string `schema:"interval"` + Conditions []string `schema:"condition"` } func WaitContainerDocker(w http.ResponseWriter, r *http.Request) { @@ -118,19 +118,27 @@ func WaitContainerLibpod(w http.ResponseWriter, r *http.Request) { } } + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + containerEngine := &abi.ContainerEngine{Libpod: runtime} + opts := entities.WaitOptions{ + Conditions: query.Conditions, + Interval: interval, + } name := GetName(r) - - waitFn := createContainerWaitFn(r.Context(), name, interval) - exitCode, err := waitFn(query.Condition...) + reports, err := containerEngine.ContainerWait(r.Context(), []string{name}, opts) if err != nil { if errors.Is(err, define.ErrNoSuchCtr) { ContainerNotFound(w, name, err) return } InternalServerError(w, err) + } + if len(reports) != 1 { + Error(w, http.StatusInternalServerError, fmt.Errorf("the ContainerWait() function returned unexpected count of reports: %d", len(reports))) return } - WriteResponse(w, http.StatusOK, strconv.Itoa(int(exitCode))) + + WriteResponse(w, http.StatusOK, strconv.Itoa(int(reports[0].ExitCode))) } type containerWaitFn func(conditions ...define.ContainerStatus) (int32, error) diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 48b27c09c8..d305bec9f9 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -1229,12 +1229,15 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // enum: // - configured // - created + // - exited + // - healthy + // - initialized + // - paused + // - removing // - running // - stopped - // - paused - // - exited - // - removing // - stopping + // - unhealthy // description: "Conditions to wait for. If no condition provided the 'exited' condition is assumed." // - in: query // name: interval diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 80ec7bc6fc..e776a60920 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -341,6 +341,8 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) erro func Wait(ctx context.Context, nameOrID string, options *WaitOptions) (int32, error) { if options == nil { options = new(WaitOptions) + } else if len(options.Condition) > 0 && len(options.Conditions) > 0 { + return -1, fmt.Errorf("%q field cannot be used with deprecated %q field", "Conditions", "Condition") } var exitCode int32 conn, err := bindings.GetClient(ctx) @@ -351,6 +353,7 @@ func Wait(ctx context.Context, nameOrID string, options *WaitOptions) (int32, er if err != nil { return exitCode, err } + delete(params, "conditions") // They're called "condition" response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/containers/%s/wait", params, nil, nameOrID) if err != nil { return exitCode, err diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index 8e9f39dd5a..6b25aae7e3 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -229,8 +229,14 @@ type UnpauseOptions struct{} // //go:generate go run ../generator/generator.go WaitOptions type WaitOptions struct { + // Conditions to wait on. Includes container statuses such as + // "running" or "stopped" and health-related values such "healthy". + Conditions []string `schema:"condition"` + // Time interval to wait before polling for completion. + Interval *string + // Container status to wait on. + // Deprecated: use Conditions instead. Condition []define.ContainerStatus - Interval *string } // StopOptions are optional options for stopping containers diff --git a/pkg/bindings/containers/types_wait_options.go b/pkg/bindings/containers/types_wait_options.go index e74c078212..260a73d6f9 100644 --- a/pkg/bindings/containers/types_wait_options.go +++ b/pkg/bindings/containers/types_wait_options.go @@ -18,19 +18,19 @@ func (o *WaitOptions) ToParams() (url.Values, error) { return util.ToParams(o) } -// WithCondition set field Condition to given value -func (o *WaitOptions) WithCondition(value []define.ContainerStatus) *WaitOptions { - o.Condition = value +// WithConditions set field Conditions to given value +func (o *WaitOptions) WithConditions(value []string) *WaitOptions { + o.Conditions = value return o } -// GetCondition returns value of field Condition -func (o *WaitOptions) GetCondition() []define.ContainerStatus { - if o.Condition == nil { - var z []define.ContainerStatus +// GetConditions returns value of field Conditions +func (o *WaitOptions) GetConditions() []string { + if o.Conditions == nil { + var z []string return z } - return o.Condition + return o.Conditions } // WithInterval set field Interval to given value @@ -47,3 +47,18 @@ func (o *WaitOptions) GetInterval() string { } return *o.Interval } + +// WithCondition set field Condition to given value +func (o *WaitOptions) WithCondition(value []define.ContainerStatus) *WaitOptions { + o.Condition = value + return o +} + +// GetCondition returns value of field Condition +func (o *WaitOptions) GetCondition() []define.ContainerStatus { + if o.Condition == nil { + var z []define.ContainerStatus + return z + } + return o.Condition +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 711b114008..16817ee4c2 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -38,17 +38,8 @@ func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string, } func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, opts entities.WaitOptions) ([]entities.WaitReport, error) { - conditions := make([]define.ContainerStatus, 0, len(opts.Conditions)) - for _, condition := range opts.Conditions { - cond, err := define.StringToContainerStatus(condition) - if err != nil { - return nil, err - } - conditions = append(conditions, cond) - } - responses := make([]entities.WaitReport, 0, len(namesOrIds)) - options := new(containers.WaitOptions).WithCondition(conditions).WithInterval(opts.Interval.String()) + options := new(containers.WaitOptions).WithConditions(opts.Conditions).WithInterval(opts.Interval.String()) for _, n := range namesOrIds { response := entities.WaitReport{} exitCode, err := containers.Wait(ic.ClientCtx, n, options) diff --git a/test/system/220-healthcheck.bats b/test/system/220-healthcheck.bats index 828232b39d..df4d26ac1c 100644 --- a/test/system/220-healthcheck.bats +++ b/test/system/220-healthcheck.bats @@ -83,7 +83,7 @@ Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\" _build_health_check_image $img cleanfile run_podman run -d --name $ctr \ --health-cmd /healthcheck \ - --health-retries=2 \ + --health-retries=3 \ --health-interval=disable \ $img @@ -105,6 +105,46 @@ Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\" run_podman rmi $img } +@test "podman wait --condition={healthy,unhealthy}" { + ctr="healthcheck_c" + img="healthcheck_i" + wait_file="$PODMAN_TMPDIR/$(random_string).wait_for_me" + _build_health_check_image $img + + for condition in healthy unhealthy;do + rm -f $wait_file + run_podman run -d --name $ctr \ + --health-cmd /healthcheck \ + --health-retries=1 \ + --health-interval=disable \ + $img + if [[ $condition == "unhealthy" ]];then + # create the uh-oh file to let the health check fail + run_podman exec $ctr touch /uh-oh + fi + + # Wait for the container in the background and create the $wait_file to + # signal the specified wait condition was met. + (timeout --foreground -v --kill=5 5 $PODMAN wait --condition=$condition $ctr && touch $wait_file) & + + # Sleep 1 second to make sure above commands are running + sleep 1 + if [[ -f $wait_file ]]; then + die "the wait file should only be created after the container turned healthy" + fi + + if [[ $condition == "healthy" ]];then + run_podman healthcheck run $ctr + else + run_podman 1 healthcheck run $ctr + fi + wait_for_file $wait_file + run_podman rm -f -t0 $ctr + done + + run_podman rmi $img +} + @test "podman healthcheck --health-on-failure" { run_podman 125 create --health-on-failure=kill $IMAGE is "$output" "Error: cannot set on-failure action to kill without a health check"