Merge pull request #9754 from mheon/add_dep

Add --requires flag to podman run/create
This commit is contained in:
OpenShift Merge Robot
2021-04-06 16:16:49 -04:00
committed by GitHub
17 changed files with 138 additions and 18 deletions

View File

@ -576,6 +576,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) {
`If a container with the same name exists, replace it`, `If a container with the same name exists, replace it`,
) )
requiresFlagName := "requires"
createFlags.StringSliceVar(
&cf.Requires,
requiresFlagName, []string{},
"Add one or more requirement containers that must be started before this container will start",
)
_ = cmd.RegisterFlagCompletionFunc(requiresFlagName, AutocompleteContainers)
restartFlagName := "restart" restartFlagName := "restart"
createFlags.StringVar( createFlags.StringVar(
&cf.Restart, &cf.Restart,

View File

@ -93,6 +93,7 @@ type ContainerCLIOpts struct {
ReadOnlyTmpFS bool ReadOnlyTmpFS bool
Restart string Restart string
Replace bool Replace bool
Requires []string
Rm bool Rm bool
RootFS bool RootFS bool
Secrets []string Secrets []string

View File

@ -486,6 +486,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
s.ReadOnlyFilesystem = c.ReadOnly s.ReadOnlyFilesystem = c.ReadOnly
s.ConmonPidFile = c.ConmonPIDFile s.ConmonPidFile = c.ConmonPIDFile
s.DependencyContainers = c.Requires
// TODO // TODO
// outside of specgen and oci though // outside of specgen and oci though
// defaults to true, check spec/storage // defaults to true, check spec/storage

View File

@ -781,6 +781,12 @@ If container is running in --read-only mode, then mount a read-write tmpfs on /r
If another container with the same name already exists, replace and remove it. The default is **false**. If another container with the same name already exists, replace and remove it. The default is **false**.
#### **\-\-requires**=**container**
Specify one or more requirements.
A requirement is a dependency container that will be started before this container.
Containers can be specified by name or ID, with multiple containers being separated by commas.
#### **\-\-restart**=*policy* #### **\-\-restart**=*policy*
Restart policy to follow when containers exit. Restart policy to follow when containers exit.
@ -1250,6 +1256,25 @@ $ podman create --tz=Asia/Shanghai alpine date
$ podman create --tz=US/Eastern alpine date $ podman create --tz=US/Eastern alpine date
``` ```
### Adding dependency containers
Podman will make sure the first container, container1, is running before the second container (container2) is started.
```
$ podman create --name container1 -t -i fedora bash
$ podman create --name container2 --requires container1 -t -i fedora bash
$ podman start --attach container2
```
Multiple containers can be required.
```
$ podman create --name container1 -t -i fedora bash
$ podman create --name container2 -t -i fedora bash
$ podman create --name container3 --requires container1,container2 -t -i fedora bash
$ podman start --attach container3
```
### Rootless Containers ### Rootless Containers
Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils
@ -1297,7 +1322,7 @@ b
NOTE: Use the environment variable `TMPDIR` to change the temporary storage location of downloaded container images. Podman defaults to use `/var/tmp`. NOTE: Use the environment variable `TMPDIR` to change the temporary storage location of downloaded container images. Podman defaults to use `/var/tmp`.
## SEE ALSO ## SEE ALSO
**podman**(1), **podman-secret**(1), **podman-save**(1), **podman-ps**(1), **podman-attach**(1), **podman-pod-create**(1), **podman-port**(1), **podman-kill**(1), **podman-stop**(1), **podman**(1), **podman-secret**(1), **podman-save**(1), **podman-ps**(1), **podman-attach**(1), **podman-pod-create**(1), **podman-port**(1), **podman-start*(1), **podman-kill**(1), **podman-stop**(1),
**podman-generate-systemd**(1) **podman-rm**(1), **subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1), **proc**(5)**. **podman-generate-systemd**(1) **podman-rm**(1), **subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1), **proc**(5)**.
## HISTORY ## HISTORY

View File

@ -825,6 +825,12 @@ If container is running in **\-\-read-only** mode, then mount a read-write tmpfs
If another container with the same name already exists, replace and remove it. The default is **false**. If another container with the same name already exists, replace and remove it. The default is **false**.
#### **\-\-requires**=**container**
Specify one or more requirements.
A requirement is a dependency container that will be started before this container.
Containers can be specified by name or ID, with multiple containers being separated by commas.
#### **\-\-restart**=*policy* #### **\-\-restart**=*policy*
Restart policy to follow when containers exit. Restart policy to follow when containers exit.
@ -1612,6 +1618,24 @@ $ podman run --tz=Asia/Shanghai alpine date
$ podman run --tz=US/Eastern alpine date $ podman run --tz=US/Eastern alpine date
``` ```
### Adding dependency containers
The first container, container1, is not started initially, but must be running before container2 will start.
The `podman run` command will start the container automatically before starting container2.
```
$ podman create --name container1 -t -i fedora bash
$ podman run --name container2 --requires container1 -t -i fedora bash
```
Multiple containers can be required.
```
$ podman create --name container1 -t -i fedora bash
$ podman create --name container2 -t -i fedora bash
$ podman run --name container3 --requires container1,container2 -t -i fedora bash
```
### Rootless Containers ### Rootless Containers
Podman runs as a non root user on most systems. This feature requires that a new enough version of **shadow-utils** Podman runs as a non root user on most systems. This feature requires that a new enough version of **shadow-utils**
@ -1657,7 +1681,7 @@ b
NOTE: Use the environment variable `TMPDIR` to change the temporary storage location of downloaded container images. Podman defaults to use `/var/tmp`. NOTE: Use the environment variable `TMPDIR` to change the temporary storage location of downloaded container images. Podman defaults to use `/var/tmp`.
## SEE ALSO ## SEE ALSO
**podman**(1), **podman-save**(1), **podman-ps**(1), **podman-attach**(1), **podman-pod-create**(1), **podman-port**(1), **podman-kill**(1), **podman-stop**(1), **podman**(1), **podman-save**(1), **podman-ps**(1), **podman-attach**(1), **podman-pod-create**(1), **podman-port**(1), **podman-start**(1), **podman-kill**(1), **podman-stop**(1),
**podman-generate-systemd**(1) **podman-rm**(1), **subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1), **proc**(5)**. **podman-generate-systemd**(1) **podman-rm**(1), **subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1), **proc**(5)**.
## HISTORY ## HISTORY

