diff options
63 files changed, 1618 insertions, 831 deletions
diff --git a/cmd/podman/containers/cleanup.go b/cmd/podman/containers/cleanup.go index 2bcd1c1e9..619031208 100644 --- a/cmd/podman/containers/cleanup.go +++ b/cmd/podman/containers/cleanup.go @@ -7,6 +7,8 @@ import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/utils" "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -43,6 +45,7 @@ func init() { flags := cleanupCommand.Flags() flags.BoolVarP(&cleanupOptions.All, "all", "a", false, "Cleans up all containers") flags.BoolVarP(&cleanupOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.StringVar(&cleanupOptions.Exec, "exec", "", "Clean up the given exec session instead of the container") flags.BoolVar(&cleanupOptions.Remove, "rm", false, "After cleanup, remove the container entirely") flags.BoolVar(&cleanupOptions.RemoveImage, "rmi", false, "After cleanup, remove the image entirely") @@ -52,8 +55,26 @@ func cleanup(cmd *cobra.Command, args []string) error { var ( errs utils.OutputErrors ) + + if cleanupOptions.Exec != "" { + switch { + case cleanupOptions.All: + return errors.Errorf("exec and all options conflict") + case len(args) > 1: + return errors.Errorf("cannot use exec option when more than one container is given") + case cleanupOptions.RemoveImage: + return errors.Errorf("exec and rmi options conflict") + } + } + responses, err := registry.ContainerEngine().ContainerCleanup(registry.GetContext(), args, cleanupOptions) if err != nil { + // `podman container cleanup` is almost always run in the + // background. Our only way of relaying information to the user + // is via syslog. + // As such, we need to logrus.Errorf our errors to ensure they + // are properly printed if --syslog is set. + logrus.Errorf("Error running container cleanup: %v", err) return err } for _, r := range responses { @@ -62,12 +83,15 @@ func cleanup(cmd *cobra.Command, args []string) error { continue } if r.RmErr != nil { + logrus.Errorf("Error removing container: %v", r.RmErr) errs = append(errs, r.RmErr) } if r.RmiErr != nil { + logrus.Errorf("Error removing image: %v", r.RmiErr) errs = append(errs, r.RmiErr) } if r.CleanErr != nil { + logrus.Errorf("Error cleaning up container: %v", r.CleanErr) errs = append(errs, r.CleanErr) } } diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index 0992b3862..7554d6a93 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -2,9 +2,11 @@ package containers import ( "bufio" + "fmt" "os" "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" envLib "github.com/containers/libpod/pkg/env" "github.com/pkg/errors" @@ -41,10 +43,12 @@ var ( var ( envInput, envFile []string execOpts entities.ExecOptions + execDetach bool ) func execFlags(flags *pflag.FlagSet) { flags.SetInterspersed(false) + flags.BoolVarP(&execDetach, "detach", "d", false, "Run the exec session in detached mode (backgrounded)") flags.StringVar(&execOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") flags.StringArrayVarP(&envInput, "env", "e", []string{}, "Set environment variables") flags.StringSliceVar(&envFile, "env-file", []string{}, "Read in a file of environment variables") @@ -106,16 +110,27 @@ func exec(cmd *cobra.Command, args []string) error { } execOpts.Envs = envLib.Join(execOpts.Envs, cliEnv) - execOpts.Streams.OutputStream = os.Stdout - execOpts.Streams.ErrorStream = os.Stderr - if execOpts.Interactive { - execOpts.Streams.InputStream = bufio.NewReader(os.Stdin) - execOpts.Streams.AttachInput = true + + if !execDetach { + streams := define.AttachStreams{} + streams.OutputStream = os.Stdout + streams.ErrorStream = os.Stderr + if execOpts.Interactive { + streams.InputStream = bufio.NewReader(os.Stdin) + streams.AttachInput = true + } + streams.AttachOutput = true + streams.AttachError = true + + exitCode, err := registry.ContainerEngine().ContainerExec(registry.GetContext(), nameOrId, execOpts, streams) + registry.SetExitCode(exitCode) + return err } - execOpts.Streams.AttachOutput = true - execOpts.Streams.AttachError = true - exitCode, err := registry.ContainerEngine().ContainerExec(registry.GetContext(), nameOrId, execOpts) - registry.SetExitCode(exitCode) - return err + id, err := registry.ContainerEngine().ContainerExecDetached(registry.GetContext(), nameOrId, execOpts) + if err != nil { + return err + } + fmt.Println(id) + return nil } diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index 83c039ed3..022c90f71 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -85,7 +85,7 @@ func images(cmd *cobra.Command, args []string) error { return errors.New("cannot specify an image and a filter(s)") } - if len(listOptions.Filter) < 1 && len(args) > 0 { + if len(args) > 0 { listOptions.Filter = append(listOptions.Filter, "reference="+args[0]) } @@ -152,10 +152,16 @@ func writeTemplate(imageS []*entities.ImageSummary) error { ) imgs := make([]imageReporter, 0, len(imageS)) for _, e := range imageS { - for _, tag := range e.RepoTags { - var h imageReporter + var h imageReporter + if len(e.RepoTags) > 0 { + for _, tag := range e.RepoTags { + h.ImageSummary = *e + h.Repository, h.Tag = tokenRepoTag(tag) + imgs = append(imgs, h) + } + } else { h.ImageSummary = *e - h.Repository, h.Tag = tokenRepoTag(tag) + h.Repository = "<none>" imgs = append(imgs, h) } listFlag.readOnly = e.IsReadOnly() diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index 7c9e3eb61..676382a99 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -61,12 +61,6 @@ Are you sure you want to continue? [y/N] `) } } - // TODO Remove once filter refactor is finished and url.Values rules :) - for _, f := range filter { - t := strings.SplitN(f, "=", 2) - pruneOpts.Filters.Add(t[0], t[1]) - } - results, err := registry.ImageEngine().Prune(registry.GetContext(), pruneOpts) if err != nil { return err diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index e24cdef98..88b615fab 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -3,6 +3,7 @@ package pods import ( "context" "fmt" + "io/ioutil" "os" "strings" @@ -147,6 +148,11 @@ func create(cmd *cobra.Command, args []string) error { if err != nil { return err } + if len(podIDFile) > 0 { + if err = ioutil.WriteFile(podIDFile, []byte(response.Id), 0644); err != nil { + return errors.Wrapf(err, "failed to write pod ID to file %q", podIDFile) + } + } fmt.Println(response.Id) return nil } diff --git a/docs/source/markdown/podman-container-cleanup.1.md b/docs/source/markdown/podman-container-cleanup.1.md index 66a6cff62..a200c2c36 100644 --- a/docs/source/markdown/podman-container-cleanup.1.md +++ b/docs/source/markdown/podman-container-cleanup.1.md @@ -16,6 +16,13 @@ Sometimes container's mount points and network stacks can remain if the podman c Cleanup all containers. +**--exec**=_session_ + +Clean up an exec session for a single container. +Can only be specified if a single container is being cleaned up (conflicts with **--all** as such). +If **--rm** is not specified, temporary files for the exec session will be cleaned up; if it is, the exec session will be removed from the container. +Conflicts with **--rmi** as the container is not being cleaned up so the image cannot be removed. + **--latest**, **-l** Instead of providing the container name or ID, use the last created container. If you use methods other than Podman to run containers such as CRI-O, the last started container could be from either of those methods. diff --git a/docs/source/markdown/podman-exec.1.md b/docs/source/markdown/podman-exec.1.md index 1bd10f9ba..b24a1f8aa 100644 --- a/docs/source/markdown/podman-exec.1.md +++ b/docs/source/markdown/podman-exec.1.md @@ -13,6 +13,10 @@ podman\-exec - Execute a command in a running container ## OPTIONS +**--detach** + +Start the exec session, but do not attach to it. The command will run in the background and the exec session will be automatically removed when it completes. The **podman exec** command will print the ID of the exec session and exit immediately after it starts. + **--detach-keys**=*sequence* Specify the key sequence for detaching a container. Format is a single character `[a-Z]` or one or more `ctrl-<value>` characters where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. Specifying "" will disable this feature. The default is *ctrl-p,ctrl-q*. diff --git a/docs/tutorials/podman-derivative-api.md b/docs/tutorials/podman-derivative-api.md index 065b0c4a9..8a1f40fc0 100644 --- a/docs/tutorials/podman-derivative-api.md +++ b/docs/tutorials/podman-derivative-api.md @@ -4,6 +4,20 @@ libpod today is a Golang library and a CLI. The choice of interface you make has advantages and disadvantages. +Using the REST API +--- + +Advantages: + + - Stable, versioned API + - Language-agnostic + - [Well-documented](http://docs.podman.io/en/latest/_static/api.html) API + +Disadvantages: + + - Error handling is less verbose than Golang API + - May be slower + Running as a subprocess --- @@ -35,12 +49,12 @@ Disadvantages: Varlink --- -Some code exists for this; splits the difference. Future uncertain. +The Varlink API is presently deprecated. We do not recommend adopting it for new projects. Making the choice --- A good question to ask first is: Do you want users to be able to use `podman` to manipulate the containers created by your project? -If so, that makes it more likely that you want to run `podman` as a subprocess. If you want a separate image store and a fundamentally +If so, that makes it more likely that you want to run `podman` as a subprocess or using the HTTP API. If you want a separate image store and a fundamentally different experience; if what you're doing with containers is quite different from those created by the `podman` CLI, that may drive you towards vendoring. @@ -40,14 +40,14 @@ require ( github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 github.com/opencontainers/runc v1.0.0-rc9 - github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7 + github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 github.com/opencontainers/runtime-tools v0.9.0 github.com/opencontainers/selinux v1.5.1 github.com/opentracing/opentracing-go v1.1.0 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 github.com/rootless-containers/rootlesskit v0.9.4 - github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f + github.com/seccomp/containers-golang v0.4.1 github.com/sirupsen/logrus v1.6.0 github.com/spf13/cobra v0.0.7 github.com/spf13/pflag v1.0.5 @@ -375,9 +375,12 @@ github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rm github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7 h1:Dliu5QO+4JYWu/yMshaMU7G3JN2POGpwjJN7gjy10Go= github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 h1:9mv9SC7GWmRWE0J/+oD8w3GsN2KYGKtg6uwLN7hfP5E= +github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/runtime-tools v0.9.0 h1:FYgwVsKRI/H9hU32MJ/4MLOzXWodKK5zsQavY8NPMkU= github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= github.com/opencontainers/selinux v1.4.0/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.5.1 h1:jskKwSMFYqyTrHEuJgQoUlTcId0av64S6EWObrIfn5Y= github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= @@ -435,6 +438,8 @@ github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 h1:2c1EFnZHIPCW8q github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f h1:OtU/w6sBKmXYaw2KEODxjcYi3oPSyyslhgGFgIJVGAI= github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f/go.mod h1:f/98/SnvAzhAEFQJ3u836FePXvcbE8BS0YGMQNn4mhA= +github.com/seccomp/containers-golang v0.4.1 h1:6hsmsP8Y9T6PWKJELqAkRWkc6Te60+zK64avkjInd44= +github.com/seccomp/containers-golang v0.4.1/go.mod h1:5fP9lgyYyklJ8fg8Geq193G1QLe0ikf34z+hZKIjmnE= github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -596,6 +601,7 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190921190940-14da1ac737cc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/libpod/container_exec.go b/libpod/container_exec.go index 6ad767b4b..f2943b73c 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -62,6 +62,13 @@ type ExecConfig struct { // given is the number that will be passed into the exec session, // starting at 3. PreserveFDs uint `json:"preserveFds,omitempty"` + // ExitCommand is the exec session's exit command. + // This command will be executed when the exec session exits. + // If unset, no command will be executed. + // Two arguments will be appended to the exit command by Libpod: + // The ID of the exec session, and the ID of the container the exec + // session is a part of (in that order). + ExitCommand []string `json:"exitCommand,omitempty"` } // ExecSession contains information on a single exec session attached to a given @@ -191,6 +198,10 @@ func (c *Container) ExecCreate(config *ExecConfig) (string, error) { return "", errors.Wrapf(err, "error copying exec configuration into exec session") } + if len(session.Config.ExitCommand) > 0 { + session.Config.ExitCommand = append(session.Config.ExitCommand, []string{session.ID(), c.ID()}...) + } + if c.state.ExecSessions == nil { c.state.ExecSessions = make(map[string]*ExecSession) } @@ -210,11 +221,52 @@ func (c *Container) ExecCreate(config *ExecConfig) (string, error) { } // ExecStart starts an exec session in the container, but does not attach to it. -// Returns immediately upon starting the exec session. +// Returns immediately upon starting the exec session, unlike other ExecStart +// functions, which will only return when the exec session exits. func (c *Container) ExecStart(sessionID string) error { - // Will be implemented in part 2, migrating Start and implementing - // detached Start. - return define.ErrNotImplemented + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + // 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) + } + + if session.State != define.ExecStateCreated { + return errors.Wrapf(define.ErrExecSessionStateInvalid, "can only start created exec sessions, while container %s session %s state is %q", c.ID(), session.ID(), session.State.String()) + } + + logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID()) + + opts, err := prepareForExec(c, session) + if err != nil { + return err + } + + pid, err := c.ociRuntime.ExecContainerDetached(c, session.ID(), opts, session.Config.AttachStdin) + if err != nil { + return err + } + + c.newContainerEvent(events.Exec) + logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID()) + + // Update and save session to reflect PID/running + session.PID = pid + session.State = define.ExecStateRunning + + return c.save() } // ExecStartAndAttach starts and attaches to an exec session in a container. @@ -511,7 +563,27 @@ func (c *Container) ExecCleanup(sessionID string) error { } if session.State == define.ExecStateRunning { - return errors.Wrapf(define.ErrExecSessionStateInvalid, "cannot clean up container %s exec session %s as it is running", c.ID(), session.ID()) + // Check if the exec session is still running. + alive, err := c.ociRuntime.ExecUpdateStatus(c, session.ID()) + if err != nil { + return err + } + + if alive { + return errors.Wrapf(define.ErrExecSessionStateInvalid, "cannot clean up container %s exec session %s as it is running", c.ID(), session.ID()) + } + + exitCode, err := c.readExecExitCode(session.ID()) + if err != nil { + return err + } + session.ExitCode = exitCode + session.PID = 0 + session.State = define.ExecStateStopped + + if err := c.save(); err != nil { + return err + } } logrus.Infof("Cleaning up container %s exec session %s", c.ID(), session.ID()) @@ -541,11 +613,11 @@ func (c *Container) ExecRemove(sessionID string, force bool) error { // Update status of exec session if running, so we cna check if it // stopped in the meantime. if session.State == define.ExecStateRunning { - stopped, err := c.ociRuntime.ExecUpdateStatus(c, session.ID()) + running, err := c.ociRuntime.ExecUpdateStatus(c, session.ID()) if err != nil { return err } - if stopped { + if !running { session.State = define.ExecStateStopped // TODO: should we retrieve exit code here? // TODO: Might be worth saving state here. @@ -800,13 +872,6 @@ func (c *Container) getActiveExecSessions() ([]string, error) { continue } if !alive { - if err := c.cleanupExecBundle(id); err != nil { - if lastErr != nil { - logrus.Errorf("Error checking container %s exec sessions: %v", c.ID(), lastErr) - } - lastErr = err - } - _, isLegacy := c.state.LegacyExecSessions[id] if isLegacy { delete(c.state.LegacyExecSessions, id) @@ -826,6 +891,12 @@ func (c *Container) getActiveExecSessions() ([]string, error) { needSave = true } + if err := c.cleanupExecBundle(id); err != nil { + if lastErr != nil { + logrus.Errorf("Error checking container %s exec sessions: %v", c.ID(), lastErr) + } + lastErr = err + } } else { activeSessions = append(activeSessions, id) } @@ -846,6 +917,8 @@ func (c *Container) getActiveExecSessions() ([]string, error) { func (c *Container) removeAllExecSessions() error { knownSessions := c.getKnownExecSessions() + logrus.Debugf("Removing all exec sessions for container %s", c.ID()) + var lastErr error for _, id := range knownSessions { if err := c.ociRuntime.ExecStopContainer(c, id, c.StopTimeout()); err != nil { @@ -910,6 +983,7 @@ func prepareForExec(c *Container, session *ExecSession) (*ExecOptions, error) { opts.User = user opts.PreserveFDs = session.Config.PreserveFDs opts.DetachKeys = session.Config.DetachKeys + opts.ExitCommand = session.Config.ExitCommand return opts, nil } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 909ad9851..43e873bd6 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1169,7 +1169,7 @@ func (c *Container) start() error { c.state.State = define.ContainerStateRunning if c.config.HealthCheckConfig != nil { - if err := c.updateHealthStatus(HealthCheckStarting); err != nil { + if err := c.updateHealthStatus(define.HealthCheckStarting); err != nil { logrus.Error(err) } if err := c.startTimer(); err != nil { diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 8ee0fb456..2bd6099f0 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1236,7 +1236,7 @@ func (c *Container) makeBindMounts() error { } // Add Secret Mounts - secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.Containers.DefaultMountsFile, c.state.RunDir, c.RootUID(), c.RootGID(), rootless.IsRootless(), false) + secretMounts := secrets.SecretMountsWithUIDGID(c.config.MountLabel, c.state.RunDir, c.runtime.config.Containers.DefaultMountsFile, c.state.Mountpoint, c.RootUID(), c.RootGID(), rootless.IsRootless(), false) for _, mount := range secretMounts { if _, ok := c.state.BindMounts[mount.Destination]; !ok { c.state.BindMounts[mount.Destination] = mount.Source diff --git a/libpod/container_internal_test.go b/libpod/container_internal_test.go index 5428504ef..fdf7c2e20 100644 --- a/libpod/container_internal_test.go +++ b/libpod/container_internal_test.go @@ -60,7 +60,7 @@ func TestPostDeleteHooks(t *testing.T) { t.Fatal(err) } - stateRegexp := `{"ociVersion":"1\.0\.1-dev","id":"123abc","status":"stopped","bundle":"` + strings.TrimSuffix(os.TempDir(), "/") + `/libpod_test_[0-9]*","annotations":{"a":"b"}}` + stateRegexp := `{"ociVersion":"1\.0\.2-dev","id":"123abc","status":"stopped","bundle":"` + strings.TrimSuffix(os.TempDir(), "/") + `/libpod_test_[0-9]*","annotations":{"a":"b"}}` for _, p := range []string{statePath, copyPath} { path := p t.Run(path, func(t *testing.T) { diff --git a/libpod/define/healthchecks.go b/libpod/define/healthchecks.go new file mode 100644 index 000000000..4114262b6 --- /dev/null +++ b/libpod/define/healthchecks.go @@ -0,0 +1,36 @@ +package define + +const ( + // HealthCheckHealthy describes a healthy container + HealthCheckHealthy string = "healthy" + // HealthCheckUnhealthy describes an unhealthy container + HealthCheckUnhealthy string = "unhealthy" + // HealthCheckStarting describes the time between when the container starts + // and the start-period (time allowed for the container to start and application + // to be running) expires. + HealthCheckStarting string = "starting" +) + +// HealthCheckStatus represents the current state of a container +type HealthCheckStatus int + +const ( + // HealthCheckSuccess means the health worked + HealthCheckSuccess HealthCheckStatus = iota + // HealthCheckFailure means the health ran and failed + HealthCheckFailure HealthCheckStatus = iota + // HealthCheckContainerStopped means the health check cannot + // be run because the container is stopped + HealthCheckContainerStopped HealthCheckStatus = iota + // HealthCheckContainerNotFound means the container could + // not be found in local store + HealthCheckContainerNotFound HealthCheckStatus = iota + // HealthCheckNotDefined means the container has no health + // check defined in it + HealthCheckNotDefined HealthCheckStatus = iota + // HealthCheckInternalError means some something failed obtaining or running + // a given health check + HealthCheckInternalError HealthCheckStatus = iota + // HealthCheckDefined means the healthcheck was found on the container + HealthCheckDefined HealthCheckStatus = iota +) diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go index aec5fa4e0..0006b7c06 100644 --- a/libpod/healthcheck.go +++ b/libpod/healthcheck.go @@ -14,43 +14,12 @@ import ( "github.com/sirupsen/logrus" ) -// HealthCheckStatus represents the current state of a container -type HealthCheckStatus int - const ( - // HealthCheckSuccess means the health worked - HealthCheckSuccess HealthCheckStatus = iota - // HealthCheckFailure means the health ran and failed - HealthCheckFailure HealthCheckStatus = iota - // HealthCheckContainerStopped means the health check cannot - // be run because the container is stopped - HealthCheckContainerStopped HealthCheckStatus = iota - // HealthCheckContainerNotFound means the container could - // not be found in local store - HealthCheckContainerNotFound HealthCheckStatus = iota - // HealthCheckNotDefined means the container has no health - // check defined in it - HealthCheckNotDefined HealthCheckStatus = iota - // HealthCheckInternalError means some something failed obtaining or running - // a given health check - HealthCheckInternalError HealthCheckStatus = iota - // HealthCheckDefined means the healthcheck was found on the container - HealthCheckDefined HealthCheckStatus = iota - // MaxHealthCheckNumberLogs is the maximum number of attempts we keep // in the healthcheck history file MaxHealthCheckNumberLogs int = 5 // MaxHealthCheckLogLength in characters MaxHealthCheckLogLength = 500 - - // HealthCheckHealthy describes a healthy container - HealthCheckHealthy string = "healthy" - // HealthCheckUnhealthy describes an unhealthy container - HealthCheckUnhealthy string = "unhealthy" - // HealthCheckStarting describes the time between when the container starts - // and the start-period (time allowed for the container to start and application - // to be running) expires. - HealthCheckStarting string = "starting" ) // hcWriteCloser allows us to use bufio as a WriteCloser @@ -65,10 +34,10 @@ func (hcwc hcWriteCloser) Close() error { // HealthCheck verifies the state and validity of the healthcheck configuration // on the container and then executes the healthcheck -func (r *Runtime) HealthCheck(name string) (HealthCheckStatus, error) { +func (r *Runtime) HealthCheck(name string) (define.HealthCheckStatus, error) { container, err := r.LookupContainer(name) if err != nil { - return HealthCheckContainerNotFound, errors.Wrapf(err, "unable to lookup %s to perform a health check", name) + return define.HealthCheckContainerNotFound, errors.Wrapf(err, "unable to lookup %s to perform a health check", name) } hcStatus, err := checkHealthCheckCanBeRun(container) if err == nil { @@ -78,7 +47,7 @@ func (r *Runtime) HealthCheck(name string) (HealthCheckStatus, error) { } // runHealthCheck runs the health check as defined by the container -func (c *Container) runHealthCheck() (HealthCheckStatus, error) { +func (c *Container) runHealthCheck() (define.HealthCheckStatus, error) { var ( newCommand []string returnCode int @@ -87,11 +56,11 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) { ) hcCommand := c.HealthCheckConfig().Test if len(hcCommand) < 1 { - return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) + return define.HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) } switch hcCommand[0] { case "", "NONE": - return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) + return define.HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) case "CMD": newCommand = hcCommand[1:] case "CMD-SHELL": @@ -102,7 +71,7 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) { newCommand = hcCommand } if len(newCommand) < 1 || newCommand[0] == "" { - return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) + return define.HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) } captureBuffer := bufio.NewWriter(&capture) hcw := hcWriteCloser{ @@ -120,13 +89,13 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) { logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID()) timeStart := time.Now() - hcResult := HealthCheckSuccess + hcResult := define.HealthCheckSuccess config := new(ExecConfig) config.Command = newCommand _, hcErr := c.Exec(config, streams, nil) if hcErr != nil { errCause := errors.Cause(hcErr) - hcResult = HealthCheckFailure + hcResult = define.HealthCheckFailure if errCause == define.ErrOCIRuntimeNotFound || errCause == define.ErrOCIRuntimePermissionDenied || errCause == define.ErrOCIRuntime { @@ -154,7 +123,7 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) { if timeEnd.Sub(timeStart) > c.HealthCheckConfig().Timeout { returnCode = -1 - hcResult = HealthCheckFailure + hcResult = define.HealthCheckFailure hcErr = errors.Errorf("healthcheck command exceeded timeout of %s", c.HealthCheckConfig().Timeout.String()) } hcl := newHealthCheckLog(timeStart, timeEnd, returnCode, eventLog) @@ -164,18 +133,18 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) { return hcResult, hcErr } -func checkHealthCheckCanBeRun(c *Container) (HealthCheckStatus, error) { +func checkHealthCheckCanBeRun(c *Container) (define.HealthCheckStatus, error) { cstate, err := c.State() if err != nil { - return HealthCheckInternalError, err + return define.HealthCheckInternalError, err } if cstate != define.ContainerStateRunning { - return HealthCheckContainerStopped, errors.Errorf("container %s is not running", c.ID()) + return define.HealthCheckContainerStopped, errors.Errorf("container %s is not running", c.ID()) } if !c.HasHealthCheck() { - return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) + return define.HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) } - return HealthCheckDefined, nil + return define.HealthCheckDefined, nil } func newHealthCheckLog(start, end time.Time, exitCode int, log string) define.HealthCheckLog { @@ -210,18 +179,18 @@ func (c *Container) updateHealthCheckLog(hcl define.HealthCheckLog, inStartPerio } if hcl.ExitCode == 0 { // set status to healthy, reset failing state to 0 - healthCheck.Status = HealthCheckHealthy + healthCheck.Status = define.HealthCheckHealthy healthCheck.FailingStreak = 0 } else { if len(healthCheck.Status) < 1 { - healthCheck.Status = HealthCheckHealthy + healthCheck.Status = define.HealthCheckHealthy } if !inStartPeriod { // increment failing streak healthCheck.FailingStreak += 1 // if failing streak > retries, then status to unhealthy if healthCheck.FailingStreak >= c.HealthCheckConfig().Retries { - healthCheck.Status = HealthCheckUnhealthy + healthCheck.Status = define.HealthCheckUnhealthy } } } diff --git a/libpod/image/filters.go b/libpod/image/filters.go index 8ca3526a0..747eba165 100644 --- a/libpod/image/filters.go +++ b/libpod/image/filters.go @@ -170,8 +170,7 @@ func (ir *Runtime) createFilterFuncs(filters []string, img *Image) ([]ResultFilt labelFilter := strings.Join(splitFilter[1:], "=") filterFuncs = append(filterFuncs, LabelFilter(ctx, labelFilter)) case "reference": - referenceFilter := strings.Join(splitFilter[1:], "=") - filterFuncs = append(filterFuncs, ReferenceFilter(ctx, referenceFilter)) + filterFuncs = append(filterFuncs, ReferenceFilter(ctx, splitFilter[1])) case "id": filterFuncs = append(filterFuncs, IdFilter(splitFilter[1])) default: diff --git a/libpod/oci.go b/libpod/oci.go index 6b1886f80..7c5218319 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -68,10 +68,10 @@ type OCIRuntime interface { AttachResize(ctr *Container, newSize remotecommand.TerminalSize) error // ExecContainer executes a command in a running container. - // Returns an int (exit code), error channel (errors from attach), and - // error (errors that occurred attempting to start the exec session). - // This returns once the exec session is running - not once it has - // completed, as one might expect. The attach session will remain + // Returns an int (PID of exec session), error channel (errors from + // attach), and error (errors that occurred attempting to start the exec + // session). This returns once the exec session is running - not once it + // has completed, as one might expect. The attach session will remain // running, in a goroutine that will return via the chan error in the // return signature. ExecContainer(ctr *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams) (int, chan error, error) @@ -81,6 +81,10 @@ type OCIRuntime interface { // start, with a goroutine running in the background to handle attach). // The HTTP attach itself maintains the same invariants as HTTPAttach. ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, httpConn net.Conn, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, cancel <-chan bool) (int, chan error, error) + // ExecContainerDetached executes a command in a running container, but + // does not attach to it. Returns the PID of the exec session and an + // error (if starting the exec session failed) + ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) // ExecAttachResize resizes the terminal of a running exec session. Only // allowed with sessions that were created with a TTY. ExecAttachResize(ctr *Container, sessionID string, newSize remotecommand.TerminalSize) error @@ -165,6 +169,9 @@ type ExecOptions struct { // If provided but set to "", detaching from the container will be // disabled. DetachKeys *string + // ExitCommand is a command that will be run after the exec session + // exits. + ExitCommand []string } // HTTPAttachStreams informs the HTTPAttach endpoint which of the container's diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go new file mode 100644 index 000000000..51819f90a --- /dev/null +++ b/libpod/oci_conmon_exec_linux.go @@ -0,0 +1,599 @@ +package libpod + +import ( + "bufio" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "syscall" + "time" + + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/errorhandling" + "github.com/containers/libpod/pkg/util" + "github.com/containers/libpod/utils" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" + "k8s.io/client-go/tools/remotecommand" +) + +// ExecContainer executes a command in a running container +func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams) (int, chan error, error) { + if options == nil { + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") + } + if len(options.Cmd) == 0 { + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") + } + + if sessionID == "" { + return -1, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") + } + + // TODO: Should we default this to false? + // Or maybe make streams mandatory? + attachStdin := true + if streams != nil { + attachStdin = streams.AttachInput + } + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = c.execOCILog(sessionID) + } + + execCmd, pipes, err := r.startExec(c, sessionID, options, attachStdin, ociLog) + if err != nil { + return -1, nil, err + } + + // Only close sync pipe. Start and attach are consumed in the attach + // goroutine. + defer func() { + if pipes.syncPipe != nil && !pipes.syncClosed { + errorhandling.CloseQuiet(pipes.syncPipe) + pipes.syncClosed = true + } + }() + + // TODO Only create if !detach + // Attach to the container before starting it + attachChan := make(chan error) + go func() { + // attachToExec is responsible for closing pipes + attachChan <- c.attachToExec(streams, options.DetachKeys, sessionID, pipes.startPipe, pipes.attachPipe) + close(attachChan) + }() + + if err := execCmd.Wait(); err != nil { + return -1, nil, errors.Wrapf(err, "cannot run conmon") + } + + pid, err := readConmonPipeData(pipes.syncPipe, ociLog) + + return pid, attachChan, err +} + +// ExecContainerHTTP executes a new command in an existing container and +// forwards its standard streams over an attach +func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, httpConn net.Conn, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, cancel <-chan bool) (int, chan error, error) { + if streams != nil { + if !streams.Stdin && !streams.Stdout && !streams.Stderr { + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") + } + } + + if options == nil { + return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide exec options to ExecContainerHTTP") + } + + detachString := config.DefaultDetachKeys + if options.DetachKeys != nil { + detachString = *options.DetachKeys + } + detachKeys, err := processDetachKeys(detachString) + if err != nil { + return -1, nil, err + } + + // TODO: Should we default this to false? + // Or maybe make streams mandatory? + attachStdin := true + if streams != nil { + attachStdin = streams.Stdin + } + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = ctr.execOCILog(sessionID) + } + + execCmd, pipes, err := r.startExec(ctr, sessionID, options, attachStdin, ociLog) + if err != nil { + return -1, nil, err + } + + // Only close sync pipe. Start and attach are consumed in the attach + // goroutine. + defer func() { + if pipes.syncPipe != nil && !pipes.syncClosed { + errorhandling.CloseQuiet(pipes.syncPipe) + pipes.syncClosed = true + } + }() + + attachChan := make(chan error) + go func() { + // attachToExec is responsible for closing pipes + attachChan <- attachExecHTTP(ctr, sessionID, httpBuf, streams, pipes, detachKeys, options.Terminal, cancel) + close(attachChan) + }() + + // Wait for conmon to succeed, when return. + if err := execCmd.Wait(); err != nil { + return -1, nil, errors.Wrapf(err, "cannot run conmon") + } + + pid, err := readConmonPipeData(pipes.syncPipe, ociLog) + + return pid, attachChan, err +} + +// ExecContainerDetached executes a command in a running container, but does +// not attach to it. +func (r *ConmonOCIRuntime) ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) { + if options == nil { + return -1, errors.Wrapf(define.ErrInvalidArg, "must provide exec options to ExecContainerHTTP") + } + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = ctr.execOCILog(sessionID) + } + + execCmd, pipes, err := r.startExec(ctr, sessionID, options, stdin, ociLog) + if err != nil { + return -1, err + } + + defer func() { + pipes.cleanup() + }() + + // Wait for Conmon to tell us we're ready to attach. + // We aren't actually *going* to attach, but this means that we're good + // to proceed. + if _, err := readConmonPipeData(pipes.attachPipe, ""); err != nil { + return -1, err + } + + // Start the exec session + if err := writeConmonPipeData(pipes.startPipe); err != nil { + return -1, err + } + + // Wait for conmon to succeed, when return. + if err := execCmd.Wait(); err != nil { + return -1, errors.Wrapf(err, "cannot run conmon") + } + + pid, err := readConmonPipeData(pipes.syncPipe, ociLog) + + return pid, err +} + +// ExecAttachResize resizes the TTY of the given exec session. +func (r *ConmonOCIRuntime) ExecAttachResize(ctr *Container, sessionID string, newSize remotecommand.TerminalSize) error { + controlFile, err := openControlFile(ctr, ctr.execBundlePath(sessionID)) + if err != nil { + return err + } + defer controlFile.Close() + + if _, err = fmt.Fprintf(controlFile, "%d %d %d\n", 1, newSize.Height, newSize.Width); err != nil { + return errors.Wrapf(err, "failed to write to ctl file to resize terminal") + } + + return nil +} + +// ExecStopContainer stops a given exec session in a running container. +func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error { + pid, err := ctr.getExecSessionPID(sessionID) + if err != nil { + return err + } + + logrus.Debugf("Going to stop container %s exec session %s", ctr.ID(), sessionID) + + // Is the session dead? + // Ping the PID with signal 0 to see if it still exists. + if err := unix.Kill(pid, 0); err != nil { + if err == unix.ESRCH { + return nil + } + return errors.Wrapf(err, "error pinging container %s exec session %s PID %d with signal 0", ctr.ID(), sessionID, pid) + } + + if timeout > 0 { + // Use SIGTERM by default, then SIGSTOP after timeout. + logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGTERM", sessionID, pid, ctr.ID()) + if err := unix.Kill(pid, unix.SIGTERM); err != nil { + if err == unix.ESRCH { + return nil + } + return errors.Wrapf(err, "error killing container %s exec session %s PID %d with SIGTERM", ctr.ID(), sessionID, pid) + } + + // Wait for the PID to stop + if err := waitPidStop(pid, time.Duration(timeout)*time.Second); err != nil { + logrus.Warnf("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL", ctr.ID(), sessionID) + } else { + // No error, container is dead + return nil + } + } + + // SIGTERM did not work. On to SIGKILL. + logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGKILL", sessionID, pid, ctr.ID()) + if err := unix.Kill(pid, unix.SIGTERM); err != nil { + if err == unix.ESRCH { + return nil + } + return errors.Wrapf(err, "error killing container %s exec session %s PID %d with SIGKILL", ctr.ID(), sessionID, pid) + } + + // Wait for the PID to stop + if err := waitPidStop(pid, killContainerTimeout*time.Second); err != nil { + return errors.Wrapf(err, "timed out waiting for container %s exec session %s PID %d to stop after SIGKILL", ctr.ID(), sessionID, pid) + } + + return nil +} + +// ExecUpdateStatus checks if the given exec session is still running. +func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) { + pid, err := ctr.getExecSessionPID(sessionID) + if err != nil { + return false, err + } + + logrus.Debugf("Checking status of container %s exec session %s", ctr.ID(), sessionID) + + // Is the session dead? + // Ping the PID with signal 0 to see if it still exists. + if err := unix.Kill(pid, 0); err != nil { + if err == unix.ESRCH { + return false, nil + } + return false, errors.Wrapf(err, "error pinging container %s exec session %s PID %d with signal 0", ctr.ID(), sessionID, pid) + } + + return true, nil +} + +// ExecContainerCleanup cleans up files created when a command is run via +// ExecContainer. This includes the attach socket for the exec session. +func (r *ConmonOCIRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error { + // Clean up the sockets dir. Issue #3962 + // Also ignore if it doesn't exist for some reason; hence the conditional return below + if err := os.RemoveAll(filepath.Join(r.socketsDir, sessionID)); err != nil && !os.IsNotExist(err) { + return err + } + return nil +} + +// ExecAttachSocketPath is the path to a container's exec session attach socket. +func (r *ConmonOCIRuntime) ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) { + // We don't even use container, so don't validity check it + if sessionID == "" { + return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid session ID to get attach socket path") + } + + return filepath.Join(r.socketsDir, sessionID, "attach"), nil +} + +// This contains pipes used by the exec API. +type execPipes struct { + syncPipe *os.File + syncClosed bool + startPipe *os.File + startClosed bool + attachPipe *os.File + attachClosed bool +} + +func (p *execPipes) cleanup() { + if p.syncPipe != nil && !p.syncClosed { + errorhandling.CloseQuiet(p.syncPipe) + p.syncClosed = true + } + if p.startPipe != nil && !p.startClosed { + errorhandling.CloseQuiet(p.startPipe) + p.startClosed = true + } + if p.attachPipe != nil && !p.attachClosed { + errorhandling.CloseQuiet(p.attachPipe) + p.attachClosed = true + } +} + +// Start an exec session's conmon parent from the given options. +func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *ExecOptions, attachStdin bool, ociLog string) (_ *exec.Cmd, _ *execPipes, deferredErr error) { + pipes := new(execPipes) + + if options == nil { + return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") + } + if len(options.Cmd) == 0 { + return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") + } + + if sessionID == "" { + return nil, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") + } + + // create sync pipe to receive the pid + parentSyncPipe, childSyncPipe, err := newPipe() + if err != nil { + return nil, nil, errors.Wrapf(err, "error creating socket pair") + } + pipes.syncPipe = parentSyncPipe + + defer func() { + if deferredErr != nil { + pipes.cleanup() + } + }() + + // create start pipe to set the cgroup before running + // attachToExec is responsible for closing parentStartPipe + childStartPipe, parentStartPipe, err := newPipe() + if err != nil { + return nil, nil, errors.Wrapf(err, "error creating socket pair") + } + pipes.startPipe = parentStartPipe + + // create the attach pipe to allow attach socket to be created before + // $RUNTIME exec starts running. This is to make sure we can capture all output + // from the process through that socket, rather than half reading the log, half attaching to the socket + // attachToExec is responsible for closing parentAttachPipe + parentAttachPipe, childAttachPipe, err := newPipe() + if err != nil { + return nil, nil, errors.Wrapf(err, "error creating socket pair") + } + pipes.attachPipe = parentAttachPipe + + childrenClosed := false + defer func() { + if !childrenClosed { + errorhandling.CloseQuiet(childSyncPipe) + errorhandling.CloseQuiet(childAttachPipe) + errorhandling.CloseQuiet(childStartPipe) + } + }() + + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return nil, nil, err + } + + finalEnv := make([]string, 0, len(options.Env)) + for k, v := range options.Env { + finalEnv = append(finalEnv, fmt.Sprintf("%s=%s", k, v)) + } + + processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID) + if err != nil { + return nil, nil, err + } + + args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog, "") + + if options.PreserveFDs > 0 { + args = append(args, formatRuntimeOpts("--preserve-fds", fmt.Sprintf("%d", options.PreserveFDs))...) + } + + for _, capability := range options.CapAdd { + args = append(args, formatRuntimeOpts("--cap", capability)...) + } + + if options.Terminal { + args = append(args, "-t") + } + + if attachStdin { + args = append(args, "-i") + } + + // Append container ID and command + args = append(args, "-e") + // TODO make this optional when we can detach + args = append(args, "--exec-attach") + args = append(args, "--exec-process-spec", processFile.Name()) + + if len(options.ExitCommand) > 0 { + args = append(args, "--exit-command", options.ExitCommand[0]) + for _, arg := range options.ExitCommand[1:] { + args = append(args, []string{"--exit-command-arg", arg}...) + } + } + + logrus.WithFields(logrus.Fields{ + "args": args, + }).Debugf("running conmon: %s", r.conmonPath) + // TODO: Need to pass this back so we can wait on it. + execCmd := exec.Command(r.conmonPath, args...) + + // TODO: This is commented because it doesn't make much sense in HTTP + // attach, and I'm not certain it does for non-HTTP attach as well. + // if streams != nil { + // // Don't add the InputStream to the execCmd. Instead, the data should be passed + // // through CopyDetachable + // if streams.AttachOutput { + // execCmd.Stdout = options.Streams.OutputStream + // } + // if streams.AttachError { + // execCmd.Stderr = options.Streams.ErrorStream + // } + // } + + conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir) + if err != nil { + return nil, nil, err + } + + if options.PreserveFDs > 0 { + for fd := 3; fd < int(3+options.PreserveFDs); fd++ { + execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))) + } + } + + // we don't want to step on users fds they asked to preserve + // Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3 + execCmd.Env = r.conmonEnv + execCmd.Env = append(execCmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", options.PreserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", options.PreserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", options.PreserveFDs+5)) + execCmd.Env = append(execCmd.Env, conmonEnv...) + + execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe) + execCmd.ExtraFiles = append(execCmd.ExtraFiles, extraFiles...) + execCmd.Dir = c.execBundlePath(sessionID) + execCmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + + err = startCommandGivenSelinux(execCmd) + + // We don't need children pipes on the parent side + errorhandling.CloseQuiet(childSyncPipe) + errorhandling.CloseQuiet(childAttachPipe) + errorhandling.CloseQuiet(childStartPipe) + childrenClosed = true + + if err != nil { + return nil, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) + } + if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { + return nil, nil, err + } + + if options.PreserveFDs > 0 { + for fd := 3; fd < int(3+options.PreserveFDs); fd++ { + // These fds were passed down to the runtime. Close them + // and not interfere + if err := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close(); err != nil { + logrus.Debugf("unable to close file fd-%d", fd) + } + } + } + + return execCmd, pipes, nil +} + +// Attach to a container over HTTP +func attachExecHTTP(c *Container, sessionID string, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool) error { + if pipes == nil || pipes.startPipe == nil || pipes.attachPipe == nil { + return errors.Wrapf(define.ErrInvalidArg, "must provide a start and attach pipe to finish an exec attach") + } + + defer func() { + if !pipes.startClosed { + errorhandling.CloseQuiet(pipes.startPipe) + pipes.startClosed = true + } + if !pipes.attachClosed { + errorhandling.CloseQuiet(pipes.attachPipe) + pipes.attachClosed = true + } + }() + + logrus.Debugf("Attaching to container %s exec session %s", c.ID(), sessionID) + + // set up the socket path, such that it is the correct length and location for exec + sockPath, err := c.execAttachSocketPath(sessionID) + if err != nil { + return err + } + socketPath := buildSocketPath(sockPath) + + // 2: read from attachFd that the parent process has set up the console socket + if _, err := readConmonPipeData(pipes.attachPipe, ""); err != nil { + return err + } + + // 2: then attach + conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"}) + if err != nil { + return errors.Wrapf(err, "failed to connect to container's attach socket: %v", socketPath) + } + defer func() { + if err := conn.Close(); err != nil { + logrus.Errorf("unable to close socket: %q", err) + } + }() + + // Make a channel to pass errors back + errChan := make(chan error) + + attachStdout := true + attachStderr := true + attachStdin := true + if streams != nil { + attachStdout = streams.Stdout + attachStderr = streams.Stderr + attachStdin = streams.Stdin + } + + // Next, STDIN. Avoid entirely if attachStdin unset. + if attachStdin { + go func() { + logrus.Debugf("Beginning STDIN copy") + _, err := utils.CopyDetachable(conn, httpBuf, detachKeys) + logrus.Debugf("STDIN copy completed") + errChan <- err + }() + } + + // 4: send start message to child + if err := writeConmonPipeData(pipes.startPipe); err != nil { + return err + } + + // Handle STDOUT/STDERR *after* start message is sent + go func() { + var err error + if isTerminal { + // Hack: return immediately if attachStdout not set to + // emulate Docker. + // Basically, when terminal is set, STDERR goes nowhere. + // Everything does over STDOUT. + // Therefore, if not attaching STDOUT - we'll never copy + // anything from here. + logrus.Debugf("Performing terminal HTTP attach for container %s", c.ID()) + if attachStdout { + err = httpAttachTerminalCopy(conn, httpBuf, c.ID()) + } + } else { + logrus.Debugf("Performing non-terminal HTTP attach for container %s", c.ID()) + err = httpAttachNonTerminalCopy(conn, httpBuf, c.ID(), attachStdin, attachStdout, attachStderr) + } + errChan <- err + logrus.Debugf("STDOUT/ERR copy completed") + }() + + if cancel != nil { + select { + case err := <-errChan: + return err + case <-cancel: + return nil + } + } else { + var connErr error = <-errChan + return connErr + } +} diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index 7ba36fe7c..9c92b036e 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -635,229 +635,6 @@ func (r *ConmonOCIRuntime) AttachResize(ctr *Container, newSize remotecommand.Te return nil } -// ExecContainer executes a command in a running container -func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams) (int, chan error, error) { - if options == nil { - return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") - } - if len(options.Cmd) == 0 { - return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") - } - - if sessionID == "" { - return -1, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") - } - - // TODO: Should we default this to false? - // Or maybe make streams mandatory? - attachStdin := true - if streams != nil { - attachStdin = streams.AttachInput - } - - var ociLog string - if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { - ociLog = c.execOCILog(sessionID) - } - - execCmd, pipes, err := r.startExec(c, sessionID, options, attachStdin, ociLog) - if err != nil { - return -1, nil, err - } - - // Only close sync pipe. Start and attach are consumed in the attach - // goroutine. - defer func() { - if pipes.syncPipe != nil && !pipes.syncClosed { - errorhandling.CloseQuiet(pipes.syncPipe) - pipes.syncClosed = true - } - }() - - // TODO Only create if !detach - // Attach to the container before starting it - attachChan := make(chan error) - go func() { - // attachToExec is responsible for closing pipes - attachChan <- c.attachToExec(streams, options.DetachKeys, sessionID, pipes.startPipe, pipes.attachPipe) - close(attachChan) - }() - - if err := execCmd.Wait(); err != nil { - return -1, nil, errors.Wrapf(err, "cannot run conmon") - } - - pid, err := readConmonPipeData(pipes.syncPipe, ociLog) - - return pid, attachChan, err -} - -// ExecContainerHTTP executes a new command in an existing container and -// forwards its standard streams over an attach -func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, httpConn net.Conn, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, cancel <-chan bool) (int, chan error, error) { - if streams != nil { - if !streams.Stdin && !streams.Stdout && !streams.Stderr { - return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide at least one stream to attach to") - } - } - - if options == nil { - return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide exec options to ExecContainerHTTP") - } - - detachString := config.DefaultDetachKeys - if options.DetachKeys != nil { - detachString = *options.DetachKeys - } - detachKeys, err := processDetachKeys(detachString) - if err != nil { - return -1, nil, err - } - - // TODO: Should we default this to false? - // Or maybe make streams mandatory? - attachStdin := true - if streams != nil { - attachStdin = streams.Stdin - } - - var ociLog string - if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { - ociLog = ctr.execOCILog(sessionID) - } - - execCmd, pipes, err := r.startExec(ctr, sessionID, options, attachStdin, ociLog) - if err != nil { - return -1, nil, err - } - - // Only close sync pipe. Start and attach are consumed in the attach - // goroutine. - defer func() { - if pipes.syncPipe != nil && !pipes.syncClosed { - errorhandling.CloseQuiet(pipes.syncPipe) - pipes.syncClosed = true - } - }() - - attachChan := make(chan error) - go func() { - // attachToExec is responsible for closing pipes - attachChan <- attachExecHTTP(ctr, sessionID, httpBuf, streams, pipes, detachKeys, options.Terminal, cancel) - close(attachChan) - }() - - // Wait for conmon to succeed, when return. - if err := execCmd.Wait(); err != nil { - return -1, nil, errors.Wrapf(err, "cannot run conmon") - } - - pid, err := readConmonPipeData(pipes.syncPipe, ociLog) - - return pid, attachChan, err -} - -// ExecAttachResize resizes the TTY of the given exec session. -func (r *ConmonOCIRuntime) ExecAttachResize(ctr *Container, sessionID string, newSize remotecommand.TerminalSize) error { - controlFile, err := openControlFile(ctr, ctr.execBundlePath(sessionID)) - if err != nil { - return err - } - defer controlFile.Close() - - if _, err = fmt.Fprintf(controlFile, "%d %d %d\n", 1, newSize.Height, newSize.Width); err != nil { - return errors.Wrapf(err, "failed to write to ctl file to resize terminal") - } - - return nil -} - -// ExecStopContainer stops a given exec session in a running container. -func (r *ConmonOCIRuntime) ExecStopContainer(ctr *Container, sessionID string, timeout uint) error { - pid, err := ctr.getExecSessionPID(sessionID) - if err != nil { - return err - } - - logrus.Debugf("Going to stop container %s exec session %s", ctr.ID(), sessionID) - - // Is the session dead? - // Ping the PID with signal 0 to see if it still exists. - if err := unix.Kill(pid, 0); err != nil { - if err == unix.ESRCH { - return nil - } - return errors.Wrapf(err, "error pinging container %s exec session %s PID %d with signal 0", ctr.ID(), sessionID, pid) - } - - if timeout > 0 { - // Use SIGTERM by default, then SIGSTOP after timeout. - logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGTERM", sessionID, pid, ctr.ID()) - if err := unix.Kill(pid, unix.SIGTERM); err != nil { - if err == unix.ESRCH { - return nil - } - return errors.Wrapf(err, "error killing container %s exec session %s PID %d with SIGTERM", ctr.ID(), sessionID, pid) - } - - // Wait for the PID to stop - if err := waitPidStop(pid, time.Duration(timeout)*time.Second); err != nil { - logrus.Warnf("Timed out waiting for container %s exec session %s to stop, resorting to SIGKILL", ctr.ID(), sessionID) - } else { - // No error, container is dead - return nil - } - } - - // SIGTERM did not work. On to SIGKILL. - logrus.Debugf("Killing exec session %s (PID %d) of container %s with SIGKILL", sessionID, pid, ctr.ID()) - if err := unix.Kill(pid, unix.SIGTERM); err != nil { - if err == unix.ESRCH { - return nil - } - return errors.Wrapf(err, "error killing container %s exec session %s PID %d with SIGKILL", ctr.ID(), sessionID, pid) - } - - // Wait for the PID to stop - if err := waitPidStop(pid, killContainerTimeout*time.Second); err != nil { - return errors.Wrapf(err, "timed out waiting for container %s exec session %s PID %d to stop after SIGKILL", ctr.ID(), sessionID, pid) - } - - return nil -} - -// ExecUpdateStatus checks if the given exec session is still running. -func (r *ConmonOCIRuntime) ExecUpdateStatus(ctr *Container, sessionID string) (bool, error) { - pid, err := ctr.getExecSessionPID(sessionID) - if err != nil { - return false, err - } - - logrus.Debugf("Checking status of container %s exec session %s", ctr.ID(), sessionID) - - // Is the session dead? - // Ping the PID with signal 0 to see if it still exists. - if err := unix.Kill(pid, 0); err != nil { - if err == unix.ESRCH { - return false, nil - } - return false, errors.Wrapf(err, "error pinging container %s exec session %s PID %d with signal 0", ctr.ID(), sessionID, pid) - } - - return true, nil -} - -// ExecContainerCleanup cleans up files created when a command is run via -// ExecContainer. This includes the attach socket for the exec session. -func (r *ConmonOCIRuntime) ExecContainerCleanup(ctr *Container, sessionID string) error { - // Clean up the sockets dir. Issue #3962 - // Also ignore if it doesn't exist for some reason; hence the conditional return below - if err := os.RemoveAll(filepath.Join(r.socketsDir, sessionID)); err != nil && !os.IsNotExist(err) { - return err - } - return nil -} - // CheckpointContainer checkpoints the given container. func (r *ConmonOCIRuntime) CheckpointContainer(ctr *Container, options ContainerCheckpointOptions) error { if err := label.SetSocketLabel(ctr.ProcessLabel()); err != nil { @@ -934,16 +711,6 @@ func (r *ConmonOCIRuntime) AttachSocketPath(ctr *Container) (string, error) { return filepath.Join(r.socketsDir, ctr.ID(), "attach"), nil } -// ExecAttachSocketPath is the path to a container's exec session attach socket. -func (r *ConmonOCIRuntime) ExecAttachSocketPath(ctr *Container, sessionID string) (string, error) { - // We don't even use container, so don't validity check it - if sessionID == "" { - return "", errors.Wrapf(define.ErrInvalidArg, "must provide a valid session ID to get attach socket path") - } - - return filepath.Join(r.socketsDir, sessionID, "attach"), nil -} - // ExitFilePath is the path to a container's exit file. func (r *ConmonOCIRuntime) ExitFilePath(ctr *Container) (string, error) { if ctr == nil { @@ -1765,297 +1532,3 @@ func httpAttachNonTerminalCopy(container *net.UnixConn, http *bufio.ReadWriter, } } - -// This contains pipes used by the exec API. -type execPipes struct { - syncPipe *os.File - syncClosed bool - startPipe *os.File - startClosed bool - attachPipe *os.File - attachClosed bool -} - -func (p *execPipes) cleanup() { - if p.syncPipe != nil && !p.syncClosed { - errorhandling.CloseQuiet(p.syncPipe) - p.syncClosed = true - } - if p.startPipe != nil && !p.startClosed { - errorhandling.CloseQuiet(p.startPipe) - p.startClosed = true - } - if p.attachPipe != nil && !p.attachClosed { - errorhandling.CloseQuiet(p.attachPipe) - p.attachClosed = true - } -} - -// Start an exec session's conmon parent from the given options. -func (r *ConmonOCIRuntime) startExec(c *Container, sessionID string, options *ExecOptions, attachStdin bool, ociLog string) (_ *exec.Cmd, _ *execPipes, deferredErr error) { - pipes := new(execPipes) - - if options == nil { - return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") - } - if len(options.Cmd) == 0 { - return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") - } - - if sessionID == "" { - return nil, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") - } - - // create sync pipe to receive the pid - parentSyncPipe, childSyncPipe, err := newPipe() - if err != nil { - return nil, nil, errors.Wrapf(err, "error creating socket pair") - } - pipes.syncPipe = parentSyncPipe - - defer func() { - if deferredErr != nil { - pipes.cleanup() - } - }() - - // create start pipe to set the cgroup before running - // attachToExec is responsible for closing parentStartPipe - childStartPipe, parentStartPipe, err := newPipe() - if err != nil { - return nil, nil, errors.Wrapf(err, "error creating socket pair") - } - pipes.startPipe = parentStartPipe - - // create the attach pipe to allow attach socket to be created before - // $RUNTIME exec starts running. This is to make sure we can capture all output - // from the process through that socket, rather than half reading the log, half attaching to the socket - // attachToExec is responsible for closing parentAttachPipe - parentAttachPipe, childAttachPipe, err := newPipe() - if err != nil { - return nil, nil, errors.Wrapf(err, "error creating socket pair") - } - pipes.attachPipe = parentAttachPipe - - childrenClosed := false - defer func() { - if !childrenClosed { - errorhandling.CloseQuiet(childSyncPipe) - errorhandling.CloseQuiet(childAttachPipe) - errorhandling.CloseQuiet(childStartPipe) - } - }() - - runtimeDir, err := util.GetRuntimeDir() - if err != nil { - return nil, nil, err - } - - finalEnv := make([]string, 0, len(options.Env)) - for k, v := range options.Env { - finalEnv = append(finalEnv, fmt.Sprintf("%s=%s", k, v)) - } - - processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID) - if err != nil { - return nil, nil, err - } - - args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog, "") - - if options.PreserveFDs > 0 { - args = append(args, formatRuntimeOpts("--preserve-fds", fmt.Sprintf("%d", options.PreserveFDs))...) - } - - for _, capability := range options.CapAdd { - args = append(args, formatRuntimeOpts("--cap", capability)...) - } - - if options.Terminal { - args = append(args, "-t") - } - - if attachStdin { - args = append(args, "-i") - } - - // Append container ID and command - args = append(args, "-e") - // TODO make this optional when we can detach - args = append(args, "--exec-attach") - args = append(args, "--exec-process-spec", processFile.Name()) - - logrus.WithFields(logrus.Fields{ - "args": args, - }).Debugf("running conmon: %s", r.conmonPath) - // TODO: Need to pass this back so we can wait on it. - execCmd := exec.Command(r.conmonPath, args...) - - // TODO: This is commented because it doesn't make much sense in HTTP - // attach, and I'm not certain it does for non-HTTP attach as well. - // if streams != nil { - // // Don't add the InputStream to the execCmd. Instead, the data should be passed - // // through CopyDetachable - // if streams.AttachOutput { - // execCmd.Stdout = options.Streams.OutputStream - // } - // if streams.AttachError { - // execCmd.Stderr = options.Streams.ErrorStream - // } - // } - - conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir) - if err != nil { - return nil, nil, err - } - - if options.PreserveFDs > 0 { - for fd := 3; fd < int(3+options.PreserveFDs); fd++ { - execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))) - } - } - - // we don't want to step on users fds they asked to preserve - // Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3 - execCmd.Env = r.conmonEnv - execCmd.Env = append(execCmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", options.PreserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", options.PreserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", options.PreserveFDs+5)) - execCmd.Env = append(execCmd.Env, conmonEnv...) - - execCmd.ExtraFiles = append(execCmd.ExtraFiles, childSyncPipe, childStartPipe, childAttachPipe) - execCmd.ExtraFiles = append(execCmd.ExtraFiles, extraFiles...) - execCmd.Dir = c.execBundlePath(sessionID) - execCmd.SysProcAttr = &syscall.SysProcAttr{ - Setpgid: true, - } - - err = startCommandGivenSelinux(execCmd) - - // We don't need children pipes on the parent side - errorhandling.CloseQuiet(childSyncPipe) - errorhandling.CloseQuiet(childAttachPipe) - errorhandling.CloseQuiet(childStartPipe) - childrenClosed = true - - if err != nil { - return nil, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) - } - if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { - return nil, nil, err - } - - if options.PreserveFDs > 0 { - for fd := 3; fd < int(3+options.PreserveFDs); fd++ { - // These fds were passed down to the runtime. Close them - // and not interfere - if err := os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close(); err != nil { - logrus.Debugf("unable to close file fd-%d", fd) - } - } - } - - return execCmd, pipes, nil -} - -// Attach to a container over HTTP -func attachExecHTTP(c *Container, sessionID string, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, pipes *execPipes, detachKeys []byte, isTerminal bool, cancel <-chan bool) error { - if pipes == nil || pipes.startPipe == nil || pipes.attachPipe == nil { - return errors.Wrapf(define.ErrInvalidArg, "must provide a start and attach pipe to finish an exec attach") - } - - defer func() { - if !pipes.startClosed { - errorhandling.CloseQuiet(pipes.startPipe) - pipes.startClosed = true - } - if !pipes.attachClosed { - errorhandling.CloseQuiet(pipes.attachPipe) - pipes.attachClosed = true - } - }() - - logrus.Debugf("Attaching to container %s exec session %s", c.ID(), sessionID) - - // set up the socket path, such that it is the correct length and location for exec - sockPath, err := c.execAttachSocketPath(sessionID) - if err != nil { - return err - } - socketPath := buildSocketPath(sockPath) - - // 2: read from attachFd that the parent process has set up the console socket - if _, err := readConmonPipeData(pipes.attachPipe, ""); err != nil { - return err - } - - // 2: then attach - conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"}) - if err != nil { - return errors.Wrapf(err, "failed to connect to container's attach socket: %v", socketPath) - } - defer func() { - if err := conn.Close(); err != nil { - logrus.Errorf("unable to close socket: %q", err) - } - }() - - // Make a channel to pass errors back - errChan := make(chan error) - - attachStdout := true - attachStderr := true - attachStdin := true - if streams != nil { - attachStdout = streams.Stdout - attachStderr = streams.Stderr - attachStdin = streams.Stdin - } - - // Next, STDIN. Avoid entirely if attachStdin unset. - if attachStdin { - go func() { - logrus.Debugf("Beginning STDIN copy") - _, err := utils.CopyDetachable(conn, httpBuf, detachKeys) - logrus.Debugf("STDIN copy completed") - errChan <- err - }() - } - - // 4: send start message to child - if err := writeConmonPipeData(pipes.startPipe); err != nil { - return err - } - - // Handle STDOUT/STDERR *after* start message is sent - go func() { - var err error - if isTerminal { - // Hack: return immediately if attachStdout not set to - // emulate Docker. - // Basically, when terminal is set, STDERR goes nowhere. - // Everything does over STDOUT. - // Therefore, if not attaching STDOUT - we'll never copy - // anything from here. - logrus.Debugf("Performing terminal HTTP attach for container %s", c.ID()) - if attachStdout { - err = httpAttachTerminalCopy(conn, httpBuf, c.ID()) - } - } else { - logrus.Debugf("Performing non-terminal HTTP attach for container %s", c.ID()) - err = httpAttachNonTerminalCopy(conn, httpBuf, c.ID(), attachStdin, attachStdout, attachStderr) - } - errChan <- err - logrus.Debugf("STDOUT/ERR copy completed") - }() - - if cancel != nil { - select { - case err := <-errChan: - return err - case <-cancel: - return nil - } - } else { - var connErr error = <-errChan - return connErr - } -} diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index 626740f72..4da16876c 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -130,6 +130,11 @@ func (r *MissingRuntime) ExecContainerHTTP(ctr *Container, sessionID string, opt return -1, nil, r.printError() } +// ExecContainerDetached is not available as the runtime is missing +func (r *MissingRuntime) ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) { + return -1, r.printError() +} + // ExecAttachResize is not available as the runtime is missing. func (r *MissingRuntime) ExecAttachResize(ctr *Container, sessionID string, newSize remotecommand.TerminalSize) error { return r.printError() diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index c670822a0..655b42e51 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -390,6 +390,8 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } } + logrus.Debugf("Removing container %s", c.ID()) + // We need to lock the pod before we lock the container. // To avoid races around removing a container and the pod it is in. // Don't need to do this in pod removal case - we're evicting the entire diff --git a/pkg/api/handlers/libpod/healthcheck.go b/pkg/api/handlers/libpod/healthcheck.go index 6eb2ab0e3..0ca3574b7 100644 --- a/pkg/api/handlers/libpod/healthcheck.go +++ b/pkg/api/handlers/libpod/healthcheck.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/utils" ) @@ -12,32 +13,27 @@ func RunHealthCheck(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) status, err := runtime.HealthCheck(name) if err != nil { - if status == libpod.HealthCheckContainerNotFound { + if status == define.HealthCheckContainerNotFound { utils.ContainerNotFound(w, name, err) return } - if status == libpod.HealthCheckNotDefined { + if status == define.HealthCheckNotDefined { utils.Error(w, "no healthcheck defined", http.StatusConflict, err) return } - if status == libpod.HealthCheckContainerStopped { + if status == define.HealthCheckContainerStopped { utils.Error(w, "container not running", http.StatusConflict, err) return } utils.InternalServerError(w, err) return } - ctr, err := runtime.LookupContainer(name) - if err != nil { - utils.InternalServerError(w, err) - return + hcStatus := define.HealthCheckUnhealthy + if status == define.HealthCheckSuccess { + hcStatus = define.HealthCheckHealthy } - - hcLog, err := ctr.GetHealthCheckLog() - if err != nil { - utils.InternalServerError(w, err) - return + report := define.HealthCheckResults{ + Status: hcStatus, } - - utils.WriteResponse(w, http.StatusOK, hcLog) + utils.WriteResponse(w, http.StatusOK, report) } diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 1c67de9db..7fb31a177 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -62,7 +62,6 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { }{ // This is where you can override the golang default value for one of fields } - // TODO I think all is implemented with a filter? if err := decoder.Decode(&query, r.URL.Query()); err != nil { return nil, err @@ -71,6 +70,10 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { if _, found := r.URL.Query()["digests"]; found && query.Digests { UnSupportedParameter("digests") } + var ( + images []*image.Image + err error + ) if len(query.Filters) > 0 { for k, v := range query.Filters { @@ -78,11 +81,33 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { filters = append(filters, fmt.Sprintf("%s=%s", k, val)) } } - return runtime.ImageRuntime().GetImagesWithFilters(filters) + images, err = runtime.ImageRuntime().GetImagesWithFilters(filters) + if err != nil { + return images, err + } } else { - return runtime.ImageRuntime().GetImages() + images, err = runtime.ImageRuntime().GetImages() + if err != nil { + return images, err + } } - + if query.All { + return images, nil + } + var returnImages []*image.Image + for _, img := range images { + if len(img.Names()) == 0 { + parent, err := img.IsParent(r.Context()) + if err != nil { + return nil, err + } + if parent { + continue + } + } + returnImages = append(returnImages, img) + } + return returnImages, nil } func GetImage(r *http.Request, name string) (*image.Image, error) { diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 3cc4b6db1..8d85a9b23 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -242,7 +242,6 @@ type ExecOptions struct { Latest bool PreserveFDs uint Privileged bool - Streams define.AttachStreams Tty bool User string WorkDir string @@ -311,6 +310,7 @@ type ContainerRunReport struct { // cleanup command type ContainerCleanupOptions struct { All bool + Exec string Latest bool Remove bool RemoveImage bool diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index e77f0758b..3d5161745 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -19,7 +19,8 @@ type ContainerEngine interface { ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) (*ContainerCpReport, error) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error) ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error) - ContainerExec(ctx context.Context, nameOrId string, options ExecOptions) (int, error) + ContainerExec(ctx context.Context, nameOrId string, options ExecOptions, streams define.AttachStreams) (int, error) + ContainerExecDetached(ctx context.Context, nameOrID string, options ExecOptions) (string, error) ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error) ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error ContainerInit(ctx context.Context, namesOrIds []string, options ContainerInitOptions) ([]*ContainerInitReport, error) diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index cce3001eb..0f909ab37 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -1,7 +1,6 @@ package entities import ( - "net/url" "time" "github.com/containers/image/v5/manifest" @@ -221,15 +220,13 @@ type ImageSearchReport struct { // Image List Options type ImageListOptions struct { - All bool `json:"all" schema:"all"` - Filter []string `json:"Filter,omitempty"` - Filters url.Values `json:"filters" schema:"filters"` + All bool `json:"all" schema:"all"` + Filter []string `json:"Filter,omitempty"` } type ImagePruneOptions struct { - All bool `json:"all" schema:"all"` - Filter []string `json:"filter" schema:"filter"` - Filters url.Values `json:"filters" schema:"filters"` + All bool `json:"all" schema:"all"` + Filter []string `json:"filter" schema:"filter"` } type ImagePruneReport struct { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 035efe575..b4e38ca23 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -536,7 +536,22 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, return nil } -func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) { +func makeExecConfig(options entities.ExecOptions) *libpod.ExecConfig { + execConfig := new(libpod.ExecConfig) + execConfig.Command = options.Cmd + execConfig.Terminal = options.Tty + execConfig.Privileged = options.Privileged + execConfig.Environment = options.Envs + execConfig.User = options.User + execConfig.WorkDir = options.WorkDir + execConfig.DetachKeys = &options.DetachKeys + execConfig.PreserveFDs = options.PreserveFDs + execConfig.AttachStdin = options.Interactive + + return execConfig +} + +func checkExecPreserveFDs(options entities.ExecOptions) (int, error) { ec := define.ExecErrorCodeGeneric if options.PreserveFDs > 0 { entries, err := ioutil.ReadDir("/proc/self/fd") @@ -559,15 +574,66 @@ func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, o } } } + return ec, nil +} + +func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions, streams define.AttachStreams) (int, error) { + ec, err := checkExecPreserveFDs(options) + if err != nil { + return ec, err + } ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod) if err != nil { return ec, err } ctr := ctrs[0] - ec, err = terminal.ExecAttachCtr(ctx, ctr, options.Tty, options.Privileged, options.Envs, options.Cmd, options.User, options.WorkDir, &options.Streams, options.PreserveFDs, options.DetachKeys) + + execConfig := makeExecConfig(options) + + ec, err = terminal.ExecAttachCtr(ctx, ctr, execConfig, &streams) return define.TranslateExecErrorToExitCode(ec, err), err } +func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrId string, options entities.ExecOptions) (string, error) { + _, err := checkExecPreserveFDs(options) + if err != nil { + return "", err + } + ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod) + if err != nil { + return "", err + } + ctr := ctrs[0] + + execConfig := makeExecConfig(options) + + // Make an exit command + storageConfig := ic.Libpod.StorageConfig() + runtimeConfig, err := ic.Libpod.GetConfig() + if err != nil { + return "", errors.Wrapf(err, "error retrieving Libpod configuration to build exec exit command") + } + podmanPath, err := os.Executable() + if err != nil { + return "", errors.Wrapf(err, "error retrieving executable to build exec exit command") + } + // TODO: Add some ability to toggle syslog + exitCommandArgs := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, podmanPath, false, true, true) + execConfig.ExitCommand = exitCommandArgs + + // Create and start the exec session + id, err := ctr.ExecCreate(execConfig) + if err != nil { + return "", err + } + + // TODO: we should try and retrieve exit code if this fails. + if err := ctr.ExecStart(id); err != nil { + return "", err + } + return id, nil +} + func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { var reports []*entities.ContainerStartReport var exitCode = define.ExecErrorCodeGeneric @@ -836,6 +902,20 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st for _, ctr := range ctrs { var err error report := entities.ContainerCleanupReport{Id: ctr.ID()} + + if options.Exec != "" { + if options.Remove { + if err := ctr.ExecRemove(options.Exec, false); err != nil { + return nil, err + } + } else { + if err := ctr.ExecCleanup(options.Exec); err != nil { + return nil, err + } + } + return []*entities.ContainerCleanupReport{}, nil + } + if options.Remove { err = ic.Libpod.RemoveContainer(ctx, ctr, false, true) if err != nil { diff --git a/pkg/domain/infra/abi/healthcheck.go b/pkg/domain/infra/abi/healthcheck.go index 351bf4f7e..4e925ef56 100644 --- a/pkg/domain/infra/abi/healthcheck.go +++ b/pkg/domain/infra/abi/healthcheck.go @@ -3,7 +3,6 @@ package abi import ( "context" - "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" ) @@ -13,9 +12,9 @@ func (ic *ContainerEngine) HealthCheckRun(ctx context.Context, nameOrId string, if err != nil { return nil, err } - hcStatus := "unhealthy" - if status == libpod.HealthCheckSuccess { - hcStatus = "healthy" + hcStatus := define.HealthCheckUnhealthy + if status == define.HealthCheckSuccess { + hcStatus = define.HealthCheckHealthy } report := define.HealthCheckResults{ Status: hcStatus, diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index c559e250c..3034e36ec 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -13,14 +13,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) err error ) - // TODO: Future work support for domain.Filters - // filters := utils.ToLibpodFilters(opts.Filters) - - if len(opts.Filter) > 0 { - images, err = ir.Libpod.ImageRuntime().GetImagesWithFilters(opts.Filter) - } else { - images, err = ir.Libpod.ImageRuntime().GetImages() - } + images, err = ir.Libpod.ImageRuntime().GetImagesWithFilters(opts.Filter) if err != nil { return nil, err } @@ -40,9 +33,18 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) } } } else { - repoTags, _ = img.RepoTags() - if len(repoTags) == 0 { - continue + repoTags, err = img.RepoTags() + if err != nil { + return nil, err + } + if len(img.Names()) == 0 { + parent, err := img.IsParent(ctx) + if err != nil { + return nil, err + } + if parent { + continue + } } } diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go index 15701342f..8d9cdde03 100644 --- a/pkg/domain/infra/abi/terminal/terminal_linux.go +++ b/pkg/domain/infra/abi/terminal/terminal_linux.go @@ -15,13 +15,13 @@ import ( ) // ExecAttachCtr execs and attaches to a container -func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpod.ExecConfig, streams *define.AttachStreams) (int, error) { resize := make(chan remotecommand.TerminalSize) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) // Check if we are attached to a terminal. If we are, generate resize // events, and set the terminal to raw mode - if haveTerminal && tty { + if haveTerminal && execConfig.Terminal { cancel, oldTermState, err := handleTerminalAttach(ctx, resize) if err != nil { return -1, err @@ -34,16 +34,6 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged b }() } - execConfig := new(libpod.ExecConfig) - execConfig.Command = cmd - execConfig.Terminal = tty - execConfig.Privileged = privileged - execConfig.Environment = env - execConfig.User = user - execConfig.WorkDir = workDir - execConfig.DetachKeys = &detachKeys - execConfig.PreserveFDs = preserveFDs - return ctr.Exec(execConfig, streams, resize) } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 828bfae5b..d02c54e76 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -4,6 +4,7 @@ import ( "context" "io" "os" + "strings" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" @@ -329,10 +330,14 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, return containers.Attach(ic.ClientCxt, nameOrId, &options.DetachKeys, nil, bindings.PTrue, options.Stdin, options.Stdout, options.Stderr, nil) } -func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) { +func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions, streams define.AttachStreams) (int, error) { return 125, errors.New("not implemented") } +func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID string, options entities.ExecOptions) (string, error) { + return "", errors.New("not implemented") +} + func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, output, errput *os.File) error { //nolint attachErr := make(chan error) attachReady := make(chan bool) @@ -355,10 +360,19 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri report := entities.ContainerStartReport{Id: name} if options.Attach { report.Err = startAndAttach(ic, name, &options.DetachKeys, options.Stdin, options.Stdout, options.Stderr) + if report.Err == nil { + exitCode, err := containers.Wait(ic.ClientCxt, name, nil) + if err == nil { + report.ExitCode = int(exitCode) + } + } else { + report.ExitCode = define.ExitCode(report.Err) + } reports = append(reports, &report) return reports, nil } report.Err = containers.Start(ic.ClientCxt, name, &options.DetachKeys) + report.ExitCode = define.ExitCode(report.Err) reports = append(reports, &report) } return reports, nil @@ -380,11 +394,18 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta // Attach if !opts.Detach { err = startAndAttach(ic, con.ID, &opts.DetachKeys, opts.InputStream, opts.OutputStream, opts.ErrorStream) - + if err == nil { + exitCode, err := containers.Wait(ic.ClientCxt, con.ID, nil) + if err == nil { + report.ExitCode = int(exitCode) + } + } } else { err = containers.Start(ic.ClientCxt, con.ID, nil) } - report.ExitCode = define.ExitCode(err) + if err != nil { + report.ExitCode = define.ExitCode(err) + } return &report, err } @@ -405,6 +426,11 @@ func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []strin } for _, ctr := range ctrs { err := containers.ContainerInit(ic.ClientCxt, ctr.ID) + // When using all, it is NOT considered an error if a container + // has already been init'd. + if err != nil && options.All && strings.Contains(errors.Cause(err).Error(), define.ErrCtrStateInvalid.Error()) { + err = nil + } reports = append(reports, &entities.ContainerInitReport{ Err: err, Id: ctr.ID, diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 4d00d331b..3d5626c45 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -4,6 +4,7 @@ import ( "context" "io/ioutil" "os" + "strings" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" @@ -25,8 +26,13 @@ func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts enti } func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) { - images, err := images.List(ir.ClientCxt, &opts.All, opts.Filters) + filters := make(map[string][]string, len(opts.Filter)) + for _, filter := range opts.Filter { + f := strings.Split(filter, "=") + filters[f[0]] = f[1:] + } + images, err := images.List(ir.ClientCxt, &opts.All, filters) if err != nil { return nil, err } @@ -61,7 +67,13 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entiti } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { - results, err := images.Prune(ir.ClientCxt, &opts.All, opts.Filters) + filters := make(map[string][]string, len(opts.Filter)) + for _, filter := range opts.Filter { + f := strings.Split(filter, "=") + filters[f[0]] = f[1:] + } + + results, err := images.Prune(ir.ClientCxt, &opts.All, filters) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index f3aaf96bf..ffd7fd4dd 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -111,7 +111,8 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener if err != nil { return nil, err } - options = append(options, createExitCommandOption(s, rt.StorageConfig(), rtc, podmanPath)) + // TODO: Enable syslog support - we'll need to put this in SpecGen. + options = append(options, libpod.WithExitCommand(CreateExitCommandArgs(rt.StorageConfig(), rtc, podmanPath, false, s.Remove, false))) runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts) if err != nil { @@ -228,7 +229,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. return options, nil } -func createExitCommandOption(s *specgen.SpecGenerator, storageConfig storage.StoreOptions, config *config.Config, podmanPath string) libpod.CtrCreateOption { +func CreateExitCommandArgs(storageConfig storage.StoreOptions, config *config.Config, podmanPath string, syslog, rm bool, exec bool) []string { // We need a cleanup process for containers in the current model. // But we can't assume that the caller is Podman - it could be another // user of the API. @@ -255,14 +256,18 @@ func createExitCommandOption(s *specgen.SpecGenerator, storageConfig storage.Sto command = append(command, []string{"--events-backend", config.Engine.EventsLogger}...) } - // TODO Mheon wants to leave this for now - //if s.sys { - // command = append(command, "--syslog", "true") - //} + if syslog { + command = append(command, "--syslog", "true") + } command = append(command, []string{"container", "cleanup"}...) - if s.Remove { + if rm { command = append(command, "--rm") } - return libpod.WithExitCommand(command) + + if exec { + command = append(command, "--exec") + } + + return command } diff --git a/pkg/util/mountOpts_linux.go b/pkg/util/mountOpts_linux.go index 3eac4dd25..bc7c675f3 100644 --- a/pkg/util/mountOpts_linux.go +++ b/pkg/util/mountOpts_linux.go @@ -7,7 +7,7 @@ import ( ) func getDefaultMountOptions(path string) (defaultMountOptions, error) { - opts := defaultMountOptions{true, true, true} + opts := defaultMountOptions{false, true, true} if path == "" { return opts, nil } diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 258cb8652..291353cad 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -901,12 +901,12 @@ func (i *VarlinkAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.Exec // HealthCheckRun executes defined container's healthcheck command and returns the container's health status. func (i *VarlinkAPI) HealthCheckRun(call iopodman.VarlinkCall, nameOrID string) error { hcStatus, err := i.Runtime.HealthCheck(nameOrID) - if err != nil && hcStatus != libpod.HealthCheckFailure { + if err != nil && hcStatus != define.HealthCheckFailure { return call.ReplyErrorOccurred(err.Error()) } - status := libpod.HealthCheckUnhealthy - if hcStatus == libpod.HealthCheckSuccess { - status = libpod.HealthCheckHealthy + status := define.HealthCheckUnhealthy + if hcStatus == define.HealthCheckSuccess { + status = define.HealthCheckHealthy } return call.ReplyHealthCheckRun(status) } diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 7d4858551..0a6373bfa 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -207,7 +207,7 @@ var _ = Describe("Podman create", func() { session = podmanTest.Podman([]string{"logs", "test_tmpfs"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.OutputToString()).To(ContainSubstring("/create/test rw,nosuid,nodev,noexec,relatime - tmpfs")) + Expect(session.OutputToString()).To(ContainSubstring("/create/test rw,nosuid,nodev,relatime - tmpfs")) }) It("podman create --pod automatically", func() { diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index da80bba47..87dddb233 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -283,4 +283,31 @@ var _ = Describe("Podman exec", func() { Expect(exec.ExitCode()).To(Equal(0)) Expect(strings.Contains(exec.OutputToString(), fmt.Sprintf("%s(%s)", gid, groupName))).To(BeTrue()) }) + + It("podman exec --detach", func() { + ctrName := "testctr" + ctr := podmanTest.Podman([]string{"run", "-t", "-i", "-d", "--name", ctrName, ALPINE, "top"}) + ctr.WaitWithDefaultTimeout() + Expect(ctr.ExitCode()).To(Equal(0)) + + exec1 := podmanTest.Podman([]string{"exec", "-t", "-i", "-d", ctrName, "top"}) + exec1.WaitWithDefaultTimeout() + Expect(ctr.ExitCode()).To(Equal(0)) + + data := podmanTest.InspectContainer(ctrName) + Expect(len(data)).To(Equal(1)) + Expect(len(data[0].ExecIDs)).To(Equal(1)) + Expect(strings.Contains(exec1.OutputToString(), data[0].ExecIDs[0])).To(BeTrue()) + + exec2 := podmanTest.Podman([]string{"exec", "-t", "-i", ctrName, "ps", "-a"}) + exec2.WaitWithDefaultTimeout() + Expect(ctr.ExitCode()).To(Equal(0)) + Expect(strings.Count(exec2.OutputToString(), "top")).To(Equal(2)) + + // Ensure that stop with a running detached exec session is + // clean. + stop := podmanTest.Podman([]string{"stop", ctrName}) + stop.WaitWithDefaultTimeout() + Expect(stop.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index f434836d3..8e63d9f4c 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -83,7 +83,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck that should fail", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "docker.io/libpod/badhealthcheck:latest"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -122,7 +121,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck failed checks in start-period should not change status", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-start-period", "2m", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -144,7 +142,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck failed checks must reach retries before unhealthy ", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index cd281e3c7..1715cf8c1 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -152,9 +152,7 @@ var _ = Describe("Podman images", func() { }) It("podman images filter reference", func() { - if podmanTest.RemoteTest { - Skip("Does not work on remote client") - } + SkipIfRemote() podmanTest.RestoreAllArtifacts() result := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "reference=docker.io*"}) result.WaitWithDefaultTimeout() @@ -180,9 +178,7 @@ var _ = Describe("Podman images", func() { }) It("podman images filter before image", func() { - if podmanTest.RemoteTest { - Skip("Does not work on remote client") - } + SkipIfRemote() dockerfile := `FROM docker.io/library/alpine:latest RUN apk update && apk add man ` @@ -194,9 +190,7 @@ RUN apk update && apk add man }) It("podman images filter after image", func() { - if podmanTest.RemoteTest { - Skip("Does not work on remote client") - } + SkipIfRemote() podmanTest.RestoreAllArtifacts() rmi := podmanTest.PodmanNoCache([]string{"rmi", "busybox"}) rmi.WaitWithDefaultTimeout() @@ -212,9 +206,7 @@ RUN apk update && apk add man }) It("podman image list filter after image", func() { - if podmanTest.RemoteTest { - Skip("Does not work on remote client") - } + SkipIfRemote() podmanTest.RestoreAllArtifacts() rmi := podmanTest.PodmanNoCache([]string{"image", "rm", "busybox"}) rmi.WaitWithDefaultTimeout() @@ -230,9 +222,7 @@ RUN apk update && apk add man }) It("podman images filter dangling", func() { - if podmanTest.RemoteTest { - Skip("Does not work on remote client") - } + SkipIfRemote() dockerfile := `FROM docker.io/library/alpine:latest ` podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") @@ -308,9 +298,7 @@ RUN apk update && apk add man }) It("podman images --all flag", func() { - if podmanTest.RemoteTest { - Skip("Does not work on remote client") - } + SkipIfRemote() podmanTest.RestoreAllArtifacts() dockerfile := `FROM docker.io/library/alpine:latest RUN mkdir hello @@ -343,10 +331,7 @@ LABEL "com.example.vendor"="Example Vendor" }) It("podman with images with no layers", func() { - if podmanTest.RemoteTest { - Skip("Does not work on remote client") - } - + SkipIfRemote() dockerfile := strings.Join([]string{ `FROM scratch`, `LABEL org.opencontainers.image.authors="<somefolks@example.org>"`, diff --git a/test/e2e/init_test.go b/test/e2e/init_test.go index 349487b03..721017d0c 100644 --- a/test/e2e/init_test.go +++ b/test/e2e/init_test.go @@ -90,7 +90,6 @@ var _ = Describe("Podman init", func() { }) It("podman init all three containers, one running", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"create", "--name", "test1", "-d", ALPINE, "ls"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -121,11 +120,10 @@ var _ = Describe("Podman init", func() { }) It("podman init running container errors", func() { - Skip(v2remotefail) - session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top"}) + session := podmanTest.Podman([]string{"run", "--name", "init_test", "-d", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - init := podmanTest.Podman([]string{"init", "--latest"}) + init := podmanTest.Podman([]string{"init", "init_test"}) init.WaitWithDefaultTimeout() Expect(init.ExitCode()).To(Equal(125)) }) diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 24bfa69ce..e56db54a2 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -2,7 +2,9 @@ package integration import ( "fmt" + "io/ioutil" "os" + "path/filepath" "strings" . "github.com/containers/libpod/test/utils" @@ -282,4 +284,26 @@ var _ = Describe("Podman pod create", func() { podCreate.WaitWithDefaultTimeout() Expect(podCreate.ExitCode()).To(Equal(125)) }) + + It("podman create pod and print id to external file", func() { + // Switch to temp dir and restore it afterwards + cwd, err := os.Getwd() + Expect(err).To(BeNil()) + Expect(os.Chdir(os.TempDir())).To(BeNil()) + targetPath := filepath.Join(os.TempDir(), "dir") + Expect(os.MkdirAll(targetPath, 0755)).To(BeNil()) + targetFile := filepath.Join(targetPath, "idFile") + defer Expect(os.RemoveAll(targetFile)).To(BeNil()) + defer Expect(os.Chdir(cwd)).To(BeNil()) + + session := podmanTest.Podman([]string{"pod", "create", "--name=abc", "--pod-id-file", targetFile}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + id, _ := ioutil.ReadFile(targetFile) + check := podmanTest.Podman([]string{"pod", "inspect", "abc"}) + check.WaitWithDefaultTimeout() + data := check.InspectPodToJSON() + Expect(data.ID).To(Equal(string(id))) + }) }) diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index ccd322dd2..e77e6dd25 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -161,7 +161,6 @@ var _ = Describe("Podman prune", func() { }) It("podman system prune pods", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -169,12 +168,13 @@ var _ = Describe("Podman prune", func() { session = podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + podid1 := session.OutputToString() - session = podmanTest.Podman([]string{"pod", "start", "-l"}) + session = podmanTest.Podman([]string{"pod", "start", podid1}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"pod", "stop", "-l"}) + session = podmanTest.Podman([]string{"pod", "stop", podid1}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -194,17 +194,17 @@ var _ = Describe("Podman prune", func() { }) It("podman system prune - pod,container stopped", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + podid1 := session.OutputToString() // Start and stop a pod to get it in exited state. - session = podmanTest.Podman([]string{"pod", "start", "-l"}) + session = podmanTest.Podman([]string{"pod", "start", podid1}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"pod", "stop", "-l"}) + session = podmanTest.Podman([]string{"pod", "stop", podid1}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -226,17 +226,17 @@ var _ = Describe("Podman prune", func() { }) It("podman system prune with running, exited pod and volume prune set true", func() { - Skip(v2remotefail) // Start and stop a pod to get it in exited state. session := podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + podid1 := session.OutputToString() - session = podmanTest.Podman([]string{"pod", "start", "-l"}) + session = podmanTest.Podman([]string{"pod", "start", podid1}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"pod", "stop", "-l"}) + session = podmanTest.Podman([]string{"pod", "stop", podid1}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -244,7 +244,9 @@ var _ = Describe("Podman prune", func() { session = podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"pod", "start", "-l"}) + podid2 := session.OutputToString() + + session = podmanTest.Podman([]string{"pod", "start", podid2}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -301,17 +303,17 @@ var _ = Describe("Podman prune", func() { }) It("podman system prune - with dangling images true", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + podid1 := session.OutputToString() // Start and stop a pod to get it in exited state. - session = podmanTest.Podman([]string{"pod", "start", "-l"}) + session = podmanTest.Podman([]string{"pod", "start", podid1}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - session = podmanTest.Podman([]string{"pod", "stop", "-l"}) + session = podmanTest.Podman([]string{"pod", "stop", podid1}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -320,9 +322,6 @@ var _ = Describe("Podman prune", func() { create.WaitWithDefaultTimeout() Expect(create.ExitCode()).To(Equal(0)) - // Adding images should be pruned - podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true") - // Adding unused volume should not be pruned as volumes not set session = podmanTest.Podman([]string{"volume", "create"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/run_exit_test.go b/test/e2e/run_exit_test.go index d4b44ff23..40731142e 100644 --- a/test/e2e/run_exit_test.go +++ b/test/e2e/run_exit_test.go @@ -62,7 +62,6 @@ var _ = Describe("Podman run exit", func() { }) It("podman run exit 50", func() { - Skip(v2remotefail) result := podmanTest.Podman([]string{"run", ALPINE, "sh", "-c", "exit 50"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(50)) diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index 1f892d9f8..58091ff68 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -117,7 +117,7 @@ var _ = Describe("Podman run with volumes", func() { session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=tmpfs,target=" + dest, ALPINE, "grep", dest, "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(session.OutputToString()).To(ContainSubstring(dest + " rw,nosuid,nodev,noexec,relatime - tmpfs")) + Expect(session.OutputToString()).To(ContainSubstring(dest + " rw,nosuid,nodev,relatime - tmpfs")) session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=tmpfs,target=/etc/ssl,tmpcopyup", ALPINE, "ls", "/etc/ssl"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/untag_test.go b/test/e2e/untag_test.go index 43b874d47..dc1a6208e 100644 --- a/test/e2e/untag_test.go +++ b/test/e2e/untag_test.go @@ -40,7 +40,6 @@ var _ = Describe("Podman untag", func() { }) It("podman untag all", func() { - Skip(v2remotefail) session := podmanTest.PodmanNoCache([]string{"untag", ALPINE}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index 5d65a950f..3233e6f04 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -115,7 +115,8 @@ echo "got here -$rand-" EOF chmod 755 $mountpoint/myscript - # By default, volumes are mounted noexec. This should fail. + # By default, volumes are mounted exec, but we have manually added the + # noexec option. This should fail. # ARGH. Unfortunately, runc (used for cgroups v1) produces a different error local expect_rc=126 local expect_msg='.* OCI runtime permission denied.*' @@ -125,12 +126,12 @@ EOF expect_msg='.* exec user process caused.*permission denied' fi - run_podman ${expect_rc} run --rm --volume $myvolume:/vol:z $IMAGE /vol/myscript + run_podman ${expect_rc} run --rm --volume $myvolume:/vol:noexec,z $IMAGE /vol/myscript is "$output" "$expect_msg" "run on volume, noexec" - # With exec, it should pass - run_podman run --rm -v $myvolume:/vol:z,exec $IMAGE /vol/myscript - is "$output" "got here -$rand-" "script in volume is runnable with exec" + # With the default, it should pass + run_podman run --rm -v $myvolume:/vol:z $IMAGE /vol/myscript + is "$output" "got here -$rand-" "script in volume is runnable with default (exec)" # Clean up run_podman volume rm $myvolume diff --git a/troubleshooting.md b/troubleshooting.md index f04d9e9fa..167ee14c3 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -219,8 +219,15 @@ the system. #### Solution -SELinux provides a boolean `container_manage_cgroup`, which allows container -processes to write to the cgroup file system. Turn on this boolean, on SELinux separated systems, to allow systemd to run properly in the container. +Newer versions of Podman (2.0 or greater) support running init based containers +with a different SELinux labels, which allow the container process access to the +cgroup file system. This feature requires container-selinux-2.132 or newer +versions. + +Prior to Podman 2.0, the SELinux boolean `container_manage_cgroup` allows +container processes to write to the cgroup file system. Turn on this boolean, +on SELinux separated systems, to allow systemd to run properly in the container. +Only do this on systems running older versions of Podman. `setsebool -P container_manage_cgroup true` @@ -240,7 +247,7 @@ cannot find newuidmap: exec: "newuidmap": executable file not found in $PATH #### Solution -Install a version of shadow-utils that includes these executables. Note RHEL7 and Centos 7 will not have support for this until RHEL7.7 is released. +Install a version of shadow-utils that includes these executables. Note RHEL 7 and CentOS 7 will not have support for this until RHEL7.7 is released. ### 11) rootless setup user: invalid argument @@ -424,9 +431,10 @@ Choose one of the following: * Install the fuse-overlayfs package for your Linux Distribution. * Add `mount_program = "/usr/bin/fuse-overlayfs"` under `[storage.options]` in your `~/.config/containers/storage.conf` file. -### 17) rhel7-init based images don't work with cgroups v2 +### 17) RHEL 7 and CentOS 7 based `init` images don't work with cgroup v2 -The systemd version shipped in rhel7-init doesn't have support for cgroups v2. You'll need at least systemd 230. +The systemd version shipped in RHEL 7 and CentOS 7 doesn't have support for cgroup v2. Support for cgroup V2 requires version 230 of systemd or newer, which +was never shipped or supported on RHEL 7 or CentOS 7. #### Symptom ```console @@ -440,7 +448,15 @@ Error: non zero exit code: 1: OCI runtime error #### Solution You'll need to either: -* configure the host to use cgroups v1 +* configure the host to use cgroup v1 + +``` +On Fedora you can do: +# dnf install -y grubby +# grubby --update-kernel=ALL --args=”systemd.unified_cgroup_hierarchy=0" +# reboot +``` + * update the image to use an updated version of systemd. ### 18) rootless containers exit once the user session exits @@ -483,7 +499,7 @@ Unable to pull images ```console $ podman unshare cat /proc/self/uid_map - 0 1000 1 + 0 1000 1 ``` #### Solution @@ -496,8 +512,8 @@ Original command now returns ``` $ podman unshare cat /proc/self/uid_map - 0 1000 1 - 1 100000 65536 + 0 1000 1 + 1 100000 65536 ``` Reference [subuid](http://man7.org/linux/man-pages/man5/subuid.5.html) and [subgid](http://man7.org/linux/man-pages/man5/subgid.5.html) man pages for more detail. diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go index 48e621c99..7b60f8bb3 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/config.go @@ -89,6 +89,8 @@ type User struct { UID uint32 `json:"uid" platform:"linux,solaris"` // GID is the group id. GID uint32 `json:"gid" platform:"linux,solaris"` + // Umask is the umask for the init process. + Umask uint32 `json:"umask,omitempty" platform:"linux,solaris"` // AdditionalGids are additional group ids set for the container's process. AdditionalGids []uint32 `json:"additionalGids,omitempty" platform:"linux,solaris"` // Username is the user name. @@ -123,13 +125,26 @@ type Hook struct { Timeout *int `json:"timeout,omitempty"` } +// Hooks specifies a command that is run in the container at a particular event in the lifecycle of a container // Hooks for container setup and teardown type Hooks struct { - // Prestart is a list of hooks to be run before the container process is executed. + // Prestart is Deprecated. Prestart is a list of hooks to be run before the container process is executed. + // It is called in the Runtime Namespace Prestart []Hook `json:"prestart,omitempty"` + // CreateRuntime is a list of hooks to be run after the container has been created but before pivot_root or any equivalent operation has been called + // It is called in the Runtime Namespace + CreateRuntime []Hook `json:"createRuntime,omitempty"` + // CreateContainer is a list of hooks to be run after the container has been created but before pivot_root or any equivalent operation has been called + // It is called in the Container Namespace + CreateContainer []Hook `json:"createContainer,omitempty"` + // StartContainer is a list of hooks to be run after the start operation is called but before the container process is started + // It is called in the Container Namespace + StartContainer []Hook `json:"startContainer,omitempty"` // Poststart is a list of hooks to be run after the container process is started. + // It is called in the Runtime Namespace Poststart []Hook `json:"poststart,omitempty"` // Poststop is a list of hooks to be run after the container process exits. + // It is called in the Runtime Namespace Poststop []Hook `json:"poststop,omitempty"` } @@ -165,6 +180,8 @@ type Linux struct { // IntelRdt contains Intel Resource Director Technology (RDT) information for // handling resource constraints (e.g., L3 cache, memory bandwidth) for the container IntelRdt *LinuxIntelRdt `json:"intelRdt,omitempty"` + // Personality contains configuration for the Linux personality syscall + Personality *LinuxPersonality `json:"personality,omitempty"` } // LinuxNamespace is the configuration for a Linux namespace @@ -291,6 +308,8 @@ type LinuxMemory struct { Swappiness *uint64 `json:"swappiness,omitempty"` // DisableOOMKiller disables the OOM killer for out of memory conditions DisableOOMKiller *bool `json:"disableOOMKiller,omitempty"` + // Enables hierarchical memory accounting + UseHierarchy *bool `json:"useHierarchy,omitempty"` } // LinuxCPU for Linux cgroup 'cpu' resource management @@ -387,6 +406,28 @@ type LinuxDeviceCgroup struct { Access string `json:"access,omitempty"` } +// LinuxPersonalityDomain refers to a personality domain. +type LinuxPersonalityDomain string + +// LinuxPersonalityFlag refers to an additional personality flag. None are currently defined. +type LinuxPersonalityFlag string + +// Define domain and flags for Personality +const ( + // PerLinux is the standard Linux personality + PerLinux LinuxPersonalityDomain = "LINUX" + // PerLinux32 sets personality to 32 bit + PerLinux32 LinuxPersonalityDomain = "LINUX32" +) + +// LinuxPersonality represents the Linux personality syscall input +type LinuxPersonality struct { + // Domain for the personality + Domain LinuxPersonalityDomain `json:"domain"` + // Additional flags + Flags []LinuxPersonalityFlag `json:"flags,omitempty"` +} + // Solaris contains platform-specific configuration for Solaris application containers. type Solaris struct { // SMF FMRI which should go "online" before we start the container process. @@ -556,12 +597,16 @@ type VMImage struct { type LinuxSeccomp struct { DefaultAction LinuxSeccompAction `json:"defaultAction"` Architectures []Arch `json:"architectures,omitempty"` + Flags []LinuxSeccompFlag `json:"flags,omitempty"` Syscalls []LinuxSyscall `json:"syscalls,omitempty"` } // Arch used for additional architectures type Arch string +// LinuxSeccompFlag is a flag to pass to seccomp(2). +type LinuxSeccompFlag string + // Additional architectures permitted to be used for system calls // By default only the native architecture of the kernel is permitted const ( @@ -595,6 +640,7 @@ const ( ActErrno LinuxSeccompAction = "SCMP_ACT_ERRNO" ActTrace LinuxSeccompAction = "SCMP_ACT_TRACE" ActAllow LinuxSeccompAction = "SCMP_ACT_ALLOW" + ActLog LinuxSeccompAction = "SCMP_ACT_LOG" ) // LinuxSeccompOperator used to match syscall arguments in Seccomp @@ -621,9 +667,10 @@ type LinuxSeccompArg struct { // LinuxSyscall is used to match a syscall in Seccomp type LinuxSyscall struct { - Names []string `json:"names"` - Action LinuxSeccompAction `json:"action"` - Args []LinuxSeccompArg `json:"args,omitempty"` + Names []string `json:"names"` + Action LinuxSeccompAction `json:"action"` + ErrnoRet *uint `json:"errnoRet,omitempty"` + Args []LinuxSeccompArg `json:"args,omitempty"` } // LinuxIntelRdt has container runtime resource constraints for Intel RDT diff --git a/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go b/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go index b920fc1b3..596af0c2f 100644 --- a/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go +++ b/vendor/github.com/opencontainers/runtime-spec/specs-go/version.go @@ -8,7 +8,7 @@ const ( // VersionMinor is for functionality in a backwards-compatible manner VersionMinor = 0 // VersionPatch is for backwards-compatible bug fixes - VersionPatch = 1 + VersionPatch = 2 // VersionDev indicates development branch. Releases will be empty string. VersionDev = "-dev" diff --git a/vendor/github.com/seccomp/containers-golang/.gitignore b/vendor/github.com/seccomp/containers-golang/.gitignore index 2cad96a16..e433eef88 100644 --- a/vendor/github.com/seccomp/containers-golang/.gitignore +++ b/vendor/github.com/seccomp/containers-golang/.gitignore @@ -1,2 +1,2 @@ -default.json -fixtures +*.orig +generate diff --git a/vendor/github.com/seccomp/containers-golang/LICENSE b/vendor/github.com/seccomp/containers-golang/LICENSE new file mode 100644 index 000000000..bd465fcf0 --- /dev/null +++ b/vendor/github.com/seccomp/containers-golang/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2018-2019 github.com/seccomp authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/seccomp/containers-golang/Makefile b/vendor/github.com/seccomp/containers-golang/Makefile index 88569e87b..2d91917f9 100644 --- a/vendor/github.com/seccomp/containers-golang/Makefile +++ b/vendor/github.com/seccomp/containers-golang/Makefile @@ -1,3 +1,5 @@ +export GO111MODULE=off + TAGS ?= seccomp BUILDFLAGS := -tags "$(AUTOTAGS) $(TAGS)" GO := go @@ -5,14 +7,26 @@ PACKAGE := github.com/seccomp/containers-golang sources := $(wildcard *.go) -default.json: $(sources) +.PHONY: seccomp.json +seccomp.json: $(sources) $(GO) build -compiler gc $(BUILDFLAGS) ./cmd/generate.go $(GO) build -compiler gc ./cmd/generate.go $(GO) run ${BUILDFLAGS} cmd/generate.go -all: default.json +all: seccomp.json .PHONY: test-unit test-unit: - $(GO) test $(BUILDFLAGS) $(shell $(GO) list ./... | grep -v ^$(PACKAGE)/vendor) - $(GO) test $(shell $(GO) list ./... | grep -v ^$(PACKAGE)/vendor) + $(GO) test -v $(BUILDFLAGS) $(shell $(GO) list ./... | grep -v ^$(PACKAGE)/vendor) + $(GO) test -v $(shell $(GO) list ./... | grep -v ^$(PACKAGE)/vendor) + +.PHONY: vendor +vendor: + export GO111MODULE=on \ + $(GO) mod tidy && \ + $(GO) mod vendor && \ + $(GO) mod verify + +.PHONY: clean +clean: + rm -f generate diff --git a/vendor/github.com/seccomp/containers-golang/README.md b/vendor/github.com/seccomp/containers-golang/README.md index 1012baec3..a44238432 100644 --- a/vendor/github.com/seccomp/containers-golang/README.md +++ b/vendor/github.com/seccomp/containers-golang/README.md @@ -1,9 +1,13 @@ +# containers-golang + +[![CircleCI](https://circleci.com/gh/seccomp/containers-golang.svg?style=shield)](https://circleci.com/gh/seccomp/containers-golang) + `containers-golang` is a set of Go libraries used by container runtimes to generate and load seccomp mappings into the kernel. seccomp (short for secure computing mode) is a BPF based syscall filter language and present a more conventional function-call based filtering interface that should be familiar to, and easily adopted by, application developers. ## Building - make - Generates default.json file, which containes the whitelisted syscalls that can be used by container runtime engines like [CRI-O][cri-o], [Buildah][buildah], [Podman][podman] and [Docker][docker], and container runtimes like OCI [Runc][runc] to controll the syscalls available to containers. + make - Generates seccomp.json file, which contains the whitelisted syscalls that can be used by container runtime engines like [CRI-O][cri-o], [Buildah][buildah], [Podman][podman] and [Docker][docker], and container runtimes like OCI [Runc][runc] to controll the syscalls available to containers. ### Supported build tags @@ -13,13 +17,9 @@ seccomp (short for secure computing mode) is a BPF based syscall filter language When developing this library, please use `make` (or `make … BUILDTAGS=…`) to take advantage of the tests and validation. -## License - -ASL 2.0 - ## Contact -- IRC: #[CRI-O](irc://irc.freenode.net:6667/#cri-o) on freenode.net +- IRC: #[containers](irc://irc.freenode.net:6667/#containers) on freenode.net [cri-o]: https://github.com/kubernetes-incubator/cri-o/pulls [buildah]: https://github.com/projectatomic/buildah diff --git a/vendor/github.com/seccomp/containers-golang/go.mod b/vendor/github.com/seccomp/containers-golang/go.mod new file mode 100644 index 000000000..2b56d46fd --- /dev/null +++ b/vendor/github.com/seccomp/containers-golang/go.mod @@ -0,0 +1,16 @@ +module github.com/seccomp/containers-golang + +go 1.13 + +require ( + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/hashicorp/go-multierror v1.0.0 // indirect + github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 + github.com/opencontainers/runtime-tools v0.9.0 + github.com/opencontainers/selinux v1.3.0 // indirect + github.com/seccomp/libseccomp-golang v0.9.1 + github.com/sirupsen/logrus v1.4.2 // indirect + github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect + golang.org/x/sys v0.0.0-20190921190940-14da1ac737cc +) diff --git a/vendor/github.com/seccomp/containers-golang/go.sum b/vendor/github.com/seccomp/containers-golang/go.sum new file mode 100644 index 000000000..ba00acd09 --- /dev/null +++ b/vendor/github.com/seccomp/containers-golang/go.sum @@ -0,0 +1,48 @@ +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7 h1:Dliu5QO+4JYWu/yMshaMU7G3JN2POGpwjJN7gjy10Go= +github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1 h1:wY4pOY8fBdSIvs9+IDHC55thBuEulhzfSgKeC1yFvzQ= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20191007145322-19e92ca81777 h1:7CkKaORyxoXsM8z56r+M0wf3uCpVGVqx4CWq7oJ/4DY= +github.com/opencontainers/runtime-spec v1.0.2-0.20191007145322-19e92ca81777/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 h1:9mv9SC7GWmRWE0J/+oD8w3GsN2KYGKtg6uwLN7hfP5E= +github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.9.0 h1:FYgwVsKRI/H9hU32MJ/4MLOzXWodKK5zsQavY8NPMkU= +github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.2.2 h1:Kx9J6eDG5/24A6DtUquGSpJQ+m2MUTahn4FtGEe8bFg= +github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= +github.com/opencontainers/selinux v1.3.0 h1:xsI95WzPZu5exzA6JzkLSfdr/DilzOhCJOqGe5TgR0g= +github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46rlXTMfXGWBo= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg= +github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190921190940-14da1ac737cc h1:EinpED/Eb9JUgDi6pkoFjw+tz69c3lHUZr2+Va84S0w= +golang.org/x/sys v0.0.0-20190921190940-14da1ac737cc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/vendor/github.com/seccomp/containers-golang/seccomp.json b/vendor/github.com/seccomp/containers-golang/seccomp.json index fe9eda56a..4c84d981f 100644 --- a/vendor/github.com/seccomp/containers-golang/seccomp.json +++ b/vendor/github.com/seccomp/containers-golang/seccomp.json @@ -52,6 +52,8 @@ "syscalls": [ { "names": [ + "_llseek", + "_newselect", "accept", "accept4", "access", @@ -118,6 +120,8 @@ "ftruncate64", "futex", "futimesat", + "get_robust_list", + "get_thread_area", "getcpu", "getcwd", "getdents", @@ -143,12 +147,10 @@ "getresuid", "getresuid32", "getrlimit", - "get_robust_list", "getrusage", "getsid", "getsockname", "getsockopt", - "get_thread_area", "gettid", "gettimeofday", "getuid", @@ -159,13 +161,13 @@ "inotify_init1", "inotify_rm_watch", "io_cancel", - "ioctl", "io_destroy", "io_getevents", - "ioprio_get", - "ioprio_set", "io_setup", "io_submit", + "ioctl", + "ioprio_get", + "ioprio_set", "ipc", "kill", "lchown", @@ -176,7 +178,6 @@ "listen", "listxattr", "llistxattr", - "_llseek", "lremovexattr", "lseek", "lsetxattr", @@ -194,6 +195,7 @@ "mlockall", "mmap", "mmap2", + "mount", "mprotect", "mq_getsetattr", "mq_notify", @@ -210,9 +212,9 @@ "munlock", "munlockall", "munmap", + "name_to_handle_at", "nanosleep", "newfstatat", - "_newselect", "open", "openat", "pause", @@ -234,6 +236,7 @@ "readlink", "readlinkat", "readv", + "reboot", "recv", "recvfrom", "recvmmsg", @@ -253,11 +256,11 @@ "rt_sigsuspend", "rt_sigtimedwait", "rt_tgsigqueueinfo", + "sched_get_priority_max", + "sched_get_priority_min", "sched_getaffinity", "sched_getattr", "sched_getparam", - "sched_get_priority_max", - "sched_get_priority_min", "sched_getscheduler", "sched_rr_get_interval", "sched_setaffinity", @@ -277,6 +280,9 @@ "sendmmsg", "sendmsg", "sendto", + "set_robust_list", + "set_thread_area", + "set_tid_address", "setfsgid", "setfsgid32", "setfsuid", @@ -297,11 +303,8 @@ "setreuid", "setreuid32", "setrlimit", - "set_robust_list", "setsid", "setsockopt", - "set_thread_area", - "set_tid_address", "setuid", "setuid32", "setxattr", @@ -335,21 +338,24 @@ "time", "timer_create", "timer_delete", - "timerfd_create", - "timerfd_gettime", - "timerfd_settime", "timer_getoverrun", "timer_gettime", "timer_settime", + "timerfd_create", + "timerfd_gettime", + "timerfd_settime", "times", "tkill", "truncate", "truncate64", "ugetrlimit", "umask", + "umount", + "umount2", "uname", "unlink", "unlinkat", + "unshare", "utime", "utimensat", "utimes", @@ -359,12 +365,7 @@ "waitid", "waitpid", "write", - "writev", - "mount", - "umount2", - "reboot", - "name_to_handle_at", - "unshare" + "writev" ], "action": "SCMP_ACT_ALLOW", "args": [], @@ -770,4 +771,4 @@ "excludes": {} } ] -} +}
\ No newline at end of file diff --git a/vendor/github.com/seccomp/containers-golang/seccomp_default_linux.go b/vendor/github.com/seccomp/containers-golang/seccomp_default_linux.go index fde3cff75..e137a5887 100644 --- a/vendor/github.com/seccomp/containers-golang/seccomp_default_linux.go +++ b/vendor/github.com/seccomp/containers-golang/seccomp_default_linux.go @@ -1,5 +1,9 @@ // +build seccomp +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 2013-2018 Docker, Inc. + package seccomp // import "github.com/seccomp/containers-golang" import ( @@ -44,6 +48,8 @@ func DefaultProfile() *Seccomp { syscalls := []*Syscall{ { Names: []string{ + "_llseek", + "_newselect", "accept", "accept4", "access", @@ -110,6 +116,8 @@ func DefaultProfile() *Seccomp { "ftruncate64", "futex", "futimesat", + "get_robust_list", + "get_thread_area", "getcpu", "getcwd", "getdents", @@ -135,12 +143,10 @@ func DefaultProfile() *Seccomp { "getresuid", "getresuid32", "getrlimit", - "get_robust_list", "getrusage", "getsid", "getsockname", "getsockopt", - "get_thread_area", "gettid", "gettimeofday", "getuid", @@ -151,13 +157,13 @@ func DefaultProfile() *Seccomp { "inotify_init1", "inotify_rm_watch", "io_cancel", - "ioctl", "io_destroy", "io_getevents", - "ioprio_get", - "ioprio_set", "io_setup", "io_submit", + "ioctl", + "ioprio_get", + "ioprio_set", "ipc", "kill", "lchown", @@ -168,7 +174,6 @@ func DefaultProfile() *Seccomp { "listen", "listxattr", "llistxattr", - "_llseek", "lremovexattr", "lseek", "lsetxattr", @@ -206,7 +211,6 @@ func DefaultProfile() *Seccomp { "name_to_handle_at", "nanosleep", "newfstatat", - "_newselect", "open", "openat", "pause", @@ -248,11 +252,11 @@ func DefaultProfile() *Seccomp { "rt_sigsuspend", "rt_sigtimedwait", "rt_tgsigqueueinfo", + "sched_get_priority_max", + "sched_get_priority_min", "sched_getaffinity", "sched_getattr", "sched_getparam", - "sched_get_priority_max", - "sched_get_priority_min", "sched_getscheduler", "sched_rr_get_interval", "sched_setaffinity", @@ -272,6 +276,9 @@ func DefaultProfile() *Seccomp { "sendmmsg", "sendmsg", "sendto", + "set_robust_list", + "set_thread_area", + "set_tid_address", "setfsgid", "setfsgid32", "setfsuid", @@ -292,11 +299,8 @@ func DefaultProfile() *Seccomp { "setreuid", "setreuid32", "setrlimit", - "set_robust_list", "setsid", "setsockopt", - "set_thread_area", - "set_tid_address", "setuid", "setuid32", "setxattr", @@ -330,12 +334,12 @@ func DefaultProfile() *Seccomp { "time", "timer_create", "timer_delete", - "timerfd_create", - "timerfd_gettime", - "timerfd_settime", "timer_getoverrun", "timer_gettime", "timer_settime", + "timerfd_create", + "timerfd_gettime", + "timerfd_settime", "times", "tkill", "truncate", @@ -343,9 +347,11 @@ func DefaultProfile() *Seccomp { "ugetrlimit", "umask", "umount", + "umount2", "uname", "unlink", "unlinkat", + "unshare", "utime", "utimensat", "utimes", diff --git a/vendor/github.com/seccomp/containers-golang/seccomp_linux.go b/vendor/github.com/seccomp/containers-golang/seccomp_linux.go index 9a495e3e2..44dcd90b8 100644 --- a/vendor/github.com/seccomp/containers-golang/seccomp_linux.go +++ b/vendor/github.com/seccomp/containers-golang/seccomp_linux.go @@ -1,5 +1,9 @@ // +build seccomp +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 2013-2018 Docker, Inc. + package seccomp // import "github.com/seccomp/containers-golang" import ( @@ -9,6 +13,7 @@ import ( "github.com/opencontainers/runtime-spec/specs-go" libseccomp "github.com/seccomp/libseccomp-golang" + "golang.org/x/sys/unix" ) //go:generate go run -tags 'seccomp' generate.go @@ -22,11 +27,25 @@ func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) { func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) { var config Seccomp if err := json.Unmarshal([]byte(body), &config); err != nil { - return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err) + return nil, fmt.Errorf("decoding seccomp profile failed: %v", err) } return setupSeccomp(&config, rs) } +// LoadProfileFromBytes takes a byte slice and decodes the seccomp profile. +func LoadProfileFromBytes(body []byte, rs *specs.Spec) (*specs.LinuxSeccomp, error) { + config := &Seccomp{} + if err := json.Unmarshal(body, config); err != nil { + return nil, fmt.Errorf("decoding seccomp profile failed: %v", err) + } + return setupSeccomp(config, rs) +} + +// LoadProfileFromConfig takes a Seccomp struct and a spec to retrieve a LinuxSeccomp +func LoadProfileFromConfig(config *Seccomp, specgen *specs.Spec) (*specs.LinuxSeccomp, error) { + return setupSeccomp(config, specgen) +} + var nativeToSeccomp = map[string]Arch{ "amd64": ArchX86_64, "arm64": ArchAARCH64, @@ -127,21 +146,22 @@ Loop: } if call.Name != "" { - newConfig.Syscalls = append(newConfig.Syscalls, createSpecsSyscall(call.Name, call.Action, call.Args)) + newConfig.Syscalls = append(newConfig.Syscalls, createSpecsSyscall([]string{call.Name}, call.Action, call.Args, call.ErrnoRet)) } - for _, n := range call.Names { - newConfig.Syscalls = append(newConfig.Syscalls, createSpecsSyscall(n, call.Action, call.Args)) + if len(call.Names) > 0 { + newConfig.Syscalls = append(newConfig.Syscalls, createSpecsSyscall(call.Names, call.Action, call.Args, call.ErrnoRet)) } } return newConfig, nil } -func createSpecsSyscall(name string, action Action, args []*Arg) specs.LinuxSyscall { +func createSpecsSyscall(names []string, action Action, args []*Arg, errnoRet *uint) specs.LinuxSyscall { newCall := specs.LinuxSyscall{ - Names: []string{name}, - Action: specs.LinuxSeccompAction(action), + Names: names, + Action: specs.LinuxSeccompAction(action), + ErrnoRet: errnoRet, } // Loop through all the arguments of the syscall and convert them @@ -157,3 +177,15 @@ func createSpecsSyscall(name string, action Action, args []*Arg) specs.LinuxSysc } return newCall } + +// IsEnabled returns true if seccomp is enabled for the host. +func IsEnabled() bool { + // Check if Seccomp is supported, via CONFIG_SECCOMP. + if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL { + // Make sure the kernel has CONFIG_SECCOMP_FILTER. + if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL { + return true + } + } + return false +} diff --git a/vendor/github.com/seccomp/containers-golang/seccomp_unsupported.go b/vendor/github.com/seccomp/containers-golang/seccomp_unsupported.go index 279340426..936a9a641 100644 --- a/vendor/github.com/seccomp/containers-golang/seccomp_unsupported.go +++ b/vendor/github.com/seccomp/containers-golang/seccomp_unsupported.go @@ -1,5 +1,9 @@ // +build !seccomp +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 2013-2018 Docker, Inc. + package seccomp // import "github.com/seccomp/containers-golang" import ( @@ -22,3 +26,18 @@ func LoadProfile(body string, rs *specs.Spec) (*specs.LinuxSeccomp, error) { func GetDefaultProfile(rs *specs.Spec) (*specs.LinuxSeccomp, error) { return nil, fmt.Errorf("Seccomp not supported on this platform") } + +// LoadProfileFromBytes takes a byte slice and decodes the seccomp profile. +func LoadProfileFromBytes(body []byte, rs *specs.Spec) (*specs.LinuxSeccomp, error) { + return nil, fmt.Errorf("Seccomp not supported on this platform") +} + +// LoadProfileFromConfig takes a Seccomp struct and a spec to retrieve a LinuxSeccomp +func LoadProfileFromConfig(config *Seccomp, specgen *specs.Spec) (*specs.LinuxSeccomp, error) { + return nil, fmt.Errorf("Seccomp not supported on this platform") +} + +// IsEnabled returns true if seccomp is enabled for the host. +func IsEnabled() bool { + return false +} diff --git a/vendor/github.com/seccomp/containers-golang/types.go b/vendor/github.com/seccomp/containers-golang/types.go index b549a55fe..6651c423f 100644 --- a/vendor/github.com/seccomp/containers-golang/types.go +++ b/vendor/github.com/seccomp/containers-golang/types.go @@ -1,5 +1,9 @@ package seccomp // import "github.com/seccomp/containers-golang" +// SPDX-License-Identifier: Apache-2.0 + +// Copyright 2013-2018 Docker, Inc. + // Seccomp represents the config for a seccomp profile for syscall restriction. type Seccomp struct { DefaultAction Action `json:"defaultAction"` @@ -90,4 +94,5 @@ type Syscall struct { Comment string `json:"comment"` Includes Filter `json:"includes"` Excludes Filter `json:"excludes"` + ErrnoRet *uint `json:"errnoRet,omitempty"` } diff --git a/vendor/github.com/seccomp/containers-golang/vendor.conf b/vendor/github.com/seccomp/containers-golang/vendor.conf deleted file mode 100644 index 6111c475b..000000000 --- a/vendor/github.com/seccomp/containers-golang/vendor.conf +++ /dev/null @@ -1,9 +0,0 @@ -github.com/opencontainers/runtime-tools master -github.com/blang/semver master -github.com/hashicorp/go-multierror master -github.com/hashicorp/errwrap master -github.com/syndtr/gocapability master -github.com/xeipuuv/gojsonschema master -github.com/xeipuuv/gojsonreference master -github.com/xeipuuv/gojsonpointer master - diff --git a/vendor/modules.txt b/vendor/modules.txt index ed3c9df85..d396297c9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -412,7 +412,7 @@ github.com/opencontainers/runc/libcontainer/devices github.com/opencontainers/runc/libcontainer/system github.com/opencontainers/runc/libcontainer/user github.com/opencontainers/runc/libcontainer/utils -# github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7 +# github.com/opencontainers/runtime-spec v1.0.3-0.20200520003142-237cc4f519e2 github.com/opencontainers/runtime-spec/specs-go # github.com/opencontainers/runtime-tools v0.9.0 github.com/opencontainers/runtime-tools/error @@ -477,7 +477,7 @@ github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/udp/udpproxy github.com/rootless-containers/rootlesskit/pkg/port/portutil # github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 github.com/safchain/ethtool -# github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f +# github.com/seccomp/containers-golang v0.4.1 github.com/seccomp/containers-golang # github.com/seccomp/libseccomp-golang v0.9.1 github.com/seccomp/libseccomp-golang |