diff options
65 files changed, 1326 insertions, 538 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index acb2b2bd3..a7663f3e3 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -18,13 +18,11 @@ executes Buildah to perform container builds, and as such the Buildah maintainers are best equipped to handle these bugs. --> -**Is this a BUG REPORT or FEATURE REQUEST?**: +**Is this a BUG REPORT or FEATURE REQUEST? (leave only one on its own line)** -[//]: # Uncomment only one, leave it on its own line: +/kind bug -[//]: # **kind bug** - -[//]: # **kind feature** +/kind feature **Description** @@ -111,6 +111,9 @@ test/goecho/goecho: .gopathok $(wildcard test/goecho/*.go) podman: .gopathok $(PODMAN_VARLINK_DEPENDENCIES) $(GO) build -ldflags '$(LDFLAGS_PODMAN)' -tags "$(BUILDTAGS)" -o bin/$@ $(PROJECT)/cmd/podman +podman-remote: .gopathok $(PODMAN_VARLINK_DEPENDENCIES) + $(GO) build -ldflags '$(LDFLAGS_PODMAN)' -tags "$(BUILDTAGS) remoteclient" -o bin/$@ $(PROJECT)/cmd/podman + local-cross: $(CROSS_BUILD_TARGETS) bin/podman.cross.%: .gopathok @@ -353,7 +356,10 @@ cmd/podman/varlink/iopodman.go: cmd/podman/varlink/io.podman.varlink API.md: cmd/podman/varlink/io.podman.varlink $(GO) generate ./docs/... -validate: gofmt .gitvalidation +validate.completions: completions/bash/podman + . completions/bash/podman + +validate: gofmt .gitvalidation validate.completions build-all-new-commits: # Validate that all the commits build on top of $(GIT_BASE_BRANCH) @@ -363,6 +369,7 @@ build-all-new-commits: .gopathok \ binaries \ clean \ + validate.completions \ default \ docs \ gofmt \ @@ -23,23 +23,22 @@ At a high level, the scope of libpod and podman is the following: ## Roadmap -1. Python frontend for Varlink API +1. Allow the Podman CLI to use a Varlink backend to connect to remote Podman instances 1. Integrate libpod into CRI-O to replace its existing container management backend 1. Further work on the podman pod command 1. Further improvements on rootless containers -1. In-memory locking to replace file locks ## Out of scope * Signing and pushing images to various image storages. See [Skopeo](https://github.com/containers/skopeo/). -* Container Runtimes daemons for working with Kubernetes CRIs. +* Container Runtimes daemons for working with the Kubernetes CRI interface. See [CRI-O](https://github.com/kubernetes-sigs/cri-o). ## OCI Projects Plans The plan is to use OCI projects and best of breed libraries for different aspects: -- Runtime: [runc](https://github.com/opencontainers/runc) (or any OCI compliant runtime) and [oci runtime tools](https://github.com/opencontainers/runtime-tools) to generate the spec +- Runtime: [runc](https://github.com/opencontainers/runc) (or any OCI compliant runtime) and [OCI runtime tools](https://github.com/opencontainers/runtime-tools) to generate the spec - Images: Image management using [containers/image](https://github.com/containers/image) - Storage: Container and image storage is managed by [containers/storage](https://github.com/containers/storage) - Networking: Networking support through use of [CNI](https://github.com/containernetworking/cni) diff --git a/cmd/podman/common.go b/cmd/podman/common.go index 0fc9a6acc..d934c8699 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -28,6 +28,10 @@ var ( Name: "latest, l", Usage: "act on the latest pod podman is aware of", } + WorkDirFlag = cli.StringFlag{ + Name: "workdir, w", + Usage: "Working directory inside the container", + } ) const ( @@ -522,10 +526,7 @@ var createFlags = []cli.Flag{ Name: "volumes-from", Usage: "Mount volumes from the specified container(s) (default [])", }, - cli.StringFlag{ - Name: "workdir, w", - Usage: "Working `directory inside the container", - }, + WorkDirFlag, } func getFormat(c *cli.Context) (string, error) { diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 395a64b3b..d98b78bd4 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -15,13 +15,11 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" ann "github.com/containers/libpod/pkg/annotations" - "github.com/containers/libpod/pkg/apparmor" "github.com/containers/libpod/pkg/inspect" ns "github.com/containers/libpod/pkg/namespaces" "github.com/containers/libpod/pkg/rootless" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" - libpodVersion "github.com/containers/libpod/version" "github.com/docker/docker/pkg/signal" "github.com/docker/go-connections/nat" "github.com/docker/go-units" @@ -162,83 +160,9 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container return ctr, createConfig, nil } -// Checks if a user-specified AppArmor profile is loaded, or loads the default profile if -// AppArmor is enabled. -// Any interaction with AppArmor requires root permissions. -func loadAppArmor(config *cc.CreateConfig) error { - if rootless.IsRootless() { - noAAMsg := "AppArmor security is not available in rootless mode" - switch config.ApparmorProfile { - case "": - logrus.Warn(noAAMsg) - case "unconfined": - default: - return fmt.Errorf(noAAMsg) - } - return nil - } - - if config.ApparmorProfile == "" && apparmor.IsEnabled() { - // Unless specified otherwise, make sure that the default AppArmor - // profile is installed. To avoid redundantly loading the profile - // on each invocation, check if it's loaded before installing it. - // Suffix the profile with the current libpod version to allow - // loading the new, potentially updated profile after an update. - profile := fmt.Sprintf("%s-%s", apparmor.DefaultLibpodProfile, libpodVersion.Version) - - loadProfile := func() error { - isLoaded, err := apparmor.IsLoaded(profile) - if err != nil { - return err - } - if !isLoaded { - err = apparmor.InstallDefault(profile) - if err != nil { - return err - } - - } - return nil - } - - if err := loadProfile(); err != nil { - switch err { - case apparmor.ErrApparmorUnsupported: - // do not set the profile when AppArmor isn't supported - logrus.Debugf("AppArmor is not supported: setting empty profile") - default: - return err - } - } else { - logrus.Infof("Sucessfully loaded AppAmor profile '%s'", profile) - config.ApparmorProfile = profile - } - } else if config.ApparmorProfile != "" && config.ApparmorProfile != "unconfined" { - if !apparmor.IsEnabled() { - return fmt.Errorf("Profile specified but AppArmor is disabled on the host") - } - - isLoaded, err := apparmor.IsLoaded(config.ApparmorProfile) - if err != nil { - switch err { - case apparmor.ErrApparmorUnsupported: - return fmt.Errorf("Profile specified but AppArmor is not supported") - default: - return fmt.Errorf("Error checking if AppArmor profile is loaded: %v", err) - } - } - if !isLoaded { - return fmt.Errorf("The specified AppArmor profile '%s' is not loaded", config.ApparmorProfile) - } - } - - return nil -} - func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { var ( labelOpts []string - err error ) if config.PidMode.IsHost() { @@ -283,10 +207,6 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { } } - if err := loadAppArmor(config); err != nil { - return err - } - if config.SeccompProfilePath == "" { if _, err := os.Stat(libpod.SeccompOverridePath); err == nil { config.SeccompProfilePath = libpod.SeccompOverridePath @@ -304,7 +224,7 @@ func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { } } config.LabelOpts = labelOpts - return err + return nil } // isPortInPortBindings determines if an exposed host port is in user diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index 1dcb88dbd..073e72e64 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os" - "strings" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" @@ -35,6 +34,7 @@ var ( Usage: "Sets the username or UID used and optionally the groupname or GID for the specified command", }, LatestFlag, + WorkDirFlag, } execDescription = ` podman exec @@ -99,15 +99,7 @@ func execCmd(c *cli.Context) error { } // ENVIRONMENT VARIABLES - env := defaultEnvVariables - for _, e := range c.StringSlice("env") { - split := strings.SplitN(e, "=", 2) - if len(split) > 1 { - env[split[0]] = split[1] - } else { - env[split[0]] = "" - } - } + env := map[string]string{} if err := readKVStrings(env, []string{}, c.StringSlice("env")); err != nil { return errors.Wrapf(err, "unable to process environment variables") @@ -117,5 +109,5 @@ func execCmd(c *cli.Context) error { envs = append(envs, fmt.Sprintf("%s=%s", k, v)) } - return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user")) + return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user"), c.String("workdir")) } diff --git a/cmd/podman/image.go b/cmd/podman/image.go index e978b9cf5..557fc1056 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -20,6 +20,7 @@ var ( saveCommand, tagCommand, trustCommand, + signCommand, } imageDescription = "Manage images" diff --git a/cmd/podman/info.go b/cmd/podman/info.go index c0639725e..4b80f94db 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -1,10 +1,10 @@ package main import ( + "github.com/containers/libpod/libpod/adapter" "runtime" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/urfave/cli" @@ -39,18 +39,20 @@ func infoCmd(c *cli.Context) error { } info := map[string]interface{}{} - runtime, err := libpodruntime.GetRuntime(c) + localRuntime, err := adapter.GetRuntime(c) if err != nil { return errors.Wrapf(err, "could not get runtime") } - defer runtime.Shutdown(false) + defer localRuntime.Runtime.Shutdown(false) - infoArr, err := runtime.Info() + infoArr, err := localRuntime.Runtime.Info() if err != nil { return errors.Wrapf(err, "error getting info") } - if c.Bool("debug") { + // TODO This is no a problem child because we don't know if we should add information + // TODO about the client or the backend. Only do for traditional podman for now. + if !localRuntime.Remote && c.Bool("debug") { debugInfo := debugInfo(c) infoArr = append(infoArr, libpod.InfoData{Type: "debug", Data: debugInfo}) } diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 6c7d8eb52..a904ef75a 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -52,7 +52,7 @@ type PsOptions struct { // BatchContainerStruct is the return obkect from BatchContainer and contains // container related information type BatchContainerStruct struct { - ConConfig *libpod.Config + ConConfig *libpod.ContainerConfig ConState libpod.ContainerStatus ExitCode int32 Exited bool @@ -329,7 +329,7 @@ func PBatch(containers []*libpod.Container, workers int, opts PsOptions) []PsCon // locks. func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStruct, error) { var ( - conConfig *libpod.Config + conConfig *libpod.ContainerConfig conState libpod.ContainerStatus err error exitCode int32 diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go new file mode 100644 index 000000000..e7367a311 --- /dev/null +++ b/cmd/podman/sign.go @@ -0,0 +1,197 @@ +package main + +import ( + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/containers/image/signature" + "github.com/containers/image/transports" + "github.com/containers/image/transports/alltransports" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/trust" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + signFlags = []cli.Flag{ + cli.StringFlag{ + Name: "sign-by", + Usage: "Name of the signing key", + }, + cli.StringFlag{ + Name: "directory, d", + Usage: "Define an alternate directory to store signatures", + }, + } + + signDescription = "Create a signature file that can be used later to verify the image" + signCommand = cli.Command{ + Name: "sign", + Usage: "Sign an image", + Description: signDescription, + Flags: sortFlags(signFlags), + Action: signCmd, + ArgsUsage: "IMAGE-NAME [IMAGE-NAME ...]", + OnUsageError: usageErrorHandler, + } +) + +// SignatureStoreDir defines default directory to store signatures +const SignatureStoreDir = "/var/lib/containers/sigstore" + +func signCmd(c *cli.Context) error { + args := c.Args() + if len(args) < 1 { + return errors.Errorf("at least one image name must be specified") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not create runtime") + } + defer runtime.Shutdown(false) + + signby := c.String("sign-by") + if signby == "" { + return errors.Errorf("please provide an identity") + } + + var sigStoreDir string + if c.IsSet("directory") { + sigStoreDir = c.String("directory") + if _, err := os.Stat(sigStoreDir); err != nil { + return errors.Wrapf(err, "invalid directory %s", sigStoreDir) + } + } + + mech, err := signature.NewGPGSigningMechanism() + if err != nil { + return errors.Wrap(err, "error initializing GPG") + } + defer mech.Close() + if err := mech.SupportsSigning(); err != nil { + return errors.Wrap(err, "signing is not supported") + } + + systemRegistriesDirPath := trust.RegistriesDirPath(runtime.SystemContext()) + registryConfigs, err := trust.LoadAndMergeConfig(systemRegistriesDirPath) + if err != nil { + return errors.Wrapf(err, "error reading registry configuration") + } + + for _, signimage := range args { + srcRef, err := alltransports.ParseImageName(signimage) + if err != nil { + return errors.Wrapf(err, "error parsing image name") + } + rawSource, err := srcRef.NewImageSource(getContext(), runtime.SystemContext()) + if err != nil { + return errors.Wrapf(err, "error getting image source") + } + manifest, _, err := rawSource.GetManifest(getContext(), nil) + if err != nil { + return errors.Wrapf(err, "error getting manifest") + } + dockerReference := rawSource.Reference().DockerReference() + if dockerReference == nil { + return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) + } + + // create the signstore file + newImage, err := runtime.ImageRuntime().New(getContext(), signimage, runtime.GetConfig().SignaturePolicyPath, "", os.Stderr, nil, image.SigningOptions{SignBy: signby}, false) + if err != nil { + return errors.Wrapf(err, "error pulling image %s", signimage) + } + + registryInfo := trust.HaveMatchRegistry(rawSource.Reference().DockerReference().String(), registryConfigs) + if registryInfo != nil { + if sigStoreDir == "" { + sigStoreDir = registryInfo.SigStoreStaging + if sigStoreDir == "" { + sigStoreDir = registryInfo.SigStore + } + } + sigStoreDir, err = isValidSigStoreDir(sigStoreDir) + if err != nil { + return errors.Wrapf(err, "invalid signature storage %s", sigStoreDir) + } + } + if sigStoreDir == "" { + sigStoreDir = SignatureStoreDir + } + + repos, err := newImage.RepoDigests() + if err != nil { + return errors.Wrapf(err, "error calculating repo digests for %s", signimage) + } + if len(repos) == 0 { + logrus.Errorf("no repodigests associated with the image %s", signimage) + continue + } + + // create signature + newSig, err := signature.SignDockerManifest(manifest, dockerReference.String(), mech, signby) + if err != nil { + return errors.Wrapf(err, "error creating new signature") + } + + sigStoreDir = filepath.Join(sigStoreDir, strings.Replace(repos[0][strings.Index(repos[0], "/")+1:len(repos[0])], ":", "=", 1)) + if err := os.MkdirAll(sigStoreDir, 0751); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + logrus.Errorf("error creating directory %s: %s", sigStoreDir, err) + continue + } + } + sigFilename, err := getSigFilename(sigStoreDir) + if err != nil { + logrus.Errorf("error creating sigstore file: %v", err) + continue + } + err = ioutil.WriteFile(filepath.Join(sigStoreDir, sigFilename), newSig, 0644) + if err != nil { + logrus.Errorf("error storing signature for %s", rawSource.Reference().DockerReference().String()) + continue + } + } + return nil +} + +func getSigFilename(sigStoreDirPath string) (string, error) { + sigFileSuffix := 1 + sigFiles, err := ioutil.ReadDir(sigStoreDirPath) + if err != nil { + return "", err + } + sigFilenames := make(map[string]bool) + for _, file := range sigFiles { + sigFilenames[file.Name()] = true + } + for { + sigFilename := "signature-" + strconv.Itoa(sigFileSuffix) + if _, exists := sigFilenames[sigFilename]; !exists { + return sigFilename, nil + } + sigFileSuffix++ + } +} + +func isValidSigStoreDir(sigStoreDir string) (string, error) { + writeURIs := map[string]bool{"file": true} + url, err := url.Parse(sigStoreDir) + if err != nil { + return sigStoreDir, errors.Wrapf(err, "invalid directory %s", sigStoreDir) + } + _, exists := writeURIs[url.Scheme] + if !exists { + return sigStoreDir, errors.Errorf("writing to %s is not supported. Use a supported scheme", sigStoreDir) + } + sigStoreDir = url.Path + return sigStoreDir, nil +} diff --git a/cmd/podman/start.go b/cmd/podman/start.go index 8bb386c68..df34deec2 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -27,9 +27,9 @@ var ( Name: "interactive, i", Usage: "Keep STDIN open even if not attached", }, - cli.BoolFlag{ + cli.BoolTFlag{ Name: "sig-proxy", - Usage: "proxy received signals to the process", + Usage: "proxy received signals to the process (default true if attaching, false otherwise)", }, LatestFlag, } @@ -67,8 +67,14 @@ func startCmd(c *cli.Context) error { return err } - if c.Bool("sig-proxy") && !attach { - return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") + sigProxy := c.BoolT("sig-proxy") + + if sigProxy && !attach { + if c.IsSet("sig-proxy") { + return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") + } else { + sigProxy = false + } } runtime, err := libpodruntime.GetRuntime(c) @@ -111,7 +117,7 @@ func startCmd(c *cli.Context) error { } // attach to the container and also start it not already running - err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), !ctrRunning) + err = startAttachCtr(ctr, os.Stdout, os.Stderr, inputStream, c.String("detach-keys"), sigProxy, !ctrRunning) if ctrRunning { return err } diff --git a/completions/bash/podman b/completions/bash/podman index d65f54690..6333dfdf2 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -32,6 +32,9 @@ __podman_containers() { __podman_q ps --format "$format" "$@" } +__podman_list_registries() { + sed -n -e '/registries.*=/ {s/.*\[\([^]]*\).*/\1/p;q}' /etc/containers/registries.conf | sed -e "s/[,']//g" +} # __podman_pods returns a list of pods. Additional options to # `podman pod ps` may be specified in order to filter the list, e.g. @@ -365,6 +368,7 @@ __podman_subcommands() { local subcommands="$1" local counter=$(($command_pos + 1)) + while [ $counter -lt $cword ]; do case "${words[$counter]}" in $(__podman_to_extglob "$subcommands") ) @@ -1111,6 +1115,8 @@ _podman_exec() { --env --user -u + --workdir + -w " local boolean_options=" --help @@ -1294,7 +1300,9 @@ _podman_image() { push rm save + sign tag + trust " local aliases=" list @@ -2354,6 +2362,92 @@ _podman_container_runlabel() { esac } +_podman_image_sign() { + local options_with_args=" + -d + --directory + --sign-by + " + local boolean_options=" + --help + -h + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images + ;; + esac +} + +_podman_image_trust_set() { + echo hello + local options_with_args=" + -f + --type + --pubkeysfile + " + local boolean_options=" + --help + -h + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + COMPREPLY=($(compgen -W "default $( __podman_list_registries )" -- "$cur")) + ;; + esac +} + +_podman_image_trust_show() { + local options_with_args=" + " + local boolean_options=" + --help + -h + -j + --json + --raw + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images + ;; + esac +} + +_podman_image_trust() { + local boolean_options=" + --help + -h + " + subcommands=" + set + show + " + local aliases=" + list + " + command=image_trust + __podman_subcommands "$subcommands $aliases" && return + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + ;; + *) + COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) + ;; + esac +} + _podman_images_prune() { local options_with_args=" " @@ -2362,6 +2456,11 @@ _podman_images_prune() { -h --help " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + esac } _podman_container_prune() { @@ -2380,6 +2479,15 @@ _podman_container_exists() { local boolean_options=" " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + __podman_complete_images + ;; + esac + } _podman_pod_exists() { @@ -2811,6 +2919,7 @@ _podman_podman() { export generate history + image images import info diff --git a/contrib/cirrus/ooe.sh b/contrib/cirrus/ooe.sh index d79e574b2..3c8a0409d 100755 --- a/contrib/cirrus/ooe.sh +++ b/contrib/cirrus/ooe.sh @@ -7,10 +7,10 @@ set -eo pipefail -SCRIPT_PATH="$0" +SCRIPT_BASEDIR="$(basename $0)" badusage() { - echo "Incorrect usage: $(basename $SCRIPT_PATH) <command> [options]" > /dev/stderr + echo "Incorrect usage: $SCRIPT_BASEDIR) <command> [options]" > /dev/stderr echo "ERROR: $1" exit 121 } @@ -18,7 +18,7 @@ badusage() { COMMAND="$@" [[ -n "$COMMAND" ]] || badusage "No command specified" -OUTPUT_TMPFILE="$(mktemp -p '' $(basename $0)_output_XXXX)" +OUTPUT_TMPFILE="$(mktemp -p '' ${SCRIPT_BASEDIR}_output_XXXX)" output_on_error() { RET=$? set +e diff --git a/docs/libpod.conf.5.md b/docs/libpod.conf.5.md index c02d247fb..98eb5bece 100644 --- a/docs/libpod.conf.5.md +++ b/docs/libpod.conf.5.md @@ -37,7 +37,9 @@ libpod to manage containers. For the bind-mount conditions, only mounts explicitly requested by the caller via `--volume` are considered. Bind mounts that libpod inserts by default (e.g. `/dev/shm`) are not considered. - If `hooks_dir` is unset for root callers, Podman and libpod will currently default to `/usr/share/containers/oci/hooks.d` and `/etc/containers/oci/hooks.d` in order of increasing precedence. Using these defaults is deprecated, and callers should migrate to explicitly setting `hooks_dir`. + Podman and libpod currently support an additional `precreate` state which is called before the runtime's `create` operation. Unlike the other stages, which receive the container state on their standard input, `precreate` hooks receive the proposed runtime configuration on their standard input. They may alter that configuration as they see fit, and write the altered form to their standard output. + + **WARNING**: the `precreate` hook lets you do powerful things, such as adding additional mounts to the runtime configuration. That power also makes it easy to break things. Before reporting libpod errors, try running your container with `precreate` hooks disabled to see if the problem is due to one of your hooks. **static_dir**="" Directory for persistent libpod files (database, etc) diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 3a75a4b00..178542f0d 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -29,7 +29,7 @@ option can be set multiple times. Add an annotation to the container. The format is key=value. The **--annotation** option can be set multiple times. -**-a**, **--attach**=[] +**--attach**, **-a**=[] Attach to STDIN, STDOUT or STDERR. @@ -158,7 +158,7 @@ If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1` then processes in your container will only use memory from the first two memory nodes. -**-d**, **--detach**=*true*|*false* +**--detach**, **-d**=*true*|*false* Detached mode: run the container in the background and print the new container ID. The default is *false*. @@ -230,7 +230,7 @@ ENTRYPOINT. You need to specify multi option commands in the form of a json string. -**-e**, **--env**=[] +**--env**, **-e**=[] Set environment variables @@ -284,7 +284,7 @@ Run an init inside the container that forwards signals and reaps processes. Path to the container-init binary. -**-i**, **--interactive**=*true*|*false* +**--interactive**, **-i**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. @@ -315,7 +315,7 @@ is not limited. If you specify a limit, it may be rounded up to a multiple of the operating system's page size and the value can be very large, millions of trillions. -**-l**, **--label**=[] +**--label**, **-l**=[] Add metadata to a container (e.g., --label com.example.key=value) @@ -347,7 +347,7 @@ according to RFC4862. Not currently supported -**-m**, **--memory**="" +**--memory**, **-m**="" Memory limit (format: <number>[<unit>], where unit = b, k, m or g) @@ -426,7 +426,7 @@ to the container with **--name** then it will generate a random string name. The name is useful any place you need to identify a container. This works for both background and foreground containers. -**--net**, **--network**="*bridge*" +**--network**, **--net**="*bridge*" Set the Network mode for the container 'bridge': create a network stack on the default bridge @@ -480,7 +480,7 @@ to all devices on the host, turns off graphdriver mount options, as well as turning off most of the security measures protecting the host from the container. -**-p**, **--publish**=[] +**--publish**, **-p**=[] Publish a container's port, or range of ports, to the host @@ -492,7 +492,7 @@ but not `podman run -p 1230-1236:1230-1240 --name RangeContainerPortsBiggerThanR With ip: `podman run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage` Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT` -**-P**, **--publish-all**=*true*|*false* +**--publish-all**, **-P**=*true*|*false* Publish all exposed ports to random ports on the host interfaces. The default is *false*. @@ -621,7 +621,7 @@ options are the same as the Linux default `mount` flags. If you do not specify any options, the systems uses the following options: `rw,noexec,nosuid,nodev,size=65536k`. -**-t**, **--tty**=*true*|*false* +**--tty**, **-t**=*true*|*false* Allocate a pseudo-TTY. The default is *false*. @@ -642,7 +642,7 @@ The following example maps uids 0-2000 in the container to the uids 30000-31999 Ulimit options -**-u**, **--user**="" +**--user**, **-u**="" Sets the username or UID used and optionally the groupname or GID for the specified command. @@ -665,7 +665,7 @@ Set the UTS mode for the container **ns**: specify the usernamespace to use. Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. -**-v**|**--volume**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] +**--volume**, **-v**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman @@ -764,7 +764,7 @@ If the location of the volume from the source container overlaps with data residing on a target container, then the volume hides that data on the target. -**-w**, **--workdir**="" +**--workdir**, **-w**="" Working directory inside the container diff --git a/docs/podman-exec.1.md b/docs/podman-exec.1.md index 284fa5a4a..77317b0ca 100644 --- a/docs/podman-exec.1.md +++ b/docs/podman-exec.1.md @@ -38,6 +38,14 @@ Sets the username or UID used and optionally the groupname or GID for the specif The following examples are all valid: --user [user | user:group | uid | uid:gid | user:gid | uid:group ] +**--workdir**, **-w**="" + +Working directory inside the container + +The default working directory for running binaries within a container is the root directory (/). +The image developer can set a different default with the WORKDIR instruction, which can be overridden +when creating the container. + ## SEE ALSO podman(1), podman-run(1) diff --git a/docs/podman-image-sign.1.md b/docs/podman-image-sign.1.md new file mode 100644 index 000000000..232bc87fe --- /dev/null +++ b/docs/podman-image-sign.1.md @@ -0,0 +1,52 @@ +% podman-image-sign(1) + +# NAME +podman-image-sign- Create a signature for an image + +# SYNOPSIS +**podman image sign** +[**--help**|**-h**] +[**--directory**|**-d**] +[**--sign-by**] +[ IMAGE... ] + +# DESCRIPTION +**podmain image sign** will create a local signature for one or more local images that have +been pulled from a registry. The signature will be written to a directory +derived from the registry configuration files in /etc/containers/registries.d. By default, the signature will be written into /var/lib/containers/sigstore directory. + +# OPTIONS +**--help** **-h** + Print usage statement. + +**--directory** **-d** + Store the signatures in the specified directory. Default: /var/lib/containers/sigstore + +**--sign-by** + Override the default identity of the signature. + +# EXAMPLES +Sign the busybox image with the identify of foo@bar.com with a user's keyring and save the signature in /tmp/signatures/. + + sudo podman image sign --sign-by foo@bar.com --directory /tmp/signatures docker://privateregistry.example.com/foobar + +# RELATED CONFIGURATION + +The write (and read) location for signatures is defined in YAML-based +configuration files in /etc/containers/registries.d/. When you sign +an image, podman will use those configuration files to determine +where to write the signature based on the the name of the originating +registry or a default storage value unless overriden with the --directory +option. For example, consider the following configuration file. + +docker: + privateregistry.example.com: + sigstore: file:///var/lib/containers/sigstore + +When signing an image preceeded with the registry name 'privateregistry.example.com', +the signature will be written into subdirectories of +/var/lib/containers/sigstore/privateregistry.example.com. The use of 'sigstore' also means +the signature will be 'read' from that same location on a pull-related function. + +# HISTORY +November 2018, Originally compiled by Qi Wang (qiwan at redhat dot com) diff --git a/docs/podman-image.1.md b/docs/podman-image.1.md index 19893dfda..5a0c4e5f9 100644 --- a/docs/podman-image.1.md +++ b/docs/podman-image.1.md @@ -27,7 +27,8 @@ The image command allows you to manage images | rm | [podman-rm(1)](podman-rmi.1.md) | Removes one or more locally stored images. | | save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. | | tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. | -| trust | [podman-image-trust(1)](podman-image-trust.1.md) | Manage container image trust policy. +| trust | [podman-image-trust(1)](podman-image-trust.1.md) | Manage container image trust policy. | +| sign | [podman-image-sign(1)](podman-image-sign.1.md) | Sign an image. | ## SEE ALSO podman diff --git a/docs/podman-info.1.md b/docs/podman-info.1.md index 478f79467..836a2c420 100644 --- a/docs/podman-info.1.md +++ b/docs/podman-info.1.md @@ -61,6 +61,7 @@ registries: - docker.io - registry.access.redhat.com store: + ConfigFile: /etc/containers/storage.conf ContainerStore: number: 37 GraphDriverName: overlay diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 971b8829a..8b96ea6d9 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -41,7 +41,7 @@ option can be set multiple times. Add an annotation to the container. The format is key=value. The **--annotation** option can be set multiple times. -**-a**, **--attach**=[] +**--attach**, **-a**=[] Attach to STDIN, STDOUT or STDERR. @@ -162,7 +162,7 @@ If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1` then processes in your container will only use memory from the first two memory nodes. -**-d**, **--detach**=*true*|*false* +**--detach**, **-d**=*true*|*false* Detached mode: run the container in the background and print the new container ID. The default is *false*. @@ -235,7 +235,7 @@ ENTRYPOINT. You need to specify multi option commands in the form of a json string. -**-e**, **--env**=[] +**--env**, **-e**=[] Set environment variables @@ -293,7 +293,7 @@ Run an init inside the container that forwards signals and reaps processes. Path to the container-init binary. -**-i**, **--interactive**=*true*|*false* +**--interactive**, **-i**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. @@ -327,7 +327,7 @@ is not limited. If you specify a limit, it may be rounded up to a multiple of the operating system's page size and the value can be very large, millions of trillions. -**-l**, **--label**=[] +**--label**, **-l**=[] Add metadata to a container (e.g., --label com.example.key=value) @@ -359,7 +359,7 @@ according to RFC4862. Not currently supported -**-m**, **--memory**="" +**--memory**, **-m**="" Memory limit (format: <number>[<unit>], where unit = b, k, m or g) @@ -408,7 +408,7 @@ to the container with **--name** then it will generate a random string name. The name is useful any place you need to identify a container. This works for both background and foreground containers. -**--net**, **--network**="*bridge*" +**--network**, **--net**="*bridge*" Set the Network mode for the container: - `bridge`: create a network stack on the default bridge @@ -464,7 +464,7 @@ to all devices on the host, turns off graphdriver mount options, as well as turning off most of the security measures protecting the host from the container. -**-p**, **--publish**=[] +**--publish**, **-p**=[] Publish a container's port, or range of ports, to the host @@ -480,7 +480,7 @@ With ip: `podman run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t s Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT` -**-P**, **--publish-all**=*true*|*false* +**--publish-all**, **-P**=*true*|*false* Publish all exposed ports to random ports on the host interfaces. The default is *false*. @@ -623,7 +623,7 @@ options are the same as the Linux default `mount` flags. If you do not specify any options, the systems uses the following options: `rw,noexec,nosuid,nodev,size=65536k`. -**-t**, **--tty**=*true*|*false* +**--tty**, **-t**=*true*|*false* Allocate a pseudo-TTY. The default is *false*. @@ -645,7 +645,7 @@ The example maps uids 0-2000 in the container to the uids 30000-31999 on the hos Ulimit options -**-u**, **--user**="" +**--user**, **-u**="" Sets the username or UID used and optionally the groupname or GID for the specified command. @@ -703,7 +703,7 @@ Current supported mount TYPES are bind, and tmpfs. · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux. -**-v**|**--volume**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] +**--volume**, **-v**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the podman @@ -802,7 +802,7 @@ If the location of the volume from the source container overlaps with data residing on a target container, then the volume hides that data on the target. -**-w**, **--workdir**="" +**--workdir**, **-w**="" Working directory inside the container diff --git a/docs/podman-start.1.md b/docs/podman-start.1.md index cfd44ac3a..f16a20efa 100644 --- a/docs/podman-start.1.md +++ b/docs/podman-start.1.md @@ -35,7 +35,7 @@ to run containers such as CRI-O, the last started container could be from either **--sig-proxy**=*true*|*false* -Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is false. +Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is *true* when attaching, *false* otherwise. ## EXAMPLE diff --git a/docs/podman.1.md b/docs/podman.1.md index bde349e6f..a73ebb55e 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -43,6 +43,10 @@ For the bind-mount conditions, only mounts explicitly requested by the caller vi If `--hooks-dir` is unset for root callers, Podman and libpod will currently default to `/usr/share/containers/oci/hooks.d` and `/etc/containers/oci/hooks.d` in order of increasing precedence. Using these defaults is deprecated, and callers should migrate to explicitly setting `--hooks-dir`. +Podman and libpod currently support an additional `precreate` state which is called before the runtime's `create` operation. Unlike the other stages, which receive the container state on their standard input, `precreate` hooks receive the proposed runtime configuration on their standard input. They may alter that configuration as they see fit, and write the altered form to their standard output. + +**WARNING**: the `precreate` hook lets you do powerful things, such as adding additional mounts to the runtime configuration. That power also makes it easy to break things. Before reporting libpod errors, try running your container with `precreate` hooks disabled to see if the problem is due to one of your hooks. + **--log-level** Log messages above specified level: debug, info, warn, error (default), fatal or panic diff --git a/libpod/adapter/client.go b/libpod/adapter/client.go new file mode 100644 index 000000000..383c242c9 --- /dev/null +++ b/libpod/adapter/client.go @@ -0,0 +1,16 @@ +// +build remoteclient + +package adapter + +import ( + "github.com/varlink/go/varlink" +) + +// Connect provides a varlink connection +func (r RemoteRuntime) Connect() (*varlink.Connection, error) { + connection, err := varlink.NewConnection("unix:/run/podman/io.podman") + if err != nil { + return nil, err + } + return connection, nil +} diff --git a/libpod/adapter/info_remote.go b/libpod/adapter/info_remote.go new file mode 100644 index 000000000..3b691ed17 --- /dev/null +++ b/libpod/adapter/info_remote.go @@ -0,0 +1,56 @@ +// +build remoteclient + +package adapter + +import ( + "encoding/json" + + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod" +) + +// Info returns information for the host system and its components +func (r RemoteRuntime) Info() ([]libpod.InfoData, error) { + // TODO the varlink implementation for info should be updated to match the output for regular info + var ( + reply []libpod.InfoData + hostInfo map[string]interface{} + store map[string]interface{} + ) + + registries := make(map[string]interface{}) + insecureRegistries := make(map[string]interface{}) + conn, err := r.Connect() + if err != nil { + return nil, err + } + defer conn.Close() + info, err := iopodman.GetInfo().Call(conn) + if err != nil { + return nil, err + } + + // info.host -> map[string]interface{} + h, err := json.Marshal(info.Host) + if err != nil { + return nil, err + } + json.Unmarshal(h, &hostInfo) + + // info.store -> map[string]interface{} + s, err := json.Marshal(info.Store) + if err != nil { + return nil, err + } + json.Unmarshal(s, &store) + + registries["registries"] = info.Registries + insecureRegistries["registries"] = info.Insecure_registries + + // Add everything to the reply + reply = append(reply, libpod.InfoData{Type: "host", Data: hostInfo}) + reply = append(reply, libpod.InfoData{Type: "registries", Data: registries}) + reply = append(reply, libpod.InfoData{Type: "insecure registries", Data: insecureRegistries}) + reply = append(reply, libpod.InfoData{Type: "store", Data: store}) + return reply, nil +} diff --git a/libpod/adapter/runtime.go b/libpod/adapter/runtime.go new file mode 100644 index 000000000..b6db51071 --- /dev/null +++ b/libpod/adapter/runtime.go @@ -0,0 +1,26 @@ +// +build !remoteclient + +package adapter + +import ( + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/urfave/cli" +) + +// LocalRuntime describes a typical libpod runtime +type LocalRuntime struct { + Runtime *libpod.Runtime + Remote bool +} + +// GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it +func GetRuntime(c *cli.Context) (*LocalRuntime, error) { + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return nil, err + } + return &LocalRuntime{ + Runtime: runtime, + }, nil +} diff --git a/libpod/adapter/runtime_remote.go b/libpod/adapter/runtime_remote.go new file mode 100644 index 000000000..715728d21 --- /dev/null +++ b/libpod/adapter/runtime_remote.go @@ -0,0 +1,28 @@ +// +build remoteclient + +package adapter + +import "github.com/urfave/cli" + +// RemoteRuntime describes a wrapper runtime struct +type RemoteRuntime struct{} + +// LocalRuntime describes a typical libpod runtime +type LocalRuntime struct { + Runtime *RemoteRuntime + Remote bool +} + +// GetRuntime returns a LocalRuntime struct with the actual runtime embedded in it +func GetRuntime(c *cli.Context) (*LocalRuntime, error) { + runtime := RemoteRuntime{} + return &LocalRuntime{ + Runtime: &runtime, + Remote: true, + }, nil +} + +// Shutdown is a bogus wrapper for compat with the libpod runtime +func (r RemoteRuntime) Shutdown(force bool) error { + return nil +} diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index ba8f7375a..b154d8bda 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -322,7 +322,7 @@ func (s *BoltState) Container(id string) (*Container, error) { ctrID := []byte(id) ctr := new(Container) - ctr.config = new(Config) + ctr.config = new(ContainerConfig) ctr.state = new(containerState) db, err := s.getDBCon() @@ -358,7 +358,7 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { } ctr := new(Container) - ctr.config = new(Config) + ctr.config = new(ContainerConfig) ctr.state = new(containerState) db, err := s.getDBCon() @@ -751,7 +751,7 @@ func (s *BoltState) AllContainers() ([]*Container, error) { } ctr := new(Container) - ctr.config = new(Config) + ctr.config = new(ContainerConfig) ctr.state = new(containerState) if err := s.getContainerFromDB(id, ctr, ctrBucket); err != nil { @@ -1137,7 +1137,7 @@ func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) { // Iterate through all containers in the pod err = podCtrs.ForEach(func(id, val []byte) error { newCtr := new(Container) - newCtr.config = new(Config) + newCtr.config = new(ContainerConfig) newCtr.state = new(containerState) ctrs = append(ctrs, newCtr) diff --git a/libpod/common_test.go b/libpod/common_test.go index 882468a3a..efbb5f404 100644 --- a/libpod/common_test.go +++ b/libpod/common_test.go @@ -17,7 +17,7 @@ import ( func getTestContainer(id, name string, manager lock.Manager) (*Container, error) { ctr := &Container{ - config: &Config{ + config: &ContainerConfig{ ID: id, Name: name, RootfsImageID: id, @@ -165,8 +165,8 @@ func testContainersEqual(t *testing.T, a, b *Container, allowedEmpty bool) { require.NotNil(t, a.state) require.NotNil(t, b.state) - aConfig := new(Config) - bConfig := new(Config) + aConfig := new(ContainerConfig) + bConfig := new(ContainerConfig) aState := new(containerState) bState := new(containerState) diff --git a/libpod/container.go b/libpod/container.go index 4e5088b32..026eb1c4f 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -1,7 +1,9 @@ package libpod import ( + "encoding/json" "fmt" + "io/ioutil" "net" "os" "path/filepath" @@ -113,7 +115,7 @@ func (ns LinuxNS) String() string { // syncContainer() immediately after locking. // ffjson: skip type Container struct { - config *Config + config *ContainerConfig state *containerState @@ -128,6 +130,11 @@ type Container struct { rootlessSlirpSyncR *os.File rootlessSlirpSyncW *os.File + + // A restored container should have the same IP address as before + // being checkpointed. If requestedIP is set it will be used instead + // of config.StaticIP. + requestedIP net.IP } // containerState contains the current state of the container @@ -200,11 +207,11 @@ type ExecSession struct { PID int `json:"pid"` } -// Config contains all information that was used to create the +// ContainerConfig contains all information that was used to create the // container. It may not be changed once created. // It is stored, read-only, on disk // easyjson:json -type Config struct { +type ContainerConfig struct { Spec *spec.Spec `json:"spec"` ID string `json:"id"` Name string `json:"name"` @@ -385,8 +392,8 @@ func (t ContainerStatus) String() string { // Unlocked // Config returns the configuration used to create the container -func (c *Container) Config() *Config { - returnConfig := new(Config) +func (c *Container) Config() *ContainerConfig { + returnConfig := new(ContainerConfig) deepcopier.Copy(c.config).To(returnConfig) return returnConfig @@ -402,6 +409,30 @@ func (c *Container) Spec() *spec.Spec { return returnSpec } +// specFromState returns the unmarshalled json config of the container. If the +// config does not exist (e.g., because the container was never started) return +// the spec from the config. +func (c *Container) specFromState() (*spec.Spec, error) { + spec := c.config.Spec + + if f, err := os.Open(c.state.ConfigPath); err == nil { + content, err := ioutil.ReadAll(f) + if err != nil { + return nil, errors.Wrapf(err, "error reading container config") + } + if err := json.Unmarshal([]byte(content), &spec); err != nil { + return nil, errors.Wrapf(err, "error unmarshalling container config") + } + } else { + // ignore when the file does not exist + if !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "error opening container config") + } + } + + return spec, nil +} + // ID returns the container's ID func (c *Container) ID() string { return c.config.ID diff --git a/libpod/container_api.go b/libpod/container_api.go index 09bc46905..4eaf737b0 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -262,7 +262,7 @@ func (c *Container) Kill(signal uint) error { // Exec starts a new process inside the container // TODO allow specifying streams to attach to // TODO investigate allowing exec without attaching -func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) error { +func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir string) error { var capList []string locked := false @@ -324,7 +324,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user string) e logrus.Debugf("Creating new exec session in container %s with session id %s", c.ID(), sessionID) - execCmd, err := c.runtime.ociRuntime.execContainer(c, cmd, capList, env, tty, hostUser, sessionID) + execCmd, err := c.runtime.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, hostUser, sessionID) if err != nil { return errors.Wrapf(err, "error exec %s", c.ID()) } diff --git a/libpod/container_easyjson.go b/libpod/container_easyjson.go index ae0972cf6..f1cb09bcc 100644 --- a/libpod/container_easyjson.go +++ b/libpod/container_easyjson.go @@ -1,6 +1,6 @@ // +build seccomp ostree selinux varlink exclude_graphdriver_devicemapper -// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. +// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT package libpod @@ -1238,7 +1238,7 @@ func (v *ExecSession) UnmarshalJSON(data []byte) error { func (v *ExecSession) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson1dbef17bDecodeGithubComContainersLibpodLibpod1(l, v) } -func easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(in *jlexer.Lexer, out *Config) { +func easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(in *jlexer.Lexer, out *ContainerConfig) { isTopLevel := in.IsStart() if in.IsNull() { if isTopLevel { @@ -1722,7 +1722,7 @@ func easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(in *jlexer.Lexer, ou in.Consumed() } } -func easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(out *jwriter.Writer, in Config) { +func easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(out *jwriter.Writer, in ContainerConfig) { out.RawByte('{') first := true _ = first @@ -2427,26 +2427,26 @@ func easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(out *jwriter.Writer, } // MarshalJSON supports json.Marshaler interface -func (v Config) MarshalJSON() ([]byte, error) { +func (v ContainerConfig) MarshalJSON() ([]byte, error) { w := jwriter.Writer{} easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(&w, v) return w.Buffer.BuildBytes(), w.Error } // MarshalEasyJSON supports easyjson.Marshaler interface -func (v Config) MarshalEasyJSON(w *jwriter.Writer) { +func (v ContainerConfig) MarshalEasyJSON(w *jwriter.Writer) { easyjson1dbef17bEncodeGithubComContainersLibpodLibpod2(w, v) } // UnmarshalJSON supports json.Unmarshaler interface -func (v *Config) UnmarshalJSON(data []byte) error { +func (v *ContainerConfig) UnmarshalJSON(data []byte) error { r := jlexer.Lexer{Data: data} easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(&r, v) return r.Error() } // UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *Config) UnmarshalEasyJSON(l *jlexer.Lexer) { +func (v *ContainerConfig) UnmarshalEasyJSON(l *jlexer.Lexer) { easyjson1dbef17bDecodeGithubComContainersLibpodLibpod2(l, v) } func easyjson1dbef17bDecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in *jlexer.Lexer, out *ocicni.PortMapping) { diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 06a0c9f32..e2730c282 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -12,7 +12,10 @@ import ( func (c *Container) getContainerInspectData(size bool, driverData *inspect.Data) (*inspect.ContainerInspectData, error) { config := c.config runtimeInfo := c.state - spec := c.config.Spec + spec, err := c.specFromState() + if err != nil { + return nil, err + } // Process is allowed to be nil in the spec args := []string{} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index cc4c36bc9..69df33bc9 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1181,6 +1181,7 @@ func (c *Container) saveSpec(spec *spec.Spec) error { return nil } +// Warning: precreate hooks may alter 'config' in place. func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (extensionStageHooks map[string][]spec.Hook, err error) { var locale string var ok bool @@ -1209,13 +1210,13 @@ func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (exten } } + allHooks := make(map[string][]spec.Hook) if c.runtime.config.HooksDir == nil { if rootless.IsRootless() { return nil, nil } - allHooks := make(map[string][]spec.Hook) for _, hDir := range []string{hooks.DefaultDir, hooks.OverrideDir} { - manager, err := hooks.New(ctx, []string{hDir}, []string{"poststop"}, lang) + manager, err := hooks.New(ctx, []string{hDir}, []string{"precreate", "poststop"}, lang) if err != nil { if os.IsNotExist(err) { continue @@ -1233,19 +1234,32 @@ func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (exten allHooks[i] = hook } } - return allHooks, nil + } else { + manager, err := hooks.New(ctx, c.runtime.config.HooksDir, []string{"precreate", "poststop"}, lang) + if err != nil { + if os.IsNotExist(err) { + logrus.Warnf("Requested OCI hooks directory %q does not exist", c.runtime.config.HooksDir) + return nil, nil + } + return nil, err + } + + allHooks, err = manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0) + if err != nil { + return nil, err + } } - manager, err := hooks.New(ctx, c.runtime.config.HooksDir, []string{"poststop"}, lang) + hookErr, err := exec.RuntimeConfigFilter(ctx, allHooks["precreate"], config, exec.DefaultPostKillTimeout) if err != nil { - if os.IsNotExist(err) { - logrus.Warnf("Requested OCI hooks directory %q does not exist", c.runtime.config.HooksDir) - return nil, nil + logrus.Warnf("container %s: precreate hook: %v", c.ID(), err) + if hookErr != nil && hookErr != err { + logrus.Debugf("container %s: precreate hook (hook error): %v", c.ID(), hookErr) } return nil, err } - return manager.Hooks(config, c.Spec().Annotations, len(c.config.UserVolumes) > 0) + return allHooks, nil } // mount mounts the container's root filesystem diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 0745b7732..2f03d45ea 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -20,6 +20,7 @@ import ( cnitypes "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/plugins/pkg/ns" crioAnnotations "github.com/containers/libpod/pkg/annotations" + "github.com/containers/libpod/pkg/apparmor" "github.com/containers/libpod/pkg/criu" "github.com/containers/libpod/pkg/lookup" "github.com/containers/libpod/pkg/resolvconf" @@ -185,6 +186,13 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } + // Apply AppArmor checks and load the default profile if needed. + updatedProfile, err := apparmor.CheckProfileAndLoadDefault(c.config.Spec.Process.ApparmorProfile) + if err != nil { + return nil, err + } + g.SetProcessApparmorProfile(updatedProfile) + if err := c.makeBindMounts(); err != nil { return nil, err } @@ -228,10 +236,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } - if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil { - return nil, errors.Wrapf(err, "error setting up OCI Hooks") - } - // Bind builtin image volumes if c.config.Rootfs == "" && c.config.ImageVolumes { if err := c.addLocalVolumes(ctx, &g, execUser); err != nil { @@ -384,6 +388,12 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { logrus.Debugf("set root propagation to %q", rootPropagation) g.SetLinuxRootPropagation(rootPropagation) } + + // Warning: precreate hooks may alter g.Config in place. + if c.state.ExtensionStageHooks, err = c.setupOCIHooks(ctx, g.Config); err != nil { + return nil, errors.Wrapf(err, "error setting up OCI Hooks") + } + return g.Config, nil } @@ -547,10 +557,8 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti } } if IP != nil { - env := fmt.Sprintf("IP=%s", IP) // Tell CNI which IP address we want. - os.Setenv("CNI_ARGS", env) - logrus.Debugf("Restoring container with %s", env) + c.requestedIP = IP } } @@ -566,12 +574,6 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti return err } - // TODO: use existing way to request static IPs, once it is merged in ocicni - // https://github.com/cri-o/ocicni/pull/23/ - - // CNI_ARGS was used to request a certain IP address. Unconditionally remove it. - os.Unsetenv("CNI_ARGS") - // Read config jsonPath := filepath.Join(c.bundlePath(), "config.json") logrus.Debugf("generate.NewFromFile at %v", jsonPath) diff --git a/libpod/container_internal_test.go b/libpod/container_internal_test.go index 51fb58713..124f1d20e 100644 --- a/libpod/container_internal_test.go +++ b/libpod/container_internal_test.go @@ -28,7 +28,7 @@ func TestPostDeleteHooks(t *testing.T) { statePath := filepath.Join(dir, "state") copyPath := filepath.Join(dir, "copy") c := Container{ - config: &Config{ + config: &ContainerConfig{ ID: "123abc", Spec: &rspec.Spec{ Annotations: map[string]string{ diff --git a/libpod/image/image.go b/libpod/image/image.go index 3a6d0e305..2e12adb70 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -305,12 +305,24 @@ func (i *Image) Names() []string { } // RepoDigests returns a string array of repodigests associated with the image -func (i *Image) RepoDigests() []string { +func (i *Image) RepoDigests() ([]string, error) { var repoDigests []string + digest := i.Digest() + for _, name := range i.Names() { - repoDigests = append(repoDigests, strings.SplitN(name, ":", 2)[0]+"@"+i.Digest().String()) + named, err := reference.ParseNormalizedNamed(name) + if err != nil { + return nil, err + } + + canonical, err := reference.WithDigest(reference.TrimNamed(named), digest) + if err != nil { + return nil, err + } + + repoDigests = append(repoDigests, canonical.String()) } - return repoDigests + return repoDigests, nil } // Created returns the time the image was created diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index 91bb2411b..2a68fd273 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/containers/storage" + "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" ) @@ -192,6 +193,51 @@ func TestImage_MatchRepoTag(t *testing.T) { cleanup(workdir, ir) } +// TestImage_RepoDigests tests RepoDigest generation. +func TestImage_RepoDigests(t *testing.T) { + dgst, err := digest.Parse("sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc") + if err != nil { + t.Fatal(err) + } + + for _, test := range []struct { + name string + names []string + expected []string + }{ + { + name: "empty", + names: []string{}, + expected: nil, + }, + { + name: "tagged", + names: []string{"docker.io/library/busybox:latest"}, + expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, + }, + { + name: "digest", + names: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, + expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"}, + }, + } { + t.Run(test.name, func(t *testing.T) { + image := &Image{ + image: &storage.Image{ + Names: test.names, + Digest: dgst, + }, + } + actual, err := image.RepoDigests() + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, test.expected, actual) + }) + } +} + // Test_splitString tests the splitString function in image that // takes input and splits on / and returns the last array item func Test_splitString(t *testing.T) { diff --git a/libpod/info.go b/libpod/info.go index 7044eba6a..a98f93897 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -13,6 +13,7 @@ import ( "time" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/system" "github.com/pkg/errors" @@ -115,6 +116,7 @@ func (r *Runtime) hostInfo() (map[string]interface{}, error) { func (r *Runtime) storeInfo() (map[string]interface{}, error) { // lets say storage driver in use, number of images, number of containers info := map[string]interface{}{} + info["ConfigFile"] = util.StorageConfigFile() info["GraphRoot"] = r.store.GraphRoot() info["RunRoot"] = r.store.RunRoot() info["GraphDriverName"] = r.store.GraphDriverName() diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 43d0a61a4..a343bee6a 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -50,7 +50,16 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port // Create and configure a new network namespace for a container func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Result, error) { - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP) + var requestedIP net.IP + if ctr.requestedIP != nil { + requestedIP = ctr.requestedIP + // cancel request for a specific IP in case the container is reused later + ctr.requestedIP = nil + } else { + requestedIP = ctr.config.StaticIP + } + + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) results, err := r.netPlugin.SetUpPod(podNetwork) if err != nil { @@ -258,7 +267,16 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, ctr.config.StaticIP) + var requestedIP net.IP + if ctr.requestedIP != nil { + requestedIP = ctr.requestedIP + // cancel request for a specific IP in case the container is reused later + ctr.requestedIP = nil + } else { + requestedIP = ctr.config.StaticIP + } + + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) // The network may have already been torn down, so don't fail here, just log if err := r.netPlugin.TearDownPod(podNetwork); err != nil { diff --git a/libpod/oci.go b/libpod/oci.go index 093bfdd35..31c1a7e85 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -728,7 +728,7 @@ func (r *OCIRuntime) unpauseContainer(ctr *Container) error { // TODO: Add --detach support // TODO: Convert to use conmon // TODO: add --pid-file and use that to generate exec session tracking -func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, user, sessionID string) (*exec.Cmd, error) { +func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, cwd, user, sessionID string) (*exec.Cmd, error) { if len(cmd) == 0 { return nil, errors.Wrapf(ErrInvalidArg, "must provide a command to execute") } @@ -749,7 +749,9 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty args = append(args, "exec") - args = append(args, "--cwd", c.config.Spec.Process.Cwd) + if cwd != "" { + args = append(args, "--cwd", cwd) + } args = append(args, "--pid-file", c.execPidPath(sessionID)) diff --git a/libpod/runtime.go b/libpod/runtime.go index ab8d02a4f..c9471247c 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -692,25 +692,19 @@ func makeRuntime(runtime *Runtime) (err error) { } } - // Set up the lock manager - var manager lock.Manager lockPath := DefaultSHMLockPath if rootless.IsRootless() { lockPath = fmt.Sprintf("%s_%d", DefaultRootlessSHMLockPath, rootless.GetRootlessUID()) } - if doRefresh { - // If SHM locks already exist, delete them and reinitialize - if err := os.Remove(filepath.Join("/dev/shm", lockPath)); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error deleting existing libpod SHM segment %s", lockPath) - } - - manager, err = lock.NewSHMLockManager(lockPath, runtime.config.NumLocks) - if err != nil { - return err - } - } else { - manager, err = lock.OpenSHMLockManager(lockPath, runtime.config.NumLocks) - if err != nil { + // Set up the lock manager + manager, err := lock.OpenSHMLockManager(lockPath, runtime.config.NumLocks) + if err != nil { + if os.IsNotExist(errors.Cause(err)) { + manager, err = lock.NewSHMLockManager(lockPath, runtime.config.NumLocks) + if err != nil { + return err + } + } else { return err } } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index e6f2c962f..ab79fe5fb 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -47,7 +47,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. } ctr := new(Container) - ctr.config = new(Config) + ctr.config = new(ContainerConfig) ctr.state = new(containerState) ctr.config.ID = stringid.GenerateNonCryptoID() diff --git a/libpod/state_test.go b/libpod/state_test.go index ee4201b1c..4bd00ab55 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -156,7 +156,7 @@ func TestGetContainerPodSameIDFails(t *testing.T) { func TestAddInvalidContainerFails(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - err := state.AddContainer(&Container{config: &Config{ID: "1234"}}) + err := state.AddContainer(&Container{config: &ContainerConfig{ID: "1234"}}) assert.Error(t, err) }) } @@ -756,7 +756,7 @@ func TestUpdateContainerNotInDatabaseReturnsError(t *testing.T) { func TestUpdateInvalidContainerReturnsError(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - err := state.UpdateContainer(&Container{config: &Config{ID: "1234"}}) + err := state.UpdateContainer(&Container{config: &ContainerConfig{ID: "1234"}}) assert.Error(t, err) }) } @@ -780,7 +780,7 @@ func TestUpdateContainerNotInNamespaceReturnsError(t *testing.T) { func TestSaveInvalidContainerReturnsError(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { - err := state.SaveContainer(&Container{config: &Config{ID: "1234"}}) + err := state.SaveContainer(&Container{config: &ContainerConfig{ID: "1234"}}) assert.Error(t, err) }) } @@ -2604,7 +2604,7 @@ func TestAddContainerToPodInvalidCtr(t *testing.T) { err = state.AddPod(testPod) assert.NoError(t, err) - err = state.AddContainerToPod(testPod, &Container{config: &Config{ID: "1234"}}) + err = state.AddContainerToPod(testPod, &Container{config: &ContainerConfig{ID: "1234"}}) assert.Error(t, err) ctrs, err := state.PodContainersByID(testPod) diff --git a/pkg/apparmor/apparmor.go b/pkg/apparmor/apparmor.go index 8b9f99477..45c029c07 100644 --- a/pkg/apparmor/apparmor.go +++ b/pkg/apparmor/apparmor.go @@ -2,11 +2,16 @@ package apparmor import ( "errors" + libpodVersion "github.com/containers/libpod/version" ) var ( + // DefaultLipodProfilePrefix is used for version-independent presence checks. + DefaultLipodProfilePrefix = "libpod-default" + "-" // DefaultLibpodProfile is the name of default libpod AppArmor profile. - DefaultLibpodProfile = "libpod-default" + DefaultLibpodProfile = DefaultLipodProfilePrefix + libpodVersion.Version // ErrApparmorUnsupported indicates that AppArmor support is not supported. ErrApparmorUnsupported = errors.New("AppArmor is not supported") + // ErrApparmorRootless indicates that AppArmor support is not supported in rootless mode. + ErrApparmorRootless = errors.New("AppArmor is not supported in rootless mode") ) diff --git a/pkg/apparmor/apparmor_linux.go b/pkg/apparmor/apparmor_linux.go index b0e3ca0fd..0787b3fa5 100644 --- a/pkg/apparmor/apparmor_linux.go +++ b/pkg/apparmor/apparmor_linux.go @@ -13,7 +13,10 @@ import ( "strings" "text/template" + "github.com/containers/libpod/pkg/rootless" runcaa "github.com/opencontainers/runc/libcontainer/apparmor" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // profileDirectory is the file store for apparmor profiles and macros. @@ -21,6 +24,9 @@ var profileDirectory = "/etc/apparmor.d" // IsEnabled returns true if AppArmor is enabled on the host. func IsEnabled() bool { + if rootless.IsRootless() { + return false + } return runcaa.IsEnabled() } @@ -71,6 +77,10 @@ func macroExists(m string) bool { // InstallDefault generates a default profile and loads it into the kernel // using 'apparmor_parser'. func InstallDefault(name string) error { + if rootless.IsRootless() { + return ErrApparmorRootless + } + p := profileData{ Name: name, } @@ -97,6 +107,10 @@ func InstallDefault(name string) error { // IsLoaded checks if a profile with the given name has been loaded into the // kernel. func IsLoaded(name string) (bool, error) { + if name != "" && rootless.IsRootless() { + return false, errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) + } + file, err := os.Open("/sys/kernel/security/apparmor/profiles") if err != nil { if os.IsNotExist(err) { @@ -188,3 +202,55 @@ func parseAAParserVersion(output string) (int, error) { return numericVersion, nil } + +// CheckProfileAndLoadDefault checks if the specified profile is loaded and +// loads the DefaultLibpodProfile if the specified on is prefixed by +// DefaultLipodProfilePrefix. This allows to always load and apply the latest +// default AppArmor profile. Note that AppArmor requires root. If it's a +// default profile, return DefaultLipodProfilePrefix, otherwise the specified +// one. +func CheckProfileAndLoadDefault(name string) (string, error) { + if name == "unconfined" { + return name, nil + } + + if name != "" && rootless.IsRootless() { + return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name) + } + + if name != "" && !runcaa.IsEnabled() { + return "", fmt.Errorf("profile %q specified but AppArmor is disabled on the host", name) + } + + // If the specified name is not empty or is not a default libpod one, + // ignore it and return the name. + if name != "" && !strings.HasPrefix(name, DefaultLipodProfilePrefix) { + isLoaded, err := IsLoaded(name) + if err != nil { + return "", err + } + if !isLoaded { + return "", fmt.Errorf("AppArmor profile %q specified but not loaded") + } + return name, nil + } + + name = DefaultLibpodProfile + // To avoid expensive redundant loads on each invocation, check + // if it's loaded before installing it. + isLoaded, err := IsLoaded(name) + if err != nil { + return "", err + } + if !isLoaded { + err = InstallDefault(name) + if err != nil { + return "", err + } + logrus.Infof("successfully loaded AppAmor profile %q", name) + } else { + logrus.Infof("AppAmor profile %q is already loaded", name) + } + + return name, nil +} diff --git a/pkg/apparmor/apparmor_unsupported.go b/pkg/apparmor/apparmor_unsupported.go index df1336b07..b2b4de5f5 100644 --- a/pkg/apparmor/apparmor_unsupported.go +++ b/pkg/apparmor/apparmor_unsupported.go @@ -2,19 +2,25 @@ package apparmor -// IsEnabled returns true if AppArmor is enabled on the host. +// IsEnabled dummy. func IsEnabled() bool { return false } -// InstallDefault generates a default profile in a temp directory determined by -// os.TempDir(), then loads the profile into the kernel using 'apparmor_parser'. +// InstallDefault dummy. func InstallDefault(name string) error { return ErrApparmorUnsupported } -// IsLoaded checks if a profile with the given name has been loaded into the -// kernel. +// IsLoaded dummy. func IsLoaded(name string) (bool, error) { return false, ErrApparmorUnsupported } + +// CheckProfileAndLoadDefault dummy. +func CheckProfileAndLoadDefault(name string) (string, error) { + if name == "" { + return "", nil + } + return "", ErrApparmorUnsupported +} diff --git a/pkg/hooks/exec/exec.go b/pkg/hooks/exec/exec.go index 94469b1d2..0dd091561 100644 --- a/pkg/hooks/exec/exec.go +++ b/pkg/hooks/exec/exec.go @@ -10,6 +10,7 @@ import ( "time" rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" ) // DefaultPostKillTimeout is the recommended default post-kill timeout. @@ -42,7 +43,11 @@ func Run(ctx context.Context, hook *rspec.Hook, state []byte, stdout io.Writer, } exit := make(chan error, 1) go func() { - exit <- cmd.Wait() + err := cmd.Wait() + if err != nil { + err = errors.Wrapf(err, "executing %v", cmd.Args) + } + exit <- err }() select { diff --git a/pkg/hooks/exec/exec_test.go b/pkg/hooks/exec/exec_test.go index 62e45ff3a..7aac315cb 100644 --- a/pkg/hooks/exec/exec_test.go +++ b/pkg/hooks/exec/exec_test.go @@ -163,14 +163,14 @@ func TestRunCancel(t *testing.T) { name: "context timeout", contextTimeout: time.Duration(1) * time.Second, expectedStdout: "waiting\n", - expectedHookError: "^signal: killed$", + expectedHookError: "^executing \\[sh -c echo waiting; sleep 2; echo done]: signal: killed$", expectedRunError: context.DeadlineExceeded, }, { name: "hook timeout", hookTimeout: &one, expectedStdout: "waiting\n", - expectedHookError: "^signal: killed$", + expectedHookError: "^executing \\[sh -c echo waiting; sleep 2; echo done]: signal: killed$", expectedRunError: context.DeadlineExceeded, }, } { @@ -207,7 +207,7 @@ func TestRunKillTimeout(t *testing.T) { } hookErr, err := Run(ctx, hook, []byte("{}"), nil, nil, time.Duration(0)) assert.Equal(t, context.DeadlineExceeded, err) - assert.Regexp(t, "^(failed to reap process within 0s of the kill signal|signal: killed)$", hookErr) + assert.Regexp(t, "^(failed to reap process within 0s of the kill signal|executing \\[sh -c sleep 1]: signal: killed)$", hookErr) } func init() { diff --git a/pkg/hooks/exec/runtimeconfigfilter.go b/pkg/hooks/exec/runtimeconfigfilter.go new file mode 100644 index 000000000..c6971f680 --- /dev/null +++ b/pkg/hooks/exec/runtimeconfigfilter.go @@ -0,0 +1,68 @@ +package exec + +import ( + "bytes" + "context" + "encoding/json" + "reflect" + "time" + + "github.com/davecgh/go-spew/spew" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/pmezard/go-difflib/difflib" + "github.com/sirupsen/logrus" +) + +var spewConfig = spew.ConfigState{ + Indent: " ", + DisablePointerAddresses: true, + DisableCapacities: true, + SortKeys: true, +} + +// RuntimeConfigFilter calls a series of hooks. But instead of +// passing container state on their standard input, +// RuntimeConfigFilter passes the proposed runtime configuration (and +// reads back a possibly-altered form from their standard output). +func RuntimeConfigFilter(ctx context.Context, hooks []spec.Hook, config *spec.Spec, postKillTimeout time.Duration) (hookErr, err error) { + data, err := json.Marshal(config) + for i, hook := range hooks { + var stdout bytes.Buffer + hookErr, err = Run(ctx, &hook, data, &stdout, nil, postKillTimeout) + if err != nil { + return hookErr, err + } + + data = stdout.Bytes() + var newConfig spec.Spec + err = json.Unmarshal(data, &newConfig) + if err != nil { + logrus.Debugf("invalid JSON from config-filter hook %d:\n%s", i, string(data)) + return nil, errors.Wrapf(err, "unmarshal output from config-filter hook %d", i) + } + + if !reflect.DeepEqual(config, &newConfig) { + old := spewConfig.Sdump(config) + new := spewConfig.Sdump(&newConfig) + diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(old), + B: difflib.SplitLines(new), + FromFile: "Old", + FromDate: "", + ToFile: "New", + ToDate: "", + Context: 1, + }) + if err == nil { + logrus.Debugf("precreate hook %d made configuration changes:\n%s", i, diff) + } else { + logrus.Warnf("precreate hook %d made configuration changes, but we could not compute a diff: %v", i, err) + } + } + + *config = newConfig + } + + return nil, nil +} diff --git a/pkg/hooks/exec/runtimeconfigfilter_test.go b/pkg/hooks/exec/runtimeconfigfilter_test.go new file mode 100644 index 000000000..52d590d14 --- /dev/null +++ b/pkg/hooks/exec/runtimeconfigfilter_test.go @@ -0,0 +1,266 @@ +package exec + +import ( + "context" + "encoding/json" + "os" + "testing" + "time" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func pointerInt(value int) *int { + return &value +} + +func pointerUInt32(value uint32) *uint32 { + return &value +} + +func pointerFileMode(value os.FileMode) *os.FileMode { + return &value +} + +func TestRuntimeConfigFilter(t *testing.T) { + unexpectedEndOfJSONInput := json.Unmarshal([]byte("{\n"), nil) + + for _, test := range []struct { + name string + contextTimeout time.Duration + hooks []spec.Hook + input *spec.Spec + expected *spec.Spec + expectedHookError string + expectedRunError error + }{ + { + name: "no-op", + hooks: []spec.Hook{ + { + Path: path, + Args: []string{"sh", "-c", "cat"}, + }, + }, + input: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + }, + expected: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + }, + }, + { + name: "device injection", + hooks: []spec.Hook{ + { + Path: path, + Args: []string{"sh", "-c", `sed 's|\("gid":0}\)|\1,{"path": "/dev/sda","type":"b","major":8,"minor":0,"fileMode":384,"uid":0,"gid":0}|'`}, + }, + }, + input: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + Linux: &spec.Linux{ + Devices: []spec.LinuxDevice{ + { + Path: "/dev/fuse", + Type: "c", + Major: 10, + Minor: 229, + FileMode: pointerFileMode(0600), + UID: pointerUInt32(0), + GID: pointerUInt32(0), + }, + }, + }, + }, + expected: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + Linux: &spec.Linux{ + Devices: []spec.LinuxDevice{ + { + Path: "/dev/fuse", + Type: "c", + Major: 10, + Minor: 229, + FileMode: pointerFileMode(0600), + UID: pointerUInt32(0), + GID: pointerUInt32(0), + }, + { + Path: "/dev/sda", + Type: "b", + Major: 8, + Minor: 0, + FileMode: pointerFileMode(0600), + UID: pointerUInt32(0), + GID: pointerUInt32(0), + }, + }, + }, + }, + }, + { + name: "chaining", + hooks: []spec.Hook{ + { + Path: path, + Args: []string{"sh", "-c", `sed 's|\("gid":0}\)|\1,{"path": "/dev/sda","type":"b","major":8,"minor":0,"fileMode":384,"uid":0,"gid":0}|'`}, + }, + { + Path: path, + Args: []string{"sh", "-c", `sed 's|/dev/sda|/dev/sdb|'`}, + }, + }, + input: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + Linux: &spec.Linux{ + Devices: []spec.LinuxDevice{ + { + Path: "/dev/fuse", + Type: "c", + Major: 10, + Minor: 229, + FileMode: pointerFileMode(0600), + UID: pointerUInt32(0), + GID: pointerUInt32(0), + }, + }, + }, + }, + expected: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + Linux: &spec.Linux{ + Devices: []spec.LinuxDevice{ + { + Path: "/dev/fuse", + Type: "c", + Major: 10, + Minor: 229, + FileMode: pointerFileMode(0600), + UID: pointerUInt32(0), + GID: pointerUInt32(0), + }, + { + Path: "/dev/sdb", + Type: "b", + Major: 8, + Minor: 0, + FileMode: pointerFileMode(0600), + UID: pointerUInt32(0), + GID: pointerUInt32(0), + }, + }, + }, + }, + }, + { + name: "context timeout", + contextTimeout: time.Duration(1) * time.Second, + hooks: []spec.Hook{ + { + Path: path, + Args: []string{"sh", "-c", "sleep 2"}, + }, + }, + input: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + }, + expected: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + }, + expectedHookError: "^executing \\[sh -c sleep 2]: signal: killed$", + expectedRunError: context.DeadlineExceeded, + }, + { + name: "hook timeout", + hooks: []spec.Hook{ + { + Path: path, + Args: []string{"sh", "-c", "sleep 2"}, + Timeout: pointerInt(1), + }, + }, + input: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + }, + expected: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + }, + expectedHookError: "^executing \\[sh -c sleep 2]: signal: killed$", + expectedRunError: context.DeadlineExceeded, + }, + { + name: "invalid JSON", + hooks: []spec.Hook{ + { + Path: path, + Args: []string{"sh", "-c", "echo '{'"}, + }, + }, + input: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + }, + expected: &spec.Spec{ + Version: "1.0.0", + Root: &spec.Root{ + Path: "rootfs", + }, + }, + expectedRunError: unexpectedEndOfJSONInput, + }, + } { + t.Run(test.name, func(t *testing.T) { + ctx := context.Background() + if test.contextTimeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, test.contextTimeout) + defer cancel() + } + hookErr, err := RuntimeConfigFilter(ctx, test.hooks, test.input, DefaultPostKillTimeout) + assert.Equal(t, test.expectedRunError, errors.Cause(err)) + if test.expectedHookError == "" { + if hookErr != nil { + t.Fatal(hookErr) + } + } else { + assert.Regexp(t, test.expectedHookError, hookErr.Error()) + } + assert.Equal(t, test.expected, test.input) + }) + } +} diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index ffc98e307..87fce7e2e 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -518,7 +518,9 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib if c.CgroupParent != "" { options = append(options, libpod.WithCgroupParent(c.CgroupParent)) } - if c.Detach { + // For a rootless container always cleanup the storage/network as they + // run in a different namespace thus not reusable when we restart. + if c.Detach || rootless.IsRootless() { options = append(options, libpod.WithExitCommand(c.createExitCommand())) } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index ffa999730..9ef0223f2 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -252,6 +252,7 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint } // SECURITY OPTS g.SetProcessNoNewPrivileges(config.NoNewPrivs) + g.SetProcessApparmorProfile(config.ApparmorProfile) blockAccessToKernelFilesystems(config, &g) diff --git a/pkg/sysinfo/sysinfo_linux.go~ b/pkg/sysinfo/sysinfo_linux.go~ deleted file mode 100644 index 471f786a7..000000000 --- a/pkg/sysinfo/sysinfo_linux.go~ +++ /dev/null @@ -1,254 +0,0 @@ -package sysinfo - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "strings" - - "github.com/opencontainers/runc/libcontainer/cgroups" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -func findCgroupMountpoints() (map[string]string, error) { - cgMounts, err := cgroups.GetCgroupMounts(false) - if err != nil { - return nil, fmt.Errorf("Failed to parse cgroup information: %v", err) - } - mps := make(map[string]string) - for _, m := range cgMounts { - for _, ss := range m.Subsystems { - mps[ss] = m.Mountpoint - } - } - return mps, nil -} - -// New returns a new SysInfo, using the filesystem to detect which features -// the kernel supports. If `quiet` is `false` warnings are printed in logs -// whenever an error occurs or misconfigurations are present. -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - cgMounts, err := findCgroupMountpoints() - if err != nil { - logrus.Warnf("Failed to parse cgroup information: %v", err) - } else { - sysInfo.cgroupMemInfo = checkCgroupMem(cgMounts, quiet) - sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet) - sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet) - sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet) - sysInfo.cgroupPids = checkCgroupPids(quiet) - } - - _, ok := cgMounts["devices"] - sysInfo.CgroupDevicesEnabled = ok - - sysInfo.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward") - sysInfo.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables") - sysInfo.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables") - - // Check if AppArmor is supported. - if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) { - sysInfo.AppArmor = true - } - - // 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 { - sysInfo.Seccomp = true - } - } - - return sysInfo -} - -// checkCgroupMem reads the memory information from the memory cgroup mount point. -func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo { - mountPoint, ok := cgMounts["memory"] - if !ok { - if !quiet { - logrus.Warn("Your kernel does not support cgroup memory limit") - } - return cgroupMemInfo{} - } - - swapLimit := cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes") - if !quiet && !swapLimit { - logrus.Warn("Your kernel does not support swap memory limit") - } - memoryReservation := cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes") - if !quiet && !memoryReservation { - logrus.Warn("Your kernel does not support memory reservation") - } - oomKillDisable := cgroupEnabled(mountPoint, "memory.oom_control") - if !quiet && !oomKillDisable { - logrus.Warn("Your kernel does not support oom control") - } - memorySwappiness := cgroupEnabled(mountPoint, "memory.swappiness") - if !quiet && !memorySwappiness { - logrus.Warn("Your kernel does not support memory swappiness") - } - kernelMemory := cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes") - if !quiet && !kernelMemory { - logrus.Warn("Your kernel does not support kernel memory limit") - } - - return cgroupMemInfo{ - MemoryLimit: true, - SwapLimit: swapLimit, - MemoryReservation: memoryReservation, - OomKillDisable: oomKillDisable, - MemorySwappiness: memorySwappiness, - KernelMemory: kernelMemory, - } -} - -// checkCgroupCPU reads the cpu information from the cpu cgroup mount point. -func checkCgroupCPU(cgMounts map[string]string, quiet bool) cgroupCPUInfo { - mountPoint, ok := cgMounts["cpu"] - if !ok { - if !quiet { - logrus.Warn("Unable to find cpu cgroup in mounts") - } - return cgroupCPUInfo{} - } - - cpuShares := cgroupEnabled(mountPoint, "cpu.shares") - if !quiet && !cpuShares { - logrus.Warn("Your kernel does not support cgroup cpu shares") - } - - cpuCfsPeriod := cgroupEnabled(mountPoint, "cpu.cfs_period_us") - if !quiet && !cpuCfsPeriod { - logrus.Warn("Your kernel does not support cgroup cfs period") - } - - cpuCfsQuota := cgroupEnabled(mountPoint, "cpu.cfs_quota_us") - if !quiet && !cpuCfsQuota { - logrus.Warn("Your kernel does not support cgroup cfs quotas") - } - - cpuRealtimePeriod := cgroupEnabled(mountPoint, "cpu.rt_period_us") - if !quiet && !cpuRealtimePeriod { - logrus.Warn("Your kernel does not support cgroup rt period") - } - - cpuRealtimeRuntime := cgroupEnabled(mountPoint, "cpu.rt_runtime_us") - if !quiet && !cpuRealtimeRuntime { - logrus.Warn("Your kernel does not support cgroup rt runtime") - } - - return cgroupCPUInfo{ - CPUShares: cpuShares, - CPUCfsPeriod: cpuCfsPeriod, - CPUCfsQuota: cpuCfsQuota, - CPURealtimePeriod: cpuRealtimePeriod, - CPURealtimeRuntime: cpuRealtimeRuntime, - } -} - -// checkCgroupBlkioInfo reads the blkio information from the blkio cgroup mount point. -func checkCgroupBlkioInfo(cgMounts map[string]string, quiet bool) cgroupBlkioInfo { - mountPoint, ok := cgMounts["blkio"] - if !ok { - if !quiet { - logrus.Warn("Unable to find blkio cgroup in mounts") - } - return cgroupBlkioInfo{} - } - - weight := cgroupEnabled(mountPoint, "blkio.weight") - if !quiet && !weight { - logrus.Warn("Your kernel does not support cgroup blkio weight") - } - - weightDevice := cgroupEnabled(mountPoint, "blkio.weight_device") - if !quiet && !weightDevice { - logrus.Warn("Your kernel does not support cgroup blkio weight_device") - } - - readBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device") - if !quiet && !readBpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.read_bps_device") - } - - writeBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device") - if !quiet && !writeBpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.write_bps_device") - } - readIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device") - if !quiet && !readIOpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.read_iops_device") - } - - writeIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device") - if !quiet && !writeIOpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.write_iops_device") - } - return cgroupBlkioInfo{ - BlkioWeight: weight, - BlkioWeightDevice: weightDevice, - BlkioReadBpsDevice: readBpsDevice, - BlkioWriteBpsDevice: writeBpsDevice, - BlkioReadIOpsDevice: readIOpsDevice, - BlkioWriteIOpsDevice: writeIOpsDevice, - } -} - -// checkCgroupCpusetInfo reads the cpuset information from the cpuset cgroup mount point. -func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetInfo { - mountPoint, ok := cgMounts["cpuset"] - if !ok { - if !quiet { - logrus.Warn("Unable to find cpuset cgroup in mounts") - } - return cgroupCpusetInfo{} - } - - cpus, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.cpus")) - if err != nil { - return cgroupCpusetInfo{} - } - - mems, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.mems")) - if err != nil { - return cgroupCpusetInfo{} - } - - return cgroupCpusetInfo{ - Cpuset: true, - Cpus: strings.TrimSpace(string(cpus)), - Mems: strings.TrimSpace(string(mems)), - } -} - -// checkCgroupPids reads the pids information from the pids cgroup mount point. -func checkCgroupPids(quiet bool) cgroupPids { - _, err := cgroups.FindCgroupMountpoint("pids") - if err != nil { - if !quiet { - logrus.Warn(err) - } - return cgroupPids{} - } - - return cgroupPids{ - PidsLimit: true, - } -} - -func cgroupEnabled(mountPoint, name string) bool { - _, err := os.Stat(path.Join(mountPoint, name)) - return err == nil -} - -func readProcBool(path string) bool { - val, err := ioutil.ReadFile(path) - if err != nil { - return false - } - return strings.TrimSpace(string(val)) == "1" -} diff --git a/pkg/util/utils.go b/pkg/util/utils.go index a6f52cb3e..2b752afe1 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -314,8 +314,9 @@ func GetDefaultStoreOptions() (storage.StoreOptions, string, error) { return storageOpts, volumePath, err } - storageConf := filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") + storageConf := StorageConfigFile() if _, err := os.Stat(storageConf); err == nil { + storageOpts = storage.StoreOptions{} storage.ReloadConfigurationFile(storageConf, &storageOpts) } else if os.IsNotExist(err) { os.MkdirAll(filepath.Dir(storageConf), 0755) @@ -334,3 +335,11 @@ func GetDefaultStoreOptions() (storage.StoreOptions, string, error) { } return storageOpts, volumePath, nil } + +// StorageConfigFile returns the path to the storage config file used +func StorageConfigFile() string { + if rootless.IsRootless() { + return filepath.Join(os.Getenv("HOME"), ".config/containers/storage.conf") + } + return storage.DefaultConfigFile +} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 5e4cb4ccb..8f8934025 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -41,13 +41,18 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error { for _, image := range images { labels, _ := image.Labels(getContext()) containers, _ := image.Containers() + repoDigests, err := image.RepoDigests() + if err != nil { + return err + } + size, _ := image.Size(getContext()) i := iopodman.ImageInList{ Id: image.ID(), ParentId: image.Parent, RepoTags: image.Names(), - RepoDigests: image.RepoDigests(), + RepoDigests: repoDigests, Created: image.Created().String(), Size: int64(*size), VirtualSize: image.VirtualSize, @@ -73,6 +78,10 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, name string) error { if err != nil { return err } + repoDigests, err := newImage.RepoDigests() + if err != nil { + return err + } size, err := newImage.Size(getContext()) if err != nil { return err @@ -82,7 +91,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, name string) error { Id: newImage.ID(), ParentId: newImage.Parent, RepoTags: newImage.Names(), - RepoDigests: newImage.RepoDigests(), + RepoDigests: repoDigests, Created: newImage.Created().String(), Size: int64(*size), VirtualSize: newImage.VirtualSize, diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index a29d22e7d..e50643dd0 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -102,6 +102,5 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { podmanInfo.Podman = pmaninfo podmanInfo.Registries = registries podmanInfo.Insecure_registries = insecureRegistries - return call.ReplyGetInfo(podmanInfo) } diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 4e892d11c..ca2f35fc9 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -199,14 +199,17 @@ var _ = Describe("Podman checkpoint", func() { }) It("podman checkpoint container with established tcp connections", func() { - Skip("Seems to not work (yet) in CI") podmanTest.RestoreArtifact(redis) - session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--network", "host", "-d", redis}) + session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "-d", redis}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + IP := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.IPAddress}}"}) + IP.WaitWithDefaultTimeout() + Expect(IP.ExitCode()).To(Equal(0)) + // Open a network connection to the redis server - conn, err := net.Dial("tcp", "127.0.0.1:6379") + conn, err := net.Dial("tcp", IP.OutputToString()+":6379") if err != nil { os.Exit(1) } @@ -287,4 +290,40 @@ var _ = Describe("Podman checkpoint", func() { Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) }) + It("podman checkpoint and restore container with same IP", func() { + session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "test_name", "-d", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + IPBefore := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.IPAddress}}"}) + IPBefore.WaitWithDefaultTimeout() + Expect(IPBefore.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"container", "checkpoint", "test_name"}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited")) + + result = podmanTest.Podman([]string{"container", "restore", "test_name"}) + result.WaitWithDefaultTimeout() + + IPAfter := podmanTest.Podman([]string{"inspect", "-l", "--format={{.NetworkSettings.IPAddress}}"}) + IPAfter.WaitWithDefaultTimeout() + Expect(IPAfter.ExitCode()).To(Equal(0)) + + // Check that IP address did not change between checkpointing and restoring + Expect(IPBefore.OutputToString()).To(Equal(IPAfter.OutputToString())) + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up")) + + result = podmanTest.Podman([]string{"rm", "-fa"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + }) diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index fec80717f..a181501a5 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -127,4 +127,36 @@ var _ = Describe("Podman exec", func() { Expect(session2.ExitCode()).To(Equal(0)) Expect(session2.OutputToString()).To(Equal(testUser)) }) + + It("podman exec simple working directory test", func() { + setup := podmanTest.RunTopContainer("test1") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"exec", "-l", "--workdir", "/tmp", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ := session.GrepString("/tmp") + Expect(match).Should(BeTrue()) + + session = podmanTest.Podman([]string{"exec", "-l", "-w", "/tmp", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ = session.GrepString("/tmp") + Expect(match).Should(BeTrue()) + }) + + It("podman exec missing working directory test", func() { + setup := podmanTest.RunTopContainer("test1") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"exec", "-l", "--workdir", "/missing", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + + session = podmanTest.Podman([]string{"exec", "-l", "-w", "/missing", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) }) diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 64245c609..9d7ac145c 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -115,4 +115,14 @@ var _ = Describe("Podman start", func() { numContainers := podmanTest.NumberOfContainers() Expect(numContainers).To(Equal(1)) }) + + It("podman start --sig-proxy should not work without --attach", func() { + session := podmanTest.Podman([]string{"create", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"start", "-l", "--sig-proxy"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) }) diff --git a/vendor.conf b/vendor.conf index c215f8294..18283cae6 100644 --- a/vendor.conf +++ b/vendor.conf @@ -92,7 +92,7 @@ k8s.io/kube-openapi 275e2ce91dec4c05a4094a7b1daee5560b555ac9 https://github.com/ k8s.io/utils 258e2a2fa64568210fbd6267cf1d8fd87c3cb86e https://github.com/kubernetes/utils github.com/mrunalp/fileutils master github.com/varlink/go master -github.com/containers/buildah bb710f39d01868e47224f35f48a128fbea6539c4 +github.com/containers/buildah e7ca330f923701dba8859f5c014d0a9a3f7f0a49 github.com/Nvveen/Gotty master github.com/fsouza/go-dockerclient master github.com/openshift/imagebuilder master diff --git a/vendor/github.com/containers/buildah/common.go b/vendor/github.com/containers/buildah/common.go index dfdc33a22..e369dc407 100644 --- a/vendor/github.com/containers/buildah/common.go +++ b/vendor/github.com/containers/buildah/common.go @@ -6,10 +6,8 @@ import ( "path/filepath" cp "github.com/containers/image/copy" - "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/containers/libpod/pkg/rootless" - "github.com/sirupsen/logrus" ) const ( @@ -34,12 +32,6 @@ func getCopyOptions(reportWriter io.Writer, sourceReference types.ImageReference } } - sourceInsecure, err := isReferenceInsecure(sourceReference, sourceCtx) - if err != nil { - logrus.Debugf("error determining if registry for %q is insecure: %v", transports.ImageName(sourceReference), err) - } else if sourceInsecure { - sourceCtx.OCIInsecureSkipTLSVerify = true - } destinationCtx := &types.SystemContext{} if destinationSystemContext != nil { @@ -51,12 +43,6 @@ func getCopyOptions(reportWriter io.Writer, sourceReference types.ImageReference } } } - destinationInsecure, err := isReferenceInsecure(destinationReference, destinationCtx) - if err != nil { - logrus.Debugf("error determining if registry for %q is insecure: %v", transports.ImageName(destinationReference), err) - } else if destinationInsecure { - destinationCtx.OCIInsecureSkipTLSVerify = true - } return &cp.Options{ ReportWriter: reportWriter, diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index d838260e7..217bcfc79 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -517,6 +517,7 @@ func (b *Executor) Run(run imagebuilder.Run, config docker.Config) error { Hostname: config.Hostname, Runtime: b.runtime, Args: b.runtimeArgs, + NoPivot: os.Getenv("BUILDAH_NOPIVOT") != "", Mounts: convertMounts(b.transientMounts), Env: config.Env, User: config.User, diff --git a/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go b/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go index ae55316b0..31e6a428c 100644 --- a/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go +++ b/vendor/github.com/containers/buildah/pkg/blobcache/blobcache.go @@ -52,14 +52,18 @@ type BlobCache interface { type blobCacheReference struct { reference types.ImageReference + // WARNING: The contents of this directory may be accessed concurrently, + // both within this process and by multiple different processes directory string compress types.LayerCompression } type blobCacheSource struct { - reference *blobCacheReference - source types.ImageSource - sys types.SystemContext + reference *blobCacheReference + source types.ImageSource + sys types.SystemContext + // this mutex synchronizes the counters below + mu sync.Mutex cacheHits int64 cacheMisses int64 cacheErrors int64 @@ -219,7 +223,7 @@ func (s *blobCacheSource) GetManifest(ctx context.Context, instanceDigest *diges } func (s *blobCacheSource) HasThreadSafeGetBlob() bool { - return false + return s.source.HasThreadSafeGetBlob() } func (s *blobCacheSource) GetBlob(ctx context.Context, blobinfo types.BlobInfo, cache types.BlobInfoCache) (io.ReadCloser, int64, error) { @@ -232,16 +236,22 @@ func (s *blobCacheSource) GetBlob(ctx context.Context, blobinfo types.BlobInfo, filename := filepath.Join(s.reference.directory, makeFilename(blobinfo.Digest, isConfig)) f, err := os.Open(filename) if err == nil { + s.mu.Lock() s.cacheHits++ + s.mu.Unlock() return f, size, nil } if !os.IsNotExist(err) { + s.mu.Lock() s.cacheErrors++ + s.mu.Unlock() return nil, -1, errors.Wrapf(err, "error checking for cache file %q", filepath.Join(s.reference.directory, filename)) } } } + s.mu.Lock() s.cacheMisses++ + s.mu.Unlock() rc, size, err := s.source.GetBlob(ctx, blobinfo, cache) if err != nil { return rc, size, errors.Wrapf(err, "error reading blob from source image %q", transports.ImageName(s.reference)) @@ -403,7 +413,7 @@ func saveStream(wg *sync.WaitGroup, decompressReader io.ReadCloser, tempFile *os } func (s *blobCacheDestination) HasThreadSafePutBlob() bool { - return false + return s.destination.HasThreadSafePutBlob() } func (d *blobCacheDestination) PutBlob(ctx context.Context, stream io.Reader, inputInfo types.BlobInfo, cache types.BlobInfoCache, isConfig bool) (types.BlobInfo, error) { diff --git a/vendor/github.com/containers/buildah/util.go b/vendor/github.com/containers/buildah/util.go index 66a4e535a..5dadec7c2 100644 --- a/vendor/github.com/containers/buildah/util.go +++ b/vendor/github.com/containers/buildah/util.go @@ -173,24 +173,6 @@ func (b *Builder) tarPath() func(path string) (io.ReadCloser, error) { } } -// isRegistryInsecure checks if the named registry is marked as not secure -func isRegistryInsecure(registry string, sc *types.SystemContext) (bool, error) { - reginfo, err := sysregistriesv2.FindRegistry(sc, registry) - if err != nil { - return false, errors.Wrapf(err, "unable to parse the registries configuration (%s)", sysregistries.RegistriesConfPath(sc)) - } - if reginfo != nil { - if reginfo.Insecure { - logrus.Debugf("registry %q is marked insecure in registries configuration %q", registry, sysregistries.RegistriesConfPath(sc)) - } else { - logrus.Debugf("registry %q is not marked insecure in registries configuration %q", registry, sysregistries.RegistriesConfPath(sc)) - } - return reginfo.Insecure, nil - } - logrus.Debugf("registry %q is not listed in registries configuration %q, assuming it's secure", registry, sysregistries.RegistriesConfPath(sc)) - return false, nil -} - // isRegistryBlocked checks if the named registry is marked as blocked func isRegistryBlocked(registry string, sc *types.SystemContext) (bool, error) { reginfo, err := sysregistriesv2.FindRegistry(sc, registry) @@ -221,11 +203,6 @@ func isReferenceSomething(ref types.ImageReference, sc *types.SystemContext, wha return false, nil } -// isReferenceInsecure checks if the registry part of a reference is insecure -func isReferenceInsecure(ref types.ImageReference, sc *types.SystemContext) (bool, error) { - return isReferenceSomething(ref, sc, isRegistryInsecure) -} - // isReferenceBlocked checks if the registry part of a reference is blocked func isReferenceBlocked(ref types.ImageReference, sc *types.SystemContext) (bool, error) { if ref != nil && ref.Transport() != nil { diff --git a/vendor/github.com/containers/storage/store.go b/vendor/github.com/containers/storage/store.go index a166799c6..5877c3b06 100644 --- a/vendor/github.com/containers/storage/store.go +++ b/vendor/github.com/containers/storage/store.go @@ -2992,7 +2992,8 @@ func copyStringInterfaceMap(m map[string]interface{}) map[string]interface{} { return ret } -const defaultConfigFile = "/etc/containers/storage.conf" +// DefaultConfigFile path to the system wide storage.conf file +const DefaultConfigFile = "/etc/containers/storage.conf" // ThinpoolOptionsConfig represents the "storage.options.thinpool" // TOML config table. @@ -3237,7 +3238,7 @@ func init() { DefaultStoreOptions.GraphRoot = "/var/lib/containers/storage" DefaultStoreOptions.GraphDriverName = "" - ReloadConfigurationFile(defaultConfigFile, &DefaultStoreOptions) + ReloadConfigurationFile(DefaultConfigFile, &DefaultStoreOptions) } func GetDefaultMountOptions() ([]string, error) { |