View File

@ -919,7 +919,7 @@ func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error
return err return err
} }
if len(deps) != 0 { if len(deps) != 0 {
return errors.Wrapf(define.ErrCtrExists, "container %s is a dependency of the following containers: %s", ctr.ID(), strings.Join(deps, ", ")) return errors.Wrapf(define.ErrDepExists, "container %s is a dependency of the following containers: %s", ctr.ID(), strings.Join(deps, ", "))
} }
if err := ctrBucket.DeleteBucket(ctrID); err != nil { if err := ctrBucket.DeleteBucket(ctrID); err != nil {

View File

@ -31,6 +31,10 @@ var (
// not exist. // not exist.
ErrNoSuchExecSession = errors.New("no such exec session") ErrNoSuchExecSession = errors.New("no such exec session")
// ErrDepExists indicates that the current object has dependencies and
// cannot be removed before them.
ErrDepExists = errors.New("dependency exists")
// ErrNoAliases indicates that the container does not have any network // ErrNoAliases indicates that the container does not have any network
// aliases. // aliases.
ErrNoAliases = errors.New("no aliases for container") ErrNoAliases = errors.New("no aliases for container")

View File

@ -391,7 +391,7 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error {
deps, ok := s.ctrDepends[ctr.ID()] deps, ok := s.ctrDepends[ctr.ID()]
if ok && len(deps) != 0 { if ok && len(deps) != 0 {
depsStr := strings.Join(deps, ", ") depsStr := strings.Join(deps, ", ")
return errors.Wrapf(define.ErrCtrExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr) return errors.Wrapf(define.ErrDepExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr)
} }
// Ensure we don't have active exec sessions // Ensure we don't have active exec sessions
@ -1497,7 +1497,7 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
deps, ok := s.ctrDepends[ctr.ID()] deps, ok := s.ctrDepends[ctr.ID()]
if ok && len(deps) != 0 { if ok && len(deps) != 0 {
depsStr := strings.Join(deps, ", ") depsStr := strings.Join(deps, ", ")
return errors.Wrapf(define.ErrCtrExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr) return errors.Wrapf(define.ErrDepExists, "the following containers depend on container %s: %s", ctr.ID(), depsStr)
} }
// Ensure we don't have active exec sessions // Ensure we don't have active exec sessions

View File

@ -42,7 +42,7 @@ func StartContainer(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusNotModified, nil) utils.WriteResponse(w, http.StatusNotModified, nil)
return return
} }
if err := con.Start(r.Context(), len(con.PodID()) > 0); err != nil { if err := con.Start(r.Context(), true); err != nil {
utils.InternalServerError(w, err) utils.InternalServerError(w, err)
return return
} }

View File

@ -154,6 +154,7 @@ type RestartOptions struct {
// StartOptions are optional options for starting containers // StartOptions are optional options for starting containers
type StartOptions struct { type StartOptions struct {
DetachKeys *string DetachKeys *string
Recursive *bool
} }
//go:generate go run ../generator/generator.go StatsOptions //go:generate go run ../generator/generator.go StatsOptions

View File

@ -35,3 +35,19 @@ func (o *StartOptions) GetDetachKeys() string {
} }
return *o.DetachKeys return *o.DetachKeys
} }
// WithRecursive
func (o *StartOptions) WithRecursive(value bool) *StartOptions {
v := &value
o.Recursive = v
return o
}
// GetRecursive
func (o *StartOptions) GetRecursive() bool {
var recursive bool
if o.Recursive == nil {
return recursive
}
return *o.Recursive
}

