Signed-off-by: Oleksandr Krutko <alexander.krutko@gmail.com>
This commit is contained in:
Oleksandr Krutko
2025-08-02 22:57:23 +03:00
parent 0c4c9e4fbc
commit a75f74b1d3
6 changed files with 97 additions and 0 deletions

View File

@ -56,6 +56,9 @@ func waitFlags(cmd *cobra.Command) {
conditionFlagName := "condition" conditionFlagName := "condition"
flags.StringSliceVar(&waitOptions.Conditions, conditionFlagName, []string{}, "Condition to wait on") flags.StringSliceVar(&waitOptions.Conditions, conditionFlagName, []string{}, "Condition to wait on")
_ = cmd.RegisterFlagCompletionFunc(conditionFlagName, common.AutocompleteWaitCondition) _ = cmd.RegisterFlagCompletionFunc(conditionFlagName, common.AutocompleteWaitCondition)
waitExitFirst := "exit-first-match"
flags.BoolVar(&waitOptions.ExitFirstMatch, waitExitFirst, false, "Wait for exit of first container which matches conditions, ignore other ones")
} }
func init() { func init() {

View File

@ -30,6 +30,9 @@ container to be fully removed. To wait for the removal of a container use
#### **--condition**=*state* #### **--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 "configured", "created", "exited", "healthy", "initialized", "paused", "removing", "running", "stopped", "stopping", "unhealthy". 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".
#### **--exit-first-match**
Wait for exit of first container which matches conditions, ignore other ones.
#### **--help**, **-h** #### **--help**, **-h**
Print usage statement Print usage statement

View File

@ -62,6 +62,8 @@ type WaitOptions struct {
Ignore bool Ignore bool
// Use the latest created container. // Use the latest created container.
Latest bool Latest bool
// Wait for exit of first container which matches conditions, ignore other ones.
ExitFirstMatch bool
} }
// WaitReport is the result of waiting a container. // WaitReport is the result of waiting a container.

View File

@ -184,6 +184,13 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin
if err != nil { if err != nil {
return nil, err return nil, err
} }
if options.ExitFirstMatch {
response := waitExitOnFirst(ctx, containers, options)
responses = append(responses, response)
return responses, nil
}
for _, c := range containers { for _, c := range containers {
if c.doesNotExist { // Only set when `options.Ignore == true` if c.doesNotExist { // Only set when `options.Ignore == true`
responses = append(responses, entities.WaitReport{ExitCode: -1}) responses = append(responses, entities.WaitReport{ExitCode: -1})
@ -208,6 +215,43 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin
return responses, nil return responses, nil
} }
func waitExitOnFirst(ctx context.Context, containers []containerWrapper, options entities.WaitOptions) entities.WaitReport {
var waitChannel = make(chan entities.WaitReport, 1)
var waitFunction = func(ctx context.Context, container containerWrapper, options entities.WaitOptions, waitChannel chan<- entities.WaitReport) {
response := entities.WaitReport{}
var conditions []string
if len(options.Conditions) == 0 {
conditions = []string{define.ContainerStateStopped.String(), define.ContainerStateExited.String()}
} else {
conditions = options.Conditions
}
exitCode, err := container.WaitForConditionWithInterval(ctx, options.Interval, conditions...)
if err != nil {
response.Error = err
} else {
response.ExitCode = exitCode
}
select {
case <-ctx.Done():
case waitChannel <- response:
}
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for _, c := range containers {
if c.doesNotExist {
continue
}
go waitFunction(ctx, c, options, waitChannel)
}
response := <-waitChannel
return response
}
func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) { func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) {
containers, err := getContainers(ic.Libpod, getContainersOptions{all: options.All, latest: options.Latest, names: namesOrIds, filters: options.Filters}) containers, err := getContainers(ic.Libpod, getContainersOptions{all: options.All, latest: options.Latest, names: namesOrIds, filters: options.Filters})
if err != nil { if err != nil {

View File

@ -40,6 +40,39 @@ func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string,
func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, opts entities.WaitOptions) ([]entities.WaitReport, error) { func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []string, opts entities.WaitOptions) ([]entities.WaitReport, error) {
responses := make([]entities.WaitReport, 0, len(namesOrIds)) responses := make([]entities.WaitReport, 0, len(namesOrIds))
options := new(containers.WaitOptions).WithConditions(opts.Conditions).WithInterval(opts.Interval.String()) options := new(containers.WaitOptions).WithConditions(opts.Conditions).WithInterval(opts.Interval.String())
if opts.ExitFirstMatch {
var waitChannel = make(chan entities.WaitReport, 1)
var waitFunction = func(ctx context.Context, nameOrId string, options *containers.WaitOptions, waitChannel chan<- entities.WaitReport) {
response := entities.WaitReport{}
exitCode, err := containers.Wait(ic.ClientCtx, nameOrId, options)
if err != nil {
if opts.Ignore && errorhandling.Contains(err, define.ErrNoSuchCtr) {
response.ExitCode = -1
} else {
response.Error = err
}
} else {
response.ExitCode = exitCode
}
select {
case <-ctx.Done():
case waitChannel <- response:
}
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for _, n := range namesOrIds {
go waitFunction(ctx, n, options, waitChannel)
}
response := <-waitChannel
responses = append(responses, response)
return responses, nil
}
for _, n := range namesOrIds { for _, n := range namesOrIds {
response := entities.WaitReport{} response := entities.WaitReport{}
exitCode, err := containers.Wait(ic.ClientCtx, n, options) exitCode, err := containers.Wait(ic.ClientCtx, n, options)

View File

@ -123,4 +123,16 @@ var _ = Describe("Podman wait", func() {
Expect(session).Should(ExitCleanly()) Expect(session).Should(ExitCleanly())
Expect(session.OutputToString()).To(Equal("-1")) Expect(session.OutputToString()).To(Equal("-1"))
}) })
It("podman wait for first return container", func() {
session1 := podmanTest.PodmanExitCleanly("run", "-d", ALPINE, "sh", "-c", "sleep 100; exit 1")
cid1 := session1.OutputToString()
session2 := podmanTest.PodmanExitCleanly("run", "-d", ALPINE, "sh", "-c", "sleep 3; exit 2")
cid2 := session2.OutputToString()
waitSession := podmanTest.PodmanExitCleanly("wait", "--exit-first-match", "--condition", "exited", cid1, cid2)
waitSession.Wait(10)
Expect(waitSession.OutputToString()).To(Equal("2"))
})
}) })