View File

@ -585,7 +585,7 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrID string,
} }
// If the container is in a pod, also set to recursively start dependencies // If the container is in a pod, also set to recursively start dependencies
err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != "") err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false)
if err != nil && errors.Cause(err) != define.ErrDetach { if err != nil && errors.Cause(err) != define.ErrDetach {
return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
} }
@ -708,7 +708,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
ctrRunning := ctrState == define.ContainerStateRunning ctrRunning := ctrState == define.ContainerStateRunning
if options.Attach { if options.Attach {
err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, !ctrRunning, ctr.PodID() != "") err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, !ctrRunning)
if errors.Cause(err) == define.ErrDetach { if errors.Cause(err) == define.ErrDetach {
// User manually detached // User manually detached
// Exit cleanly immediately // Exit cleanly immediately
@ -784,7 +784,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri
RawInput: rawInput, RawInput: rawInput,
ExitCode: 125, ExitCode: 125,
} }
if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { if err := ctr.Start(ctx, true); err != nil {
// if lastError != nil { // if lastError != nil {
// fmt.Fprintln(os.Stderr, lastError) // fmt.Fprintln(os.Stderr, lastError)
// } // }
@ -845,10 +845,6 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
} }
} }
var joinPod bool
if len(ctr.PodID()) > 0 {
joinPod = true
}
report := entities.ContainerRunReport{Id: ctr.ID()} report := entities.ContainerRunReport{Id: ctr.ID()}
if logrus.GetLevel() == logrus.DebugLevel { if logrus.GetLevel() == logrus.DebugLevel {
@ -859,7 +855,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
} }
if opts.Detach { if opts.Detach {
// if the container was created as part of a pod, also start its dependencies, if any. // if the container was created as part of a pod, also start its dependencies, if any.
if err := ctr.Start(ctx, joinPod); err != nil { if err := ctr.Start(ctx, true); err != nil {
// This means the command did not exist // This means the command did not exist
report.ExitCode = define.ExitCode(err) report.ExitCode = define.ExitCode(err)
return &report, err return &report, err
@ -869,7 +865,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
} }
// if the container was created as part of a pod, also start its dependencies, if any. // if the container was created as part of a pod, also start its dependencies, if any.
if err := terminal.StartAttachCtr(ctx, ctr, opts.OutputStream, opts.ErrorStream, opts.InputStream, opts.DetachKeys, opts.SigProxy, true, joinPod); err != nil { if err := terminal.StartAttachCtr(ctx, ctr, opts.OutputStream, opts.ErrorStream, opts.InputStream, opts.DetachKeys, opts.SigProxy, true); err != nil {
// We've manually detached from the container // We've manually detached from the container
// Do not perform cleanup, or wait for container exit code // Do not perform cleanup, or wait for container exit code
// Just exit immediately // Just exit immediately

View File

@ -39,7 +39,7 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpo
// StartAttachCtr starts and (if required) attaches to a container // StartAttachCtr starts and (if required) attaches to a container
// if you change the signature of this function from os.File to io.Writer, it will trigger a downstream // if you change the signature of this function from os.File to io.Writer, it will trigger a downstream
// error. we may need to just lint disable this one. // error. we may need to just lint disable this one.
func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { //nolint-interfacer func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool) error { //nolint-interfacer
resize := make(chan define.TerminalSize) resize := make(chan define.TerminalSize)
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
@ -88,7 +88,7 @@ func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr,
return ctr.Attach(streams, detachKeys, resize) return ctr.Attach(streams, detachKeys, resize)
} }
attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, recursive) attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, true)
if err != nil { if err != nil {
return err return err
} }

View File

@ -629,7 +629,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
if opts.Detach { if opts.Detach {
// Detach and return early // Detach and return early
err := containers.Start(ic.ClientCtx, con.ID, nil) err := containers.Start(ic.ClientCtx, con.ID, new(containers.StartOptions).WithRecursive(true))
if err != nil { if err != nil {
report.ExitCode = define.ExitCode(err) report.ExitCode = define.ExitCode(err)
} }

View File

@ -364,6 +364,17 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
if len(s.Secrets) != 0 { if len(s.Secrets) != 0 {
options = append(options, libpod.WithSecrets(s.Secrets)) options = append(options, libpod.WithSecrets(s.Secrets))
} }
if len(s.DependencyContainers) > 0 {
deps := make([]*libpod.Container, 0, len(s.DependencyContainers))
for _, ctr := range s.DependencyContainers {
depCtr, err := rt.LookupContainer(ctr)
if err != nil {
return nil, errors.Wrapf(err, "%q is not a valid container, cannot be used as a dependency", ctr)
}
deps = append(deps, depCtr)
}
options = append(options, libpod.WithDependencyCtrs(deps))
}
return options, nil return options, nil
} }

View File

@ -160,10 +160,17 @@ type ContainerBasicConfig struct {
// to 0, 1, 2) that will be passed to the executed process. The total FDs // to 0, 1, 2) that will be passed to the executed process. The total FDs
// passed will be 3 + PreserveFDs. // passed will be 3 + PreserveFDs.
// set tags as `json:"-"` for not supported remote // set tags as `json:"-"` for not supported remote
// Optional.
PreserveFDs uint `json:"-"` PreserveFDs uint `json:"-"`
// Timezone is the timezone inside the container. // Timezone is the timezone inside the container.
// Local means it has the same timezone as the host machine // Local means it has the same timezone as the host machine
// Optional.
Timezone string `json:"timezone,omitempty"` Timezone string `json:"timezone,omitempty"`
// DependencyContainers is an array of containers this container
// depends on. Dependency containers must be started before this
// container. Dependencies can be specified by name or full/partial ID.
// Optional.
DependencyContainers []string `json:"dependencyContainers,omitempty"`
} }
// ContainerStorageConfig contains information on the storage configuration of a // ContainerStorageConfig contains information on the storage configuration of a

View File

@ -1588,4 +1588,29 @@ WORKDIR /madethis`, BB)
Expect(session.OutputToString()).To(ContainSubstring("mysecret")) Expect(session.OutputToString()).To(ContainSubstring("mysecret"))
}) })
It("podman run --requires", func() {
depName := "ctr1"
depContainer := podmanTest.Podman([]string{"create", "--name", depName, ALPINE, "top"})
depContainer.WaitWithDefaultTimeout()
Expect(depContainer.ExitCode()).To(Equal(0))
mainName := "ctr2"
mainContainer := podmanTest.Podman([]string{"run", "--name", mainName, "--requires", depName, "-d", ALPINE, "top"})
mainContainer.WaitWithDefaultTimeout()
Expect(mainContainer.ExitCode()).To(Equal(0))
stop := podmanTest.Podman([]string{"stop", "--all"})
stop.WaitWithDefaultTimeout()
Expect(stop.ExitCode()).To(Equal(0))
start := podmanTest.Podman([]string{"start", mainName})
start.WaitWithDefaultTimeout()
Expect(start.ExitCode()).To(Equal(0))
running := podmanTest.Podman([]string{"ps", "-q"})
running.WaitWithDefaultTimeout()
Expect(running.ExitCode()).To(Equal(0))
Expect(len(running.OutputToStringArray())).To(Equal(2))
})
}) })