diff options
105 files changed, 1436 insertions, 429 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 22f84d7ec..1458e2cc6 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -39,7 +39,7 @@ env: UBUNTU_NAME: "ubuntu-20" PRIOR_UBUNTU_NAME: "ubuntu-19" - _BUILT_IMAGE_SUFFIX: "podman-6439450735542272" + _BUILT_IMAGE_SUFFIX: "podman-6530021898584064" FEDORA_CACHE_IMAGE_NAME: "${FEDORA_NAME}-${_BUILT_IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "${PRIOR_FEDORA_NAME}-${_BUILT_IMAGE_SUFFIX}" UBUNTU_CACHE_IMAGE_NAME: "${UBUNTU_NAME}-${_BUILT_IMAGE_SUFFIX}" @@ -59,7 +59,7 @@ env: #### Default to NOT operating in any special-case testing mode #### SPECIALMODE: "none" # don't do anything special - TEST_REMOTE_CLIENT: 'false' # don't test remote client by default + RCLI: 'false' # don't test remote client by default ADD_SECOND_PARTITION: 'false' # will certainly fail inside containers MOD_CONTAINERS_CONF: 'true' # Update containers.conf runtime if required by OS environment @@ -422,8 +422,8 @@ testing_task: env: ADD_SECOND_PARTITION: 'true' matrix: - - TEST_REMOTE_CLIENT: 'true' - - TEST_REMOTE_CLIENT: 'false' + - RCLI: 'true' + - RCLI: 'false' networking_script: '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/networking.sh' setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}' @@ -470,8 +470,8 @@ special_testing_rootless_task: ADD_SECOND_PARTITION: 'true' SPECIALMODE: 'rootless' # See docs matrix: - - TEST_REMOTE_CLIENT: 'true' - - TEST_REMOTE_CLIENT: 'false' + - RCLI: 'true' + - RCLI: 'false' timeout_in: 60m @@ -674,8 +674,8 @@ verify_test_built_images_task: env: ADD_SECOND_PARTITION: 'true' matrix: - - TEST_REMOTE_CLIENT: 'true' - - TEST_REMOTE_CLIENT: 'false' + - RCLI: 'true' + - RCLI: 'false' matrix: PACKER_BUILDER_NAME: "${FEDORA_NAME}" PACKER_BUILDER_NAME: "${PRIOR_FEDORA_NAME}" @@ -305,7 +305,7 @@ testunit: libpodimage ## Run unittest on the built image localunit: test/goecho/goecho varlink_generate hack/check_root.sh make localunit rm -rf ${COVERAGE_PATH} && mkdir -p ${COVERAGE_PATH} - ginkgo \ + $(GOBIN)/ginkgo \ -r \ $(TESTFLAGS) \ --skipPackage test/e2e,pkg/apparmor,test/endpoint,pkg/bindings,hack \ @@ -321,16 +321,16 @@ localunit: test/goecho/goecho varlink_generate .PHONY: ginkgo ginkgo: - ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -nodes 3 -debug test/e2e/. hack/. + $(GOBIN)/ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -nodes 3 -debug test/e2e/. hack/. .PHONY: ginkgo-remote ginkgo-remote: - ginkgo -v $(TESTFLAGS) -tags "$(REMOTETAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. + $(GOBIN)/ginkgo -v $(TESTFLAGS) -tags "$(REMOTETAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. .PHONY: endpoint ifneq (,$(findstring varlink,$(BUILDTAGS))) endpoint: - ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -debug test/endpoint/. + $(GOBIN)/ginkgo -v $(TESTFLAGS) -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -debug test/endpoint/. endpoint: endif diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 0b6897d3a..bf50bb56b 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -387,8 +387,6 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Annotations = annotations s.WorkDir = c.Workdir - userCommand := []string{} - var command []string if c.Entrypoint != nil { entrypoint := []string{} if ep := *c.Entrypoint; len(ep) > 0 { @@ -398,27 +396,13 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } } s.Entrypoint = entrypoint - // Build the command - // If we have an entry point, it goes first - command = entrypoint } // Include the command used to create the container. s.ContainerCreateCommand = os.Args if len(inputCommand) > 0 { - // User command overrides data CMD - command = append(command, inputCommand...) - userCommand = append(userCommand, inputCommand...) - } - - switch { - case len(inputCommand) > 0: - s.Command = userCommand - case c.Entrypoint != nil: - s.Command = []string{} - default: - s.Command = command + s.Command = inputCommand } // SHM Size @@ -527,6 +511,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string } switch con[0] { + case "proc-opts": + s.ProcOpts = strings.Split(con[1], ",") case "label": // TODO selinux opts and label opts are the same thing s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1]) diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 64271031d..ebb6ed98f 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -109,6 +109,7 @@ func jsonOut(responses []entities.ListContainer) error { r := make([]entities.ListContainer, 0) for _, con := range responses { con.CreatedAt = units.HumanDuration(time.Since(time.Unix(con.Created, 0))) + " ago" + con.Status = psReporter{con}.Status() r = append(r, con) } b, err := json.MarshalIndent(r, "", " ") diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go index ee0f64d99..043871a8c 100644 --- a/cmd/podman/images/list.go +++ b/cmd/podman/images/list.go @@ -195,6 +195,7 @@ func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { } else { h.ImageSummary = *e h.Repository = "<none>" + h.Tag = "<none>" imgs = append(imgs, h) } listFlag.readOnly = e.IsReadOnly() @@ -205,27 +206,34 @@ func sortImages(imageS []*entities.ImageSummary) ([]imageReporter, error) { } func tokenRepoTag(ref string) (string, string, error) { - if ref == "<none>:<none>" { return "<none>", "<none>", nil } repo, err := reference.Parse(ref) if err != nil { - return "", "", err + return "<none>", "<none>", err } named, ok := repo.(reference.Named) if !ok { - return ref, "", nil + return ref, "<none>", nil + } + name := named.Name() + if name == "" { + name = "<none>" } tagged, ok := repo.(reference.Tagged) if !ok { - return named.Name(), "", nil + return name, "<none>", nil + } + tag := tagged.Tag() + if tag == "" { + tag = "<none>" } - return named.Name(), tagged.Tag(), nil + return name, tag, nil } diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 024045b9d..82a3513f5 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -5,10 +5,9 @@ import ( "os" "strings" - "github.com/containers/podman/v2/libpod/define" - "github.com/containers/podman/v2/cmd/podman/parse" "github.com/containers/podman/v2/cmd/podman/registry" + "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" @@ -83,9 +82,10 @@ func saveFlags(flags *pflag.FlagSet) { } -func save(cmd *cobra.Command, args []string) error { +func save(cmd *cobra.Command, args []string) (finalErr error) { var ( - tags []string + tags []string + succeeded = false ) if cmd.Flag("compress").Changed && (saveOpts.Format != define.OCIManifestDir && saveOpts.Format != define.V2s2ManifestDir && saveOpts.Format == "") { return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") @@ -95,7 +95,22 @@ func save(cmd *cobra.Command, args []string) error { if terminal.IsTerminal(int(fi.Fd())) { return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") } - saveOpts.Output = "/dev/stdout" + pipePath, cleanup, err := setupPipe() + if err != nil { + return err + } + if cleanup != nil { + defer func() { + errc := cleanup() + if succeeded { + writeErr := <-errc + if writeErr != nil && finalErr == nil { + finalErr = writeErr + } + } + }() + } + saveOpts.Output = pipePath } if err := parse.ValidateFileName(saveOpts.Output); err != nil { return err @@ -103,5 +118,9 @@ func save(cmd *cobra.Command, args []string) error { if len(args) > 1 { tags = args[1:] } - return registry.ImageEngine().Save(context.Background(), args[0], tags, saveOpts) + err := registry.ImageEngine().Save(context.Background(), args[0], tags, saveOpts) + if err == nil { + succeeded = true + } + return err } diff --git a/cmd/podman/images/utils_linux.go b/cmd/podman/images/utils_linux.go new file mode 100644 index 000000000..5521abab4 --- /dev/null +++ b/cmd/podman/images/utils_linux.go @@ -0,0 +1,47 @@ +package images + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +// setupPipe for fixing https://github.com/containers/podman/issues/7017 +// uses named pipe since containers/image EvalSymlinks fails with /dev/stdout +// the caller should use the returned function to clean up the pipeDir +func setupPipe() (string, func() <-chan error, error) { + errc := make(chan error) + pipeDir, err := ioutil.TempDir(os.TempDir(), "pipeDir") + if err != nil { + return "", nil, err + } + pipePath := filepath.Join(pipeDir, "saveio") + err = unix.Mkfifo(pipePath, 0600) + if err != nil { + if e := os.RemoveAll(pipeDir); e != nil { + logrus.Errorf("error removing named pipe: %q", e) + } + return "", nil, errors.Wrapf(err, "error creating named pipe") + } + go func() { + fpipe, err := os.Open(pipePath) + if err != nil { + errc <- err + return + } + _, err = io.Copy(os.Stdout, fpipe) + fpipe.Close() + errc <- err + }() + return pipePath, func() <-chan error { + if e := os.RemoveAll(pipeDir); e != nil { + logrus.Errorf("error removing named pipe: %q", e) + } + return errc + }, nil +} diff --git a/cmd/podman/images/utils_unsupported.go b/cmd/podman/images/utils_unsupported.go new file mode 100644 index 000000000..69d1df786 --- /dev/null +++ b/cmd/podman/images/utils_unsupported.go @@ -0,0 +1,7 @@ +// +build !linux + +package images + +func setupPipe() (string, func() <-chan error, error) { + return "/dev/stdout", nil, nil +} diff --git a/cmd/podman/registry/remote.go b/cmd/podman/registry/remote.go index 7dbdd3824..9b7523ac0 100644 --- a/cmd/podman/registry/remote.go +++ b/cmd/podman/registry/remote.go @@ -5,22 +5,24 @@ import ( "sync" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/spf13/cobra" + "github.com/spf13/pflag" ) -var ( - // Was --remote given on command line - remoteOverride bool - remoteSync sync.Once -) +// Value for --remote given on command line +var remoteFromCLI = struct { + Value bool + sync sync.Once +}{} -// IsRemote returns true if podman was built to run remote +// IsRemote returns true if podman was built to run remote or --remote flag given on CLI // Use in init() functions as a initialization check func IsRemote() bool { - remoteSync.Do(func() { - remote := &cobra.Command{} - remote.Flags().BoolVarP(&remoteOverride, "remote", "r", false, "") - _ = remote.ParseFlags(os.Args) + remoteFromCLI.sync.Do(func() { + fs := pflag.NewFlagSet("remote", pflag.ContinueOnError) + fs.BoolVarP(&remoteFromCLI.Value, "remote", "r", false, "") + fs.ParseErrorsWhitelist.UnknownFlags = true + fs.SetInterspersed(false) + _ = fs.Parse(os.Args[1:]) }) - return podmanOptions.EngineMode == entities.TunnelMode || remoteOverride + return podmanOptions.EngineMode == entities.TunnelMode || remoteFromCLI.Value } diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 9e9011dc9..dd9c75ece 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -6,7 +6,6 @@ import ( "path" "runtime" "runtime/pprof" - "strconv" "strings" "github.com/containers/common/pkg/config" @@ -112,15 +111,6 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { cfg := registry.PodmanConfig() - // Validate --remote and --latest not given on same command - latest := cmd.Flags().Lookup("latest") - if latest != nil { - value, _ := strconv.ParseBool(latest.Value.String()) - if cfg.Remote && value { - return errors.Errorf("For %s \"--remote\" and \"--latest\", are mutually exclusive flags", cmd.CommandPath()) - } - } - // Prep the engines if _, err := registry.NewImageEngine(cmd, args); err != nil { return err @@ -300,6 +290,7 @@ func resolveDestination() (string, string) { cfg, err := config.ReadCustomConfig() if err != nil { + logrus.Warning(errors.Wrap(err, "unable to read local containers.conf")) return registry.DefaultAPIAddress(), "" } diff --git a/cmd/podman/system/connection/add.go b/cmd/podman/system/connection/add.go index 89cea10ca..af13b970c 100644 --- a/cmd/podman/system/connection/add.go +++ b/cmd/podman/system/connection/add.go @@ -124,6 +124,7 @@ func add(cmd *cobra.Command, args []string) error { cfg.Engine.ServiceDestinations = map[string]config.Destination{ args[0]: dst, } + cfg.Engine.ActiveService = args[0] } else { cfg.Engine.ServiceDestinations[args[0]] = dst } @@ -181,12 +182,20 @@ func getUDS(cmd *cobra.Command, uri *url.URL) (string, error) { authMethods = append(authMethods, ssh.PublicKeysCallback(a.Signers)) } - config := &ssh.ClientConfig{ + if len(authMethods) == 0 { + pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username())) + if err != nil { + return "", err + } + authMethods = append(authMethods, ssh.Password(string(pass))) + } + + cfg := &ssh.ClientConfig{ User: uri.User.Username(), Auth: authMethods, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } - dial, err := ssh.Dial("tcp", uri.Host, config) + dial, err := ssh.Dial("tcp", uri.Host, cfg) if err != nil { return "", errors.Wrapf(err, "failed to connect to %q", uri.Host) } diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go index a33f47959..aacb41e69 100644 --- a/cmd/podman/validate/args.go +++ b/cmd/podman/validate/args.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" + "github.com/containers/podman/v2/cmd/podman/registry" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -47,17 +48,20 @@ func IDOrLatestArgs(cmd *cobra.Command, args []string) error { // CheckAllLatestAndCIDFile checks that --all and --latest are used correctly. // If cidfile is set, also check for the --cidfile flag. func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error { + var specifiedLatest bool argLen := len(args) - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !cidfile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("cidfile") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") + if !registry.IsRemote() { + specifiedLatest, _ = c.Flags().GetBool("latest") + if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { + if !cidfile { + return errors.New("unable to lookup values for 'latest' or 'all'") + } else if c.Flags().Lookup("cidfile") == nil { + return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'") + } } } specifiedAll, _ := c.Flags().GetBool("all") - specifiedLatest, _ := c.Flags().GetBool("latest") specifiedCIDFile := false if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 { specifiedCIDFile = true @@ -98,17 +102,21 @@ func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool // CheckAllLatestAndPodIDFile checks that --all and --latest are used correctly. // If withIDFile is set, also check for the --pod-id-file flag. func CheckAllLatestAndPodIDFile(c *cobra.Command, args []string, ignoreArgLen bool, withIDFile bool) error { + var specifiedLatest bool argLen := len(args) - if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { - if !withIDFile { - return errors.New("unable to lookup values for 'latest' or 'all'") - } else if c.Flags().Lookup("pod-id-file") == nil { - return errors.New("unable to lookup values for 'latest', 'all' or 'pod-id-file'") + if !registry.IsRemote() { + // remote clients have no latest flag + specifiedLatest, _ = c.Flags().GetBool("latest") + if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil { + if !withIDFile { + return errors.New("unable to lookup values for 'latest' or 'all'") + } else if c.Flags().Lookup("pod-id-file") == nil { + return errors.New("unable to lookup values for 'latest', 'all' or 'pod-id-file'") + } } } specifiedAll, _ := c.Flags().GetBool("all") - specifiedLatest, _ := c.Flags().GetBool("latest") specifiedPodIDFile := false if pid, _ := c.Flags().GetStringArray("pod-id-file"); len(pid) > 0 { specifiedPodIDFile = true diff --git a/cmd/podman/validate/latest.go b/cmd/podman/validate/latest.go index 5c76fe847..c9bff798a 100644 --- a/cmd/podman/validate/latest.go +++ b/cmd/podman/validate/latest.go @@ -7,9 +7,8 @@ import ( func AddLatestFlag(cmd *cobra.Command, b *bool) { // Initialization flag verification - cmd.Flags().BoolVarP(b, "latest", "l", false, - "Act on the latest container podman is aware of\nNot supported with the \"--remote\" flag") - if registry.IsRemote() { - _ = cmd.Flags().MarkHidden("latest") + if !registry.IsRemote() { + cmd.Flags().BoolVarP(b, "latest", "l", false, + "Act on the latest container podman is aware of\nNot supported with the \"--remote\" flag") } } diff --git a/commands-demo.md b/commands-demo.md index 555f83af9..74879842f 100644 --- a/commands-demo.md +++ b/commands-demo.md @@ -27,11 +27,11 @@ | [podman-generate-systemd(1)](https://podman.readthedocs.io/en/latest/markdown/podman-generate-systemd.1.html) | Generate systemd unit file(s) for a container or pod. Not supported for the remote client | | [podman-history(1)](https://podman.readthedocs.io/en/latest/markdown/podman-history.1.html) | Shows the history of an image | | [podman-image(1)](https://podman.readthedocs.io/en/latest/image.html) | Manage Images | -| [podman-image-exists(1)](https://podman.readthedocs.io/en/latest/markdown/podman-image-exists.1.html) | Check if an image exists in local storage | | [podman-image-diff(1)](https://podman.readthedocs.io/en/latest/markdown/podman-image-diff.html) | Inspect changes on an image's filesystem. | +| [podman-image-exists(1)](https://podman.readthedocs.io/en/latest/markdown/podman-image-exists.1.html) | Check if an image exists in local storage | | [podman-image-prune(1)](https://podman.readthedocs.io/en/latest/markdown/podman-image-prune.1.html) | Remove all unused images from the local store | -| [podman-image-sign(1)](https://podman.readthedocs.io/en/latest/markdown/podman-image-sign.1.html) | Create a signature for an image | | [podman-image-search(1)](https://podman.readthedocs.io/en/latest/markdown/podman-search.1.html) | Search a registry for an image. | +| [podman-image-sign(1)](https://podman.readthedocs.io/en/latest/markdown/podman-image-sign.1.html) | Create a signature for an image | | [podman-image-tree(1)](https://podman.readthedocs.io/en/latest/markdown/podman-image-tree.1.html) | Prints layer hierarchy of an image in a tree format | | [podman-image-trust(1)](https://podman.readthedocs.io/en/latest/markdown/podman-image-trust.1.html) | Manage container registry image trust policy | | [podman-images(1)](https://podman.readthedocs.io/en/latest/markdown/podman-images.1.html) | List images in local storage | [![...](/docs/source/markdown/play.png)](https://podman.io/asciinema/podman/images/) | [Here](https://github.com/containers/Demos/blob/master/podman_cli/podman_images.sh) | @@ -58,9 +58,9 @@ | [podman-pod-exists(1)](https://podman.readthedocs.io/en/latest/markdown/podman-pod-exists.1.html) | Check if a pod exists in local storage | | [podman-pod-inspect(1)](https://podman.readthedocs.io/en/latest/markdown/podman-pod-inspect.1.html) | Displays information describing a pod | | [podman-pod-kill(1)](https://podman.readthedocs.io/en/latest/markdown/podman-pod-kill.1.html) | Kill the main process of each container in one or more pods | -| [podman-pod-ps(1)](https://podman.readthedocs.io/en/latest/markdown/podman-pod-ps.1.html) | Prints out information about pods | | [podman-pod-pause(1)](https://podman.readthedocs.io/en/latest/markdown/podman-pause.1.html) | Pause one or more containers | | [podman-pod-prune(1)](https://podman.readthedocs.io/en/latest/markdown/podman-pod-prune.1.html) | Remove all stopped pods and their containers | +| [podman-pod-ps(1)](https://podman.readthedocs.io/en/latest/markdown/podman-pod-ps.1.html) | Prints out information about pods | | [podman-pod-restart](https://podman.readthedocs.io/en/latest/markdown/podman-pod-restart.1.html) | Restart one or more pods | | [podman-pod-rm(1)](https://podman.readthedocs.io/en/latest/markdown/podman-pod-rm.1.html) | Remove one or more stopped pods and containers | | [podman-pod-start(1)](https://podman.readthedocs.io/en/latest/markdown/podman-pod-start.1.html) | Start one or more pods | diff --git a/contrib/cirrus/apiv2_test.sh b/contrib/cirrus/apiv2_test.sh index 33e9fbc6b..546fe8e30 100755 --- a/contrib/cirrus/apiv2_test.sh +++ b/contrib/cirrus/apiv2_test.sh @@ -7,7 +7,7 @@ source $(dirname $0)/lib.sh req_env_var GOSRC SCRIPT_BASE OS_RELEASE_ID OS_RELEASE_VER CONTAINER_RUNTIME VARLINK_LOG LOCAL_OR_REMOTE=local -if [[ "$TEST_REMOTE_CLIENT" = "true" ]]; then +if [[ "$RCLI" = "true" ]]; then LOCAL_OR_REMOTE=remote fi diff --git a/contrib/cirrus/build_release.sh b/contrib/cirrus/build_release.sh index 07db88f81..45634f368 100755 --- a/contrib/cirrus/build_release.sh +++ b/contrib/cirrus/build_release.sh @@ -4,11 +4,11 @@ set -e source $(dirname $0)/lib.sh -req_env_var TEST_REMOTE_CLIENT OS_RELEASE_ID GOSRC +req_env_var RCLI OS_RELEASE_ID GOSRC cd $GOSRC -if [[ "$TEST_REMOTE_CLIENT" == "true" ]] && [[ -z "$CROSS_PLATFORM" ]] +if [[ "$RCLI" == "true" ]] && [[ -z "$CROSS_PLATFORM" ]] then CROSS_PLATFORM=linux fi diff --git a/contrib/cirrus/check_image.sh b/contrib/cirrus/check_image.sh index 39c2be3f8..13172fe1c 100755 --- a/contrib/cirrus/check_image.sh +++ b/contrib/cirrus/check_image.sh @@ -6,7 +6,7 @@ source $(dirname $0)/lib.sh EVIL_UNITS="$($CIRRUS_WORKING_DIR/$PACKER_BASE/systemd_banish.sh --list)" -req_env_var PACKER_BUILDER_NAME TEST_REMOTE_CLIENT EVIL_UNITS OS_RELEASE_ID CG_FS_TYPE +req_env_var PACKER_BUILDER_NAME RCLI EVIL_UNITS OS_RELEASE_ID CG_FS_TYPE NFAILS=0 echo "Validating VM image" diff --git a/contrib/cirrus/container_test.sh b/contrib/cirrus/container_test.sh index 8a4ed9492..b56a12232 100644 --- a/contrib/cirrus/container_test.sh +++ b/contrib/cirrus/container_test.sh @@ -18,7 +18,7 @@ if [ "${ID}" != "fedora" ] || [ "${CONTAINER_RUNTIME}" != "" ]; then INTEGRATION_TEST_ENVS="SKIP_USERNS=1" fi -echo "$(date --rfc-3339=seconds) $(basename $0) started with '$*' and TEST_REMOTE_CLIENT='${TEST_REMOTE_CLIENT}'" +echo "$(date --rfc-3339=seconds) $(basename $0) started with '$*' and RCLI='${RCLI}'" pwd @@ -57,9 +57,9 @@ while getopts "bituv" opt; do esac done -# The TEST_REMOTE_CLIENT environment variable decides whether +# The RCLI environment variable decides whether # to test varlinke -if [[ "$TEST_REMOTE_CLIENT" == "true" ]]; then +if [[ "$RCLI" == "true" ]]; then remote=1 fi diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh index 692d5a236..c65f5e25f 100755 --- a/contrib/cirrus/integration_test.sh +++ b/contrib/cirrus/integration_test.sh @@ -7,7 +7,7 @@ source $(dirname $0)/lib.sh req_env_var GOSRC SCRIPT_BASE OS_RELEASE_ID OS_RELEASE_VER CONTAINER_RUNTIME VARLINK_LOG LOCAL_OR_REMOTE=local -if [[ "$TEST_REMOTE_CLIENT" = "true" ]]; then +if [[ "$RCLI" = "true" ]]; then LOCAL_OR_REMOTE=remote fi diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index d2af4d883..968b2de39 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -96,12 +96,12 @@ LILTO="timeout_attempt_delay_command 120s 5 30s" BIGTO="timeout_attempt_delay_command 300s 5 60s" # Safe env. vars. to transfer from root -> $ROOTLESS_USER (go env handled separately) -ROOTLESS_ENV_RE='(CIRRUS_.+)|(ROOTLESS_.+)|(.+_IMAGE.*)|(.+_BASE)|(.*DIRPATH)|(.*FILEPATH)|(SOURCE.*)|(DEPEND.*)|(.+_DEPS_.+)|(OS_REL.*)|(.+_ENV_RE)|(TRAVIS)|(CI.+)|(TEST_REMOTE.*)' +ROOTLESS_ENV_RE='(CIRRUS_.+)|(ROOTLESS_.+)|(.+_IMAGE.*)|(.+_BASE)|(.*DIRPATH)|(.*FILEPATH)|(SOURCE.*)|(DEPEND.*)|(.+_DEPS_.+)|(OS_REL.*)|(.+_ENV_RE)|(TRAVIS)|(CI.+)|(REMOTE.*)' # Unsafe env. vars for display SECRET_ENV_RE='(IRCID)|(ACCOUNT)|(GC[EP]..+)|(SSH)' SPECIALMODE="${SPECIALMODE:-none}" -TEST_REMOTE_CLIENT="${TEST_REMOTE_CLIENT:-false}" +RCLI="${RCLI:-false}" export CONTAINER_RUNTIME=${CONTAINER_RUNTIME:-podman} # When running as root, this may be empty or not, as a user, it MUST be set. diff --git a/contrib/cirrus/logcollector.sh b/contrib/cirrus/logcollector.sh index 0b179591a..859da2966 100755 --- a/contrib/cirrus/logcollector.sh +++ b/contrib/cirrus/logcollector.sh @@ -4,7 +4,7 @@ set -e source $(dirname $0)/lib.sh -req_env_var CIRRUS_WORKING_DIR OS_RELEASE_ID TEST_REMOTE_CLIENT +req_env_var CIRRUS_WORKING_DIR OS_RELEASE_ID RCLI # Assume there are other log collection commands to follow - Don't # let one break another that may be useful, but also keep any @@ -34,12 +34,12 @@ case $1 in journal) showrun journalctl -b ;; podman) showrun ./bin/podman system info ;; varlink) - if [[ "$TEST_REMOTE_CLIENT" == "true" ]] + if [[ "$RCLI" == "true" ]] then echo "(Trailing 100 lines of $VARLINK_LOG)" showrun tail -100 $VARLINK_LOG else - die 0 "\$TEST_REMOTE_CLIENT is not 'true': $TEST_REMOTE_CLIENT" + die 0 "\$RCLI is not 'true': $RCLI" fi ;; packages) diff --git a/contrib/cirrus/packer/fedora_packaging.sh b/contrib/cirrus/packer/fedora_packaging.sh index f19932a9f..4a8f62e45 100644 --- a/contrib/cirrus/packer/fedora_packaging.sh +++ b/contrib/cirrus/packer/fedora_packaging.sh @@ -26,7 +26,7 @@ source /usr/share/automation/environment # Set this to 1 to NOT enable updates-testing repository DISABLE_UPDATES_TESTING=${DISABLE_UPDATES_TESTING:0} -# Do not enable update-stesting on the previous Fedora release +# Do not enable updates-testing on the previous Fedora release if ((DISABLE_UPDATES_TESTING!=0)); then warn "Enabling updates-testing repository for image based on $FEDORA_BASE_IMAGE" $LILTO $SUDO ooe.sh dnf install -y 'dnf-command(config-manager)' @@ -37,7 +37,15 @@ fi $BIGTO ooe.sh $SUDO dnf update -y +# Fedora, as of 31, uses cgroups v2 by default. runc does not support +# cgroups v2, only crun does. (As of 2020-07-30 runc support is +# forthcoming but not even close to ready yet). To ensure a reliable +# runtime environment, force-remove runc if it is present. +# However, because a few other repos. which use these images still need +# it, ensure the runc package is cached in $PACKAGE_DOWNLOAD_DIR so +# it may be swap it in when required. REMOVE_PACKAGES=(runc) + INSTALL_PACKAGES=(\ autoconf automake @@ -118,11 +126,12 @@ INSTALL_PACKAGES=(\ python2 python3-PyYAML python3-dateutil - python3-psutil - python3-pytoml - python3-libsemanage python3-libselinux + python3-libsemanage python3-libvirt + python3-psutil + python3-pytoml + python3-requests redhat-rpm-config rpcbind rsync @@ -163,7 +172,7 @@ $BIGTO ooe.sh $SUDO dnf install -y ${INSTALL_PACKAGES[@]} # $BIGTO ooe.sh $SUDO dnf --enablerepo=updates-testing -y upgrade crun [[ ${#REMOVE_PACKAGES[@]} -eq 0 ]] || \ - $LILTO ooe.sh $SUDO dnf erase -y ${REMOVE_PACKAGES[@]} + $LILTO ooe.sh $SUDO dnf erase -y "${REMOVE_PACKAGES[@]}" if [[ ${#DOWNLOAD_PACKAGES[@]} -gt 0 ]]; then echo "Downloading packages for optional installation at runtime, as needed." @@ -171,8 +180,7 @@ if [[ ${#DOWNLOAD_PACKAGES[@]} -gt 0 ]]; then ooe.sh $SUDO dnf -y module enable cri-o:$(get_kubernetes_version) $SUDO mkdir -p "$PACKAGE_DOWNLOAD_DIR" cd "$PACKAGE_DOWNLOAD_DIR" - $LILTO ooe.sh $SUDO dnf download -y --resolve ${DOWNLOAD_PACKAGES[@]} - ls -la "$PACKAGE_DOWNLOAD_DIR/" + $LILTO ooe.sh $SUDO dnf download -y --resolve "${DOWNLOAD_PACKAGES[@]}" fi echo "Installing runtime tooling" diff --git a/contrib/cirrus/packer/ubuntu_packaging.sh b/contrib/cirrus/packer/ubuntu_packaging.sh index d11c612c5..935e81147 100644 --- a/contrib/cirrus/packer/ubuntu_packaging.sh +++ b/contrib/cirrus/packer/ubuntu_packaging.sh @@ -65,7 +65,7 @@ INSTALL_PACKAGES=(\ gettext git go-md2man - golang + golang-1.14 iproute2 iptables jq @@ -101,12 +101,14 @@ INSTALL_PACKAGES=(\ podman protobuf-c-compiler protobuf-compiler + python-dateutil python-protobuf python2 python3-dateutil python3-pip python3-psutil python3-pytoml + python3-requests python3-setuptools rsync runc @@ -135,6 +137,10 @@ if [[ "$OS_RELEASE_VER" -le 19 ]]; then python-minimal yum-utils ) +else + INSTALL_PACKAGES+=(\ + python-is-python3 + ) fi # Do this at the last possible moment to avoid dpkg lock conflicts @@ -144,22 +150,26 @@ $BIGTO ooe.sh $SUDOAPTGET upgrade echo "Installing general testing and system dependencies" # Necessary to update cache of newly added repos $LILTO ooe.sh $SUDOAPTGET update -$BIGTO ooe.sh $SUDOAPTGET install ${INSTALL_PACKAGES[@]} +$BIGTO ooe.sh $SUDOAPTGET install "${INSTALL_PACKAGES[@]}" if [[ ${#DOWNLOAD_PACKAGES[@]} -gt 0 ]]; then echo "Downloading packages for optional installation at runtime, as needed." $SUDO ln -s /var/cache/apt/archives "$PACKAGE_DOWNLOAD_DIR" - $LILTO ooe.sh $SUDOAPTGET install --download-only ${DOWNLOAD_PACKAGES[@]} - ls -la "$PACKAGE_DOWNLOAD_DIR/" + $LILTO ooe.sh $SUDOAPTGET install --download-only "${DOWNLOAD_PACKAGES[@]}" fi -echo "Installing runtime tooling" -# Save some runtime by having these already available +echo "Configuring Go environment" +# There are multiple (otherwise conflicting) versions of golang available +# on Ubuntu. Being primarily localized by env. vars and defaults, dropping +# a symlink is the appropriate way to "install" a specific version system-wide. +$SUDO ln -sf /usr/lib/go-1.14/bin/go /usr/bin/go +# Initially go was not installed cd $GOSRC -# Required since initially go was not installed -source $GOSRC/$SCRIPT_BASE/lib.sh +source $SCRIPT_BASE/lib.sh echo "Go environment has been setup:" go env + +echo "Building/Installing runtime tooling" $SUDO hack/install_catatonit.sh $SUDO make install.libseccomp.sudo -$SUDO make install.tools +$SUDO make install.tools GO_BUILD='go build' # -mod=vendor breaks this diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index e5f3168da..0b9d686d3 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -39,16 +39,6 @@ done cd "${GOSRC}/" case "${OS_RELEASE_ID}" in ubuntu) - apt-get update - apt-get install -y containers-common - if [[ "$OS_RELEASE_VER" == "19" ]]; then - apt-get purge -y --auto-remove golang* - apt-get install -y golang-1.13 - ln -s /usr/lib/go-1.13/bin/go /usr/bin/go - fi - if [[ "$OS_RELEASE_VER" == "20" ]]; then - apt-get install -y python-is-python3 - fi ;; fedora) # All SELinux distros need this for systemd-in-a-container @@ -113,7 +103,7 @@ case "$SPECIALMODE" in tee -a /etc/environment) && eval "$X" && echo "$X" X=$(echo "export SPECIALMODE='${SPECIALMODE}'" | \ tee -a /etc/environment) && eval "$X" && echo "$X" - X=$(echo "export TEST_REMOTE_CLIENT='${TEST_REMOTE_CLIENT}'" | \ + X=$(echo "export RCLI='${RCLI}'" | \ tee -a /etc/environment) && eval "$X" && echo "$X" setup_rootless fi diff --git a/contrib/cirrus/system_test.sh b/contrib/cirrus/system_test.sh index 33e9fbc6b..546fe8e30 100755 --- a/contrib/cirrus/system_test.sh +++ b/contrib/cirrus/system_test.sh @@ -7,7 +7,7 @@ source $(dirname $0)/lib.sh req_env_var GOSRC SCRIPT_BASE OS_RELEASE_ID OS_RELEASE_VER CONTAINER_RUNTIME VARLINK_LOG LOCAL_OR_REMOTE=local -if [[ "$TEST_REMOTE_CLIENT" = "true" ]]; then +if [[ "$RCLI" = "true" ]]; then LOCAL_OR_REMOTE=remote fi diff --git a/contrib/msi/podman.wxs b/contrib/msi/podman.wxs index c2c2cea4f..ff8160a53 100644 --- a/contrib/msi/podman.wxs +++ b/contrib/msi/podman.wxs @@ -24,8 +24,7 @@ <CreateFolder/> </Component> <Component Id="MainExecutable" Guid="73752F94-6589-4C7B-ABED-39D655A19714"> - <File Id="520C6E17-77A2-4F41-9611-30FA763A0702" Name="podman-remote-windows.exe" Source="bin/podman-remote-windows.exe"/> - <File Id="A14218A0-4180-44AC-B109-7C63B3099DCA" Name="podman.bat" Source="podman.bat" KeyPath="yes"/> + <File Id="520C6E17-77A2-4F41-9611-30FA763A0702" Name="podman.exe" Source="bin/podman-remote-windows.exe" KeyPath="yes"/> </Component> </Directory> </Directory> @@ -33,7 +32,7 @@ </Directory> <Property Id="setx" Value="setx.exe"/> - <CustomAction Id="ChangePath" ExeCommand="PATH "%PATH%;[INSTALLDIR] "" Property="setx" Execute="deferred" Impersonate="yes" Return="check"/> + <CustomAction Id="ChangePath" ExeCommand="PATH "%PATH%;[INSTALLDIR]"" Property="setx" Execute="deferred" Impersonate="yes" Return="check"/> <Feature Id="Complete" Level="1"> <ComponentRef Id="INSTALLDIR_Component"/> diff --git a/contrib/podmanimage/stable/Dockerfile b/contrib/podmanimage/stable/Dockerfile index fdf461f72..bcd3a5d3d 100644 --- a/contrib/podmanimage/stable/Dockerfile +++ b/contrib/podmanimage/stable/Dockerfile @@ -16,7 +16,7 @@ RUN useradd podman; yum -y update; yum -y reinstall shadow-utils; yum -y install ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ # chmod containers.conf and adjust storage.conf to enable Fuse storage. -RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock +RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf +RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/stable/manual/Containerfile b/contrib/podmanimage/stable/manual/Containerfile index 79ff95956..f9ae57dbf 100644 --- a/contrib/podmanimage/stable/manual/Containerfile +++ b/contrib/podmanimage/stable/manual/Containerfile @@ -29,8 +29,8 @@ RUN yum -y install /tmp/podman-1.7.0-3.fc30.x86_64.rpm fuse-overlayfs --exclude ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ # chmod containers.conf and adjust storage.conf to enable Fuse storage. -RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock +RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf +RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/testing/Dockerfile b/contrib/podmanimage/testing/Dockerfile index 0124879e0..97690360d 100644 --- a/contrib/podmanimage/testing/Dockerfile +++ b/contrib/podmanimage/testing/Dockerfile @@ -18,7 +18,7 @@ RUN useradd podman; yum -y update; yum -y reinstall shadow-utils; yum -y install ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ # chmod containers.conf and adjust storage.conf to enable Fuse storage. -RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock +RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf +RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/podmanimage/upstream/Dockerfile b/contrib/podmanimage/upstream/Dockerfile index 52b3e1d4d..ca7370de9 100644 --- a/contrib/podmanimage/upstream/Dockerfile +++ b/contrib/podmanimage/upstream/Dockerfile @@ -66,7 +66,7 @@ RUN useradd podman; yum -y update; yum -y reinstall shadow-utils; yum -y install ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/ # chmod containers.conf and adjust storage.conf to enable Fuse storage. -RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf -RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock +RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' -e 's|^mountopt[[:space:]]*=.*$|mountopt = "nodev,fsync=0"|g' /etc/containers/storage.conf +RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers /var/lib/shared/vfs-images /var/lib/shared/vfs-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock; touch /var/lib/shared/vfs-images/images.lock; touch /var/lib/shared/vfs-layers/layers.lock ENV _CONTAINERS_USERNS_CONFIGURED="" diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 2411eaabc..363aa60d7 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -91,6 +91,7 @@ Recommends: container-selinux Recommends: slirp4netns Recommends: fuse-overlayfs %endif +Recommends: xz # vendored libraries # awk '{print "Provides: bundled(golang("$1")) = "$2}' vendor.conf | sort diff --git a/contrib/systemd/system/podman.service b/contrib/systemd/system/podman.service index c8751168d..e14bbe078 100644 --- a/contrib/systemd/system/podman.service +++ b/contrib/systemd/system/podman.service @@ -6,5 +6,6 @@ Documentation=man:podman-system-service(1) StartLimitIntervalSec=0 [Service] -Type=simple +Type=notify +KillMode=process ExecStart=/usr/bin/podman system service diff --git a/docs/source/Commands.rst b/docs/source/Commands.rst index aba29bd82..096bdbedf 100644 --- a/docs/source/Commands.rst +++ b/docs/source/Commands.rst @@ -6,11 +6,13 @@ Commands :doc:`attach <markdown/podman-attach.1>` Attach to a running container +:doc:`auto-update <markdown/podman-auto-update.1>` Auto update containers according to their auto-update policy + :doc:`build <markdown/podman-build.1>` Build an image using instructions from Containerfiles :doc:`commit <markdown/podman-commit.1>` Create new image based on the changed container -:doc:`containers <managecontainers>` Manage Containers +:doc:`container <managecontainers>` Manage Containers :doc:`cp <markdown/podman-cp.1>` Copy files/folders between a container and the local filesystem @@ -52,6 +54,8 @@ Commands :doc:`logs <markdown/podman-logs.1>` Fetch the logs of a container +:doc:`manifest <manifest>` Create and manipulate manifest lists and image indexes + :doc:`mount <markdown/podman-mount.1>` Mount a working container's root filesystem :doc:`network <network>` Manage Networks @@ -94,12 +98,14 @@ Commands :doc:`top <markdown/podman-top.1>` Display the running processes of a container -:doc:`umount <markdown/podman-umount.1>` Unmounts working container's root filesystem +:doc:`unmount <markdown/podman-unmount.1>` Unmounts working container's root filesystem :doc:`unpause <markdown/podman-unpause.1>` Unpause the processes in one or more containers :doc:`unshare <markdown/podman-unshare.1>` Run a command in a modified user namespace +:doc:`untag <markdown/podman-untag.1>` Removes one or more names from a locally-stored image + :doc:`version <markdown/podman-version.1>` Display the Podman Version Information :doc:`volume <volume>` Manage volumes diff --git a/docs/source/connection.rst b/docs/source/connection.rst new file mode 100644 index 000000000..64eb18c57 --- /dev/null +++ b/docs/source/connection.rst @@ -0,0 +1,12 @@ +Manage the destination(s) for Podman service(s) +================= + +:doc:`add <markdown/podman-system-connection-add.1>` Record destination for the Podman service + +:doc:`default <markdown/podman-system-connection-default.1>` Set named destination as default for the Podman service + +:doc:`list <markdown/podman-system-connection-list.1>` List the destination for the Podman service(s) + +:doc:`remove <markdown/podman-system-connection-remove.1>` Delete named destination + +:doc:`rename <markdown/podman-system-connection-rename.1>` Rename the destination for Podman service diff --git a/docs/source/image.rst b/docs/source/image.rst index a8c6171e1..2b0ef3d43 100644 --- a/docs/source/image.rst +++ b/docs/source/image.rst @@ -4,6 +4,8 @@ Image :doc:`build <markdown/podman-build.1>` Build an image using instructions from Containerfiles +:doc:`diff <markdown/podman-image-diff.1>` Inspect changes on an image's filesystem + :doc:`exists <markdown/podman-image-exists.1>` Check if an image exists in local storage :doc:`history <markdown/podman-history.1>` Show history of a specified image @@ -16,6 +18,8 @@ Image :doc:`load <markdown/podman-load.1>` Load an image from container archive +:doc:`mount <markdown/podman-image-mount.1>` Mount an image's root filesystem. + :doc:`prune <markdown/podman-image-prune.1>` Remove unused images :doc:`pull <markdown/podman-pull.1>` Pull an image from a registry @@ -26,6 +30,8 @@ Image :doc:`save <markdown/podman-save.1>` Save image to an archive +:doc:`search <markdown/podman-search.1>` Search a registry for an image + :doc:`sign <markdown/podman-image-sign.1>` Sign an image :doc:`tag <markdown/podman-tag.1>` Add an additional name to a local image @@ -33,3 +39,7 @@ Image :doc:`tree <markdown/podman-image-tree.1>` Prints layer hierarchy of an image in a tree format :doc:`trust <markdown/podman-image-trust.1>` Manage container image trust policy + +:doc:`unmount <markdown/podman-unmount.1>` Unmount an image's root filesystem + +:doc:`untag <markdown/podman-untag.1>` Removes one or more names from a locally-stored image diff --git a/docs/source/index.rst b/docs/source/index.rst index 715ca2744..9a1381080 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,6 +15,7 @@ If you are completely new to containers, we recommend that you check out the :do :caption: Contents: Introduction + :doc:`<markdown/podman.1>` Simple management tool for pods, containers and images Commands Reference Tutorials diff --git a/docs/source/managecontainers.rst b/docs/source/managecontainers.rst index 5c5a83a82..849fd1d25 100644 --- a/docs/source/managecontainers.rst +++ b/docs/source/managecontainers.rst @@ -37,10 +37,10 @@ Manage Containers :doc:`port <markdown/podman-port.1>` List port mappings or a specific mapping for the container -:doc:`restart <markdown/podman-restart.1>` Restart one or more containers - :doc:`prune <markdown/podman-container-prune.1>` Remove all stopped containers +:doc:`restart <markdown/podman-restart.1>` Restart one or more containers + :doc:`restore <markdown/podman-container-restore.1>` Restores one or more containers from a checkpoint :doc:`rm <markdown/podman-rm.1>` Remove one or more containers @@ -57,7 +57,7 @@ Manage Containers :doc:`top <markdown/podman-top.1>` Display the running processes of a container -:doc:`umount <markdown/podman-umount.1>` Unmounts working container's root filesystem +:doc:`unmount <markdown/podman-unmount.1>` Unmounts working container's root filesystem :doc:`unpause <markdown/podman-unpause.1>` Unpause the processes in one or more containers diff --git a/docs/source/manifest.rst b/docs/source/manifest.rst new file mode 100644 index 000000000..c63c28502 --- /dev/null +++ b/docs/source/manifest.rst @@ -0,0 +1,14 @@ +Create and manipulate manifest lists and image indexes +================= + +:doc:`add <markdown/podman-manifest-add.1>` Add an image to a manifest list or image index + +:doc:`annotate <markdown/podman-manifest-annotate.1>` Add or update information about an entry in a manifest list or image index + +:doc:`create <markdown/podman-manifest-create.1>` Create a manifest list or image index + +:doc:`inspect <markdown/podman-manifest-inspect.1>` Display a manifest list or image index + +:doc:`push <markdown/podman-manifest-push.1>` Push a manifest list or image index to a registry + +:doc:`remove <markdown/podman-manifest-remove.1>` Remove an image from a manifest list or image index diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 9267e5729..05aea53b6 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -562,9 +562,15 @@ Valid values are: - `ns:<path>`: path to a network namespace to join - `private`: create a new namespace for the container (default) - `slirp4netns[:OPTIONS,...]`: use slirp4netns to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options: - **port_handler=rootlesskit**: Use rootlesskit for port forwarding. Default. - **port_handler=slirp4netns**: Use the slirp4netns port forwarding. - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default to false. + - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default is false. + - **cidr=CIDR**: Specify ip range to use for this network. (Default is `10.0.2.0/24`). + - **enable_ipv6=true|false**: Enable IPv6. Default is false. (Required for `outbound_addr6`). + - **outbound_addr=INTERFACE**: Specify the outbound interface slirp should bind to (ipv4 traffic only). + - **outbound_addr=IPv4**: Specify the outbound ipv4 address slirp should bind to. + - **outbound_addr6=INTERFACE**: Specify the outbound interface slirp should bind to (ipv6 traffic only). + - **outbound_addr6=IPv6**: Specify the outbound ipv6 address slirp should bind to. + - **port_handler=rootlesskit**: Use rootlesskit for port forwarding. Default. + - **port_handler=slirp4netns**: Use the slirp4netns port forwarding. **--network-alias**=*alias* @@ -750,6 +756,9 @@ Security Options - `seccomp=unconfined` : Turn off seccomp confinement for the container - `seccomp=profile.json` : White listed syscalls seccomp Json file to be used as a seccomp filter +- `proc-opts=OPTIONS` : Comma separated list of options to use for the /proc mount. More details for the + possible mount options are specified at **proc(5)** man page. + Note: Labeling can be disabled for all containers by setting label=false in the **containers.conf** (`/etc/containers/containers.conf` or `$HOME/.config/containers/containers.conf`) file. **--shm-size**=*size* @@ -796,8 +805,8 @@ Run container in systemd mode. The default is *true*. The value *always* enforces the systemd mode is enforced without looking at the executable name. Otherwise, if set to true and the -command you are running inside the container is systemd, /usr/sbin/init -or /sbin/init. +command you are running inside the container is systemd, /usr/sbin/init, +/sbin/init or /usr/local/sbin/init. If the command you are running inside of the container is systemd, Podman will setup tmpfs mount points in the following directories: @@ -1162,7 +1171,7 @@ b NOTE: Use the environment variable `TMPDIR` to change the temporary storage location of downloaded container images. Podman defaults to use `/var/tmp`. ## SEE ALSO -**subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1). +**subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1), **proc**(5)**. ## HISTORY October 2017, converted from Docker documentation to Podman by Dan Walsh for Podman <dwalsh@redhat.com> diff --git a/docs/source/markdown/podman-image.1.md b/docs/source/markdown/podman-image.1.md index 55e95d032..bbf5f6d84 100644 --- a/docs/source/markdown/podman-image.1.md +++ b/docs/source/markdown/podman-image.1.md @@ -20,8 +20,8 @@ The image command allows you to manage images | import | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a image or image's configuration. | | list | [podman-images(1)](podman-images.1.md) | List the container images on the system.(alias ls) | -| mount | [podman-image-mount(1)](podman-image-mount.1.md) | Mount an image's root filesystem. | | load | [podman-load(1)](podman-load.1.md) | Load an image from the docker archive. | +| mount | [podman-image-mount(1)](podman-image-mount.1.md) | Mount an image's root filesystem. | | prune | [podman-image-prune(1)](podman-image-prune.1.md) | Remove all unused images from the local store. | | pull | [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. | | push | [podman-push(1)](podman-push.1.md) | Push an image from local storage to elsewhere. | @@ -30,10 +30,10 @@ The image command allows you to manage images | search | [podman-search(1)](podman-search.1.md) | Search a registry for an image. | | sign | [podman-image-sign(1)](podman-image-sign.1.md) | Create a signature for an image. | | tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. | -| untag | [podman-untag(1)](podman-untag.1.md) | Removes one or more names from a locally-stored image. | -| unmount | [podman-image-unmount(1)](podman-image-unmount.1.md) | Unmount an image's root filesystem. | | tree | [podman-image-tree(1)](podman-image-tree.1.md) | Prints layer hierarchy of an image in a tree format. | | trust | [podman-image-trust(1)](podman-image-trust.1.md) | Manage container registry image trust policy. | +| unmount | [podman-image-unmount(1)](podman-image-unmount.1.md) | Unmount an image's root filesystem. | +| untag | [podman-untag(1)](podman-untag.1.md) | Removes one or more names from a locally-stored image. | ## SEE ALSO podman diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index e47d1fa83..ef78e15e3 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -570,9 +570,15 @@ Valid _mode_ values are: - **ns:**_path_: path to a network namespace to join; - `private`: create a new namespace for the container (default) - **slirp4netns[:OPTIONS,...]**: use **slirp4netns**(1) to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options: - **port_handler=rootlesskit**: Use rootlesskit for port forwarding. Default. - **port_handler=slirp4netns**: Use the slirp4netns port forwarding. - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default to false. + - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default is false. + - **cidr=CIDR**: Specify ip range to use for this network. (Default is `10.0.2.0/24`). + - **enable_ipv6=true|false**: Enable IPv6. Default is false. (Required for `outbound_addr6`). + - **outbound_addr=INTERFACE**: Specify the outbound interface slirp should bind to (ipv4 traffic only). + - **outbound_addr=IPv4**: Specify the outbound ipv4 address slirp should bind to. + - **outbound_addr6=INTERFACE**: Specify the outbound interface slirp should bind to (ipv6 traffic only). + - **outbound_addr6=IPv6**: Specify the outbound ipv6 address slirp should bind to. + - **port_handler=rootlesskit**: Use rootlesskit for port forwarding. Default. + - **port_handler=slirp4netns**: Use the slirp4netns port forwarding. **--network-alias**=*alias* @@ -768,6 +774,8 @@ Security Options - **no-new-privileges**: Disable container processes from gaining additional privileges - **seccomp=unconfined**: Turn off seccomp confinement for the container - **seccomp**=_profile.json_: Allowed syscall list seccomp JSON file to be used as a seccomp filter +- **proc-opts**=_OPTIONS_ : Comma separated list of options to use for the /proc mount. More details + for the possible mount options are specified at **proc(5)** man page. Note: Labeling can be disabled for all containers by setting **label=false** in the **containers.conf**(5) file. @@ -831,8 +839,8 @@ Run container in systemd mode. The default is **true**. The value *always* enforces the systemd mode is enforced without looking at the executable name. Otherwise, if set to **true** and the -command you are running inside the container is systemd, _/usr/sbin/init_ -or _/sbin/init_. +command you are running inside the container is systemd, _/usr/sbin/init_, +_/sbin/init_ or _/usr/local/sbin/init_. If the command you are running inside of the container is systemd Podman will setup tmpfs mount points in the following directories: @@ -905,20 +913,22 @@ Ulimit options. You can use **host** to copy the current configuration from the Sets the username or UID used and optionally the groupname or GID for the specified command. -Without this argument the command will be run as root in the container. +Without this argument, the command will run as the user specified in the container image. Unless overridden by a `USER` command in the Containerfile or by a value passed to this option, this user generally defaults to root. + +When a user namespace is not in use, the UID and GID used within the container and on the host will match. When user namespaces are in use, however, the UID and GID in the container may correspond to another UID and GID on the host. In rootless containers, for example, a user namespace is always used, and root in the container will by default correspond to the UID and GID of the user invoking Podman. **--userns**=**auto**|**host**|**keep-id**|**container:**_id_|**ns:**_namespace_ -Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value means user namespaces are disabled. +Set the user namespace mode for the container. It defaults to the **PODMAN_USERNS** environment variable. An empty value ("") means user namespaces are disabled unless an explicit mapping is set with they `--uidmapping` and `--gidmapping` options. - **auto**: automatically create a namespace. It is possible to specify other options to `auto`. The supported options are **size=SIZE** to specify an explicit size for the automatic user namespace. e.g. `--userns=auto:size=8192`. If `size` is not specified, `auto` will guess a size for the user namespace. **uidmapping=HOST_UID:CONTAINER_UID:SIZE** to force a UID mapping to be present in the user namespace. **gidmapping=HOST_UID:CONTAINER_UID:SIZE** to force a GID mapping to be present in the user namespace. -- **host**: run in the user namespace of the caller. This is the default if no user namespace options are set. The processes running in the container will have the same privileges on the host as any other process launched by the calling user. +- **host**: run in the user namespace of the caller. The processes running in the container will have the same privileges on the host as any other process launched by the calling user (default). - **keep-id**: creates a user namespace where the current rootless user's UID:GID are mapped to the same values in the container. This option is ignored for containers created by the root user. - **ns**: run the container in the given existing user namespace. -- **private**: create a new namespace for the container (default) +- **private**: create a new namespace for the container. - **container**: join the user namespace of the specified container. This option is incompatible with **--gidmap**, **--uidmap**, **--subuid** and **--subgid**. @@ -1441,7 +1451,7 @@ b NOTE: Use the environment variable `TMPDIR` to change the temporary storage location of downloaded container images. Podman defaults to use `/var/tmp`. ## SEE ALSO -**subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1). +**subgid**(5), **subuid**(5), **containers.conf**(5), **systemd.unit**(5), **setsebool**(8), **slirp4netns**(1), **fuse-overlayfs**(1), **proc**(5)**. ## HISTORY September 2018, updated by Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp> diff --git a/docs/source/markdown/podman-system-connection.1.md b/docs/source/markdown/podman-system-connection.1.md index 86199c6b9..0673aaee1 100644 --- a/docs/source/markdown/podman-system-connection.1.md +++ b/docs/source/markdown/podman-system-connection.1.md @@ -13,13 +13,13 @@ The user will be prompted for the ssh login password or key file pass phrase as ## COMMANDS -| Command | Man Page | Description | -| ------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------- | -| add | [podman-system-connection-add(1)](podman-system-connection-add.1.md) | Record destination for the Podman service | -| default | [podman-system-connection-default(1)](podman-system-connection-default.1.md) | Set named destination as default for the Podman service | -| list | [podman-system-connection-list(1)](podman-system-connection-list.1.md) | List the destination for the Podman service(s) | -| remove | [podman-system-connection-remove(1)](podman-system-connection-remove.1.md) | Delete named destination | -| rename | [podman-system-connection-rename(1)](podman-system-connection-rename.1.md) | Rename the destination for Podman service | +| Command | Man Page | Description | +| -------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------- | +| add | [podman-system-connection\-add(1)](podman-system-connection-add.1.md) | Record destination for the Podman service | +| default | [podman-system-connection\-default(1)](podman-system-connection-default.1.md) | Set named destination as default for the Podman service | +| list | [podman-system-connection\-list(1)](podman-system-connection-list.1.md) | List the destination for the Podman service(s) | +| remove | [podman-system-connection\-remove(1)](podman-system-connection-remove.1.md) | Delete named destination | +| rename | [podman-system-connection\-rename(1)](podman-system-connection-rename.1.md) | Rename the destination for Podman service | ## EXAMPLE ``` diff --git a/docs/source/system.rst b/docs/source/system.rst index 2d93a1d6d..566fd1a95 100644 --- a/docs/source/system.rst +++ b/docs/source/system.rst @@ -1,6 +1,8 @@ System ====== +:doc:`connection <connection>` Manage the destination(s) for Podman service(s) + :doc:`df <markdown/podman-system-df.1>` Show podman disk usage :doc:`info <markdown/podman-info.1>` Display podman system information @@ -12,3 +14,5 @@ System :doc:`renumber <markdown/podman-system-renumber.1>` Migrate lock numbers :doc:`reset <markdown/podman-system-reset.1>` Reset podman storage + +:doc:`service <markdown/podman-system-service.1>` Run an API service @@ -63,6 +63,6 @@ require ( golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 k8s.io/api v0.18.6 - k8s.io/apimachinery v0.18.6 + k8s.io/apimachinery v0.18.8 k8s.io/client-go v0.0.0-20190620085101-78d2af792bab ) @@ -145,6 +145,7 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= @@ -244,6 +245,7 @@ github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07 h1:rw3IAne6CDuVF github.com/ishidawataru/sctp v0.0.0-20191218070446-00ab2ac2db07/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= github.com/jamescun/tuntap v0.0.0-20190712092105-cb1fb277045c/go.mod h1:zzwpsgcYhzzIP5WyF8g9ivCv38cY9uAV9Gu0m3lThhE= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -644,6 +646,8 @@ k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA= k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag= k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/apimachinery v0.18.8 h1:jimPrycCqgx2QPearX3to1JePz7wSbVLq+7PdBTTwQ0= +k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g= k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= diff --git a/hack/xref-helpmsgs-manpages b/hack/xref-helpmsgs-manpages index 16b596589..7b617eed7 100755 --- a/hack/xref-helpmsgs-manpages +++ b/hack/xref-helpmsgs-manpages @@ -26,8 +26,14 @@ $| = 1; my $Default_Podman = './bin/podman'; my $PODMAN = $ENV{PODMAN} || $Default_Podman; +# Path to all doc files, including .rst and (down one level) markdown +my $Docs_Path = 'docs/source'; + # Path to podman markdown source files (of the form podman-*.1.md) -my $Markdown_Path = 'docs/source/markdown'; +my $Markdown_Path = "$Docs_Path/markdown"; + +# Global error count +my $Errs = 0; # END user-customizable section ############################################################################### @@ -96,35 +102,38 @@ sub main { my $help = podman_help(); my $man = podman_man('podman'); + my $rst = podman_rst(); + + xref_by_help($help, $man); + xref_by_man($help, $man); - my $retval = xref_by_help($help, $man) - + xref_by_man($help, $man); + xref_rst($help, $rst); - exit !!$retval; + exit !!$Errs; } +############################################################################### +# BEGIN cross-referencing + ################## # xref_by_help # Find keys in '--help' but not in man ################## sub xref_by_help { my ($help, $man, @subcommand) = @_; - my $errs = 0; for my $k (sort keys %$help) { if (exists $man->{$k}) { if (ref $help->{$k}) { - $errs += xref_by_help($help->{$k}, $man->{$k}, @subcommand, $k); + xref_by_help($help->{$k}, $man->{$k}, @subcommand, $k); } # Otherwise, non-ref is leaf node such as a --option } else { my $man = $man->{_path} || 'man'; warn "$ME: podman @subcommand --help lists $k, but $k not in $man\n"; - ++$errs; + ++$Errs; } } - - return $errs; } ################# @@ -137,13 +146,11 @@ sub xref_by_help { sub xref_by_man { my ($help, $man, @subcommand) = @_; - my $errs = 0; - # FIXME: this generates way too much output for my $k (grep { $_ ne '_path' } sort keys %$man) { if (exists $help->{$k}) { if (ref $man->{$k}) { - $errs += xref_by_man($help->{$k}, $man->{$k}, @subcommand, $k); + xref_by_man($help->{$k}, $man->{$k}, @subcommand, $k); } } elsif ($k ne '--help' && $k ne '-h') { @@ -175,13 +182,38 @@ sub xref_by_man { next if "@subcommand" eq 'system' && $k eq 'service'; warn "$ME: podman @subcommand: $k in $man, but not --help\n"; - ++$errs; + ++$Errs; } } +} - return $errs; +############## +# xref_rst # Cross-check *.rst files against help +############## +sub xref_rst { + my ($help, $rst, @subcommand) = @_; + + # Cross-check against rst (but only subcommands, not options). + # We key on $help because that is Absolute Truth: anything in podman --help + # must be referenced in an rst (the converse is not true). + for my $k (sort grep { $_ !~ /^-/ } keys %$help) { + # Check for subcommands, if any (eg podman system -> connection -> add) + if (ref $help->{$k}) { + xref_rst($help->{$k}, $rst->{$k}, @subcommand, $k); + } + + # Check that command is mentioned in at least one .rst file + if (! exists $rst->{$k}{_desc}) { + my @podman = ("podman", @subcommand, $k); + warn "$ME: no link in *.rst for @podman\n"; + ++$Errs; + } + } } +# END cross-referencing +############################################################################### +# BEGIN data gathering ################# # podman_help # Parse output of 'podman [subcommand] --help' @@ -249,6 +281,7 @@ sub podman_man { or die "$ME: Cannot read $manpath: $!\n"; my $section = ''; my @most_recent_flags; + my $previous_subcmd = ''; while (my $line = <$fh>) { chomp $line; next unless $line; # skip empty lines @@ -278,6 +311,11 @@ sub podman_man { elsif ($line =~ /^\|\s+(\S+)\s+\|\s+\[\S+\]\((\S+)\.1\.md\)/) { # $1 will be changed by recursion _*BEFORE*_ left-hand assignment my $subcmd = $1; + if ($previous_subcmd gt $subcmd) { + warn "$ME: $subpath: '$previous_subcmd' and '$subcmd' are out of order\n"; + ++$Errs; + } + $previous_subcmd = $subcmd; $man{$subcmd} = podman_man($2); } } @@ -315,4 +353,76 @@ sub podman_man { } +################ +# podman_rst # Parse contents of docs/source/*.rst +################ +sub podman_rst { + my %rst; + + # Read all .rst files, looking for ":doc:`subcmd <target>` description" + for my $rst (glob "$Docs_Path/*.rst") { + open my $fh, '<', $rst + or die "$ME: Cannot read $rst: $!\n"; + + # The basename of foo.rst is usually, but not always, the name of + # a podman subcommand. There are a few special cases: + (my $command = $rst) =~ s!^.*/(.*)\.rst!$1!; + + my $subcommand_href = \%rst; + if ($command eq 'Commands') { + ; + } + elsif ($command eq 'managecontainers') { + $subcommand_href = $rst{container} //= { }; + } + elsif ($command eq 'connection') { + $subcommand_href = $rst{system}{connection} //= { }; + } + else { + $subcommand_href = $rst{$command} //= { }; + } + + my $previous_subcommand = ''; + while (my $line = <$fh>) { + if ($line =~ /^:doc:`(\S+)\s+<(.*?)>`\s+(.*)/) { + my ($subcommand, $target, $desc) = ($1, $2, $3); + + # Check that entries are in alphabetical order + if ($subcommand lt $previous_subcommand) { + warn "$ME: $rst:$.: '$previous_subcommand' and '$subcommand' are out of order\n"; + ++$Errs; + } + $previous_subcommand = $subcommand; + + # Mark this subcommand as documented. + $subcommand_href->{$subcommand}{_desc} = $desc; + + # Check for invalid links. These will be one of two forms: + # <markdown/foo.1> -> markdown/foo.1.md + # <foo> -> foo.rst + if ($target =~ m!^markdown/!) { + if (! -e "$Docs_Path/$target.md") { + warn "$ME: $rst:$.: '$subcommand' links to nonexistent $target\n"; + ++$Errs; + } + } + else { + if (! -e "$Docs_Path/$target.rst") { + warn "$ME: $rst:$.: '$subcommand' links to nonexistent $target.rst\n"; + } + } + } + } + close $fh; + } + + # Special case: 'image trust set/show' are documented in image-trust.1 + $rst{image}{trust}{$_} = { _desc => 'ok' } for (qw(set show)); + + return \%rst; +} + +# END data gathering +############################################################################### + 1; diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 2575f0e86..9dd5ca465 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -424,6 +424,61 @@ func (s *BoltState) SetNamespace(ns string) error { return nil } +// GetName returns the name associated with a given ID. Since IDs are globally +// unique, it works for both containers and pods. +// Returns ErrNoSuchCtr if the ID does not exist. +func (s *BoltState) GetName(id string) (string, error) { + if id == "" { + return "", define.ErrEmptyID + } + + if !s.valid { + return "", define.ErrDBClosed + } + + idBytes := []byte(id) + + db, err := s.getDBCon() + if err != nil { + return "", err + } + defer s.deferredCloseDBCon(db) + + name := "" + + err = db.View(func(tx *bolt.Tx) error { + idBkt, err := getIDBucket(tx) + if err != nil { + return err + } + + nameBytes := idBkt.Get(idBytes) + if nameBytes == nil { + return define.ErrNoSuchCtr + } + + if s.namespaceBytes != nil { + nsBkt, err := getNSBucket(tx) + if err != nil { + return err + } + + idNs := nsBkt.Get(idBytes) + if !bytes.Equal(idNs, s.namespaceBytes) { + return define.ErrNoSuchCtr + } + } + + name = string(nameBytes) + return nil + }) + if err != nil { + return "", err + } + + return name, nil +} + // Container retrieves a single container from the state by its full ID func (s *BoltState) Container(id string) (*Container, error) { if id == "" { diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 9fb9738dc..fdee3877c 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -626,7 +626,7 @@ func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) erro Destination: "/sys/fs/cgroup/systemd", Type: "bind", Source: "/sys/fs/cgroup/systemd", - Options: []string{"bind", "nodev", "noexec", "nosuid"}, + Options: []string{"bind", "nodev", "noexec", "nosuid", "rprivate"}, } g.AddMount(systemdMnt) g.AddLinuxMaskedPaths("/sys/fs/cgroup/systemd/release_agent") diff --git a/libpod/events/config.go b/libpod/events/config.go index c34408e63..bb35c03c0 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -101,6 +101,8 @@ const ( Attach Status = "attach" // AutoUpdate ... AutoUpdate Status = "auto-update" + // Build ... + Build Status = "build" // Checkpoint ... Checkpoint Status = "checkpoint" // Cleanup ... diff --git a/libpod/events/events.go b/libpod/events/events.go index 0253b1ee5..722c9595e 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -127,6 +127,8 @@ func StringToStatus(name string) (Status, error) { switch name { case Attach.String(): return Attach, nil + case Build.String(): + return Build, nil case Checkpoint.String(): return Checkpoint, nil case Cleanup.String(): diff --git a/libpod/image/filters.go b/libpod/image/filters.go index 9738a7d5e..db647954f 100644 --- a/libpod/image/filters.go +++ b/libpod/image/filters.go @@ -29,6 +29,26 @@ func CreatedBeforeFilter(createTime time.Time) ResultFilter { } } +// IntermediateFilter returns filter for intermediate images (i.e., images +// with children and no tags). +func (ir *Runtime) IntermediateFilter(ctx context.Context, images []*Image) (ResultFilter, error) { + tree, err := ir.layerTree() + if err != nil { + return nil, err + } + return func(i *Image) bool { + if len(i.Names()) > 0 { + return true + } + children, err := tree.children(ctx, i, false) + if err != nil { + logrus.Error(err.Error()) + return false + } + return len(children) == 0 + }, nil +} + // CreatedAfterFilter allows you to filter on images created after // the given time.Time func CreatedAfterFilter(createTime time.Time) ResultFilter { diff --git a/libpod/image/image.go b/libpod/image/image.go index 4664da63e..6106084d5 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -859,26 +859,6 @@ func (i *Image) Dangling() bool { return len(i.Names()) == 0 } -// Intermediate returns true if the image is cache or intermediate image. -// Cache image has parent and child. -func (i *Image) Intermediate(ctx context.Context) (bool, error) { - parent, err := i.IsParent(ctx) - if err != nil { - return false, err - } - if !parent { - return false, nil - } - img, err := i.GetParent(ctx) - if err != nil { - return false, err - } - if img != nil { - return true, nil - } - return false, nil -} - // User returns the image's user func (i *Image) User(ctx context.Context) (string, error) { imgInspect, err := i.inspect(ctx, false) @@ -1217,7 +1197,7 @@ func splitString(input string) string { // the parent of any other layer in store. Double check that image with that // layer exists as well. func (i *Image) IsParent(ctx context.Context) (bool, error) { - children, err := i.getChildren(ctx, 1) + children, err := i.getChildren(ctx, false) if err != nil { if errors.Cause(err) == ErrImageIsBareList { return false, nil @@ -1292,63 +1272,16 @@ func areParentAndChild(parent, child *imgspecv1.Image) bool { // GetParent returns the image ID of the parent. Return nil if a parent is not found. func (i *Image) GetParent(ctx context.Context) (*Image, error) { - var childLayer *storage.Layer - images, err := i.imageruntime.GetImages() + tree, err := i.imageruntime.layerTree() if err != nil { return nil, err } - if i.TopLayer() != "" { - if childLayer, err = i.imageruntime.store.Layer(i.TopLayer()); err != nil { - return nil, err - } - } - // fetch the configuration for the child image - child, err := i.ociv1Image(ctx) - if err != nil { - if errors.Cause(err) == ErrImageIsBareList { - return nil, nil - } - return nil, err - } - for _, img := range images { - if img.ID() == i.ID() { - continue - } - candidateLayer := img.TopLayer() - // as a child, our top layer, if we have one, is either the - // candidate parent's layer, or one that's derived from it, so - // skip over any candidate image where we know that isn't the - // case - if childLayer != nil { - // The child has at least one layer, so a parent would - // have a top layer that's either the same as the child's - // top layer or the top layer's recorded parent layer, - // which could be an empty value. - if candidateLayer != childLayer.Parent && candidateLayer != childLayer.ID { - continue - } - } else { - // The child has no layers, but the candidate does. - if candidateLayer != "" { - continue - } - } - // fetch the configuration for the candidate image - candidate, err := img.ociv1Image(ctx) - if err != nil { - return nil, err - } - // compare them - if areParentAndChild(candidate, child) { - return img, nil - } - } - return nil, nil + return tree.parent(ctx, i) } // GetChildren returns a list of the imageIDs that depend on the image func (i *Image) GetChildren(ctx context.Context) ([]string, error) { - children, err := i.getChildren(ctx, 0) + children, err := i.getChildren(ctx, true) if err != nil { if errors.Cause(err) == ErrImageIsBareList { return nil, nil @@ -1358,62 +1291,15 @@ func (i *Image) GetChildren(ctx context.Context) ([]string, error) { return children, nil } -// getChildren returns a list of at most "max" imageIDs that depend on the image -func (i *Image) getChildren(ctx context.Context, max int) ([]string, error) { - var children []string - - if _, err := i.toImageRef(ctx); err != nil { - return nil, nil - } - - images, err := i.imageruntime.GetImages() - if err != nil { - return nil, err - } - - // fetch the configuration for the parent image - parent, err := i.ociv1Image(ctx) +// getChildren returns a list of imageIDs that depend on the image. If all is +// false, only the first child image is returned. +func (i *Image) getChildren(ctx context.Context, all bool) ([]string, error) { + tree, err := i.imageruntime.layerTree() if err != nil { return nil, err } - parentLayer := i.TopLayer() - for _, img := range images { - if img.ID() == i.ID() { - continue - } - if img.TopLayer() == "" { - if parentLayer != "" { - // this image has no layers, but we do, so - // it can't be derived from this one - continue - } - } else { - candidateLayer, err := img.Layer() - if err != nil { - return nil, err - } - // if this image's top layer is not our top layer, and is not - // based on our top layer, we can skip it - if candidateLayer.Parent != parentLayer && candidateLayer.ID != parentLayer { - continue - } - } - // fetch the configuration for the candidate image - candidate, err := img.ociv1Image(ctx) - if err != nil { - return nil, err - } - // compare them - if areParentAndChild(parent, candidate) { - children = append(children, img.ID()) - } - // if we're not building an exhaustive list, maybe we're done? - if max > 0 && len(children) >= max { - break - } - } - return children, nil + return tree.children(ctx, i, all) } // InputIsID returns a bool if the user input for an image @@ -1670,6 +1556,7 @@ type LayerInfo struct { // GetLayersMapWithImageInfo returns map of image-layers, with associated information like RepoTags, parent and list of child layers. func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, error) { + // TODO: evaluate if we can reuse `layerTree` here. // Memory allocated to store map of layers with key LayerID. // Map will build dependency chain with ParentID and ChildID(s) diff --git a/libpod/image/layer_tree.go b/libpod/image/layer_tree.go new file mode 100644 index 000000000..3699655fd --- /dev/null +++ b/libpod/image/layer_tree.go @@ -0,0 +1,222 @@ +package image + +import ( + "context" + + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +// layerTree is an internal representation of local layers. +type layerTree struct { + // nodes is the actual layer tree with layer IDs being keys. + nodes map[string]*layerNode + // ociCache is a cache for Image.ID -> OCI Image. Translations are done + // on-demand. + ociCache map[string]*ociv1.Image +} + +// node returns a layerNode for the specified layerID. +func (t *layerTree) node(layerID string) *layerNode { + node, exists := t.nodes[layerID] + if !exists { + node = &layerNode{} + t.nodes[layerID] = node + } + return node +} + +// toOCI returns an OCI image for the specified image. +func (t *layerTree) toOCI(ctx context.Context, i *Image) (*ociv1.Image, error) { + var err error + oci, exists := t.ociCache[i.ID()] + if !exists { + oci, err = i.ociv1Image(ctx) + t.ociCache[i.ID()] = oci + } + return oci, err +} + +// layerNode is a node in a layerTree. It's ID is the key in a layerTree. +type layerNode struct { + children []*layerNode + images []*Image + parent *layerNode +} + +// layerTree extracts a layerTree from the layers in the local storage and +// relates them to the specified images. +func (ir *Runtime) layerTree() (*layerTree, error) { + layers, err := ir.store.Layers() + if err != nil { + return nil, err + } + + images, err := ir.GetImages() + if err != nil { + return nil, err + } + + tree := layerTree{ + nodes: make(map[string]*layerNode), + ociCache: make(map[string]*ociv1.Image), + } + + // First build a tree purely based on layer information. + for _, layer := range layers { + node := tree.node(layer.ID) + if layer.Parent == "" { + continue + } + parent := tree.node(layer.Parent) + node.parent = parent + parent.children = append(parent.children, node) + } + + // Now assign the images to each (top) layer. + for i := range images { + img := images[i] // do not leak loop variable outside the scope + topLayer := img.TopLayer() + if topLayer == "" { + continue + } + node, exists := tree.nodes[topLayer] + if !exists { + return nil, errors.Errorf("top layer %s of image %s not found in layer tree", img.TopLayer(), img.ID()) + } + node.images = append(node.images, img) + } + + return &tree, nil +} + +// children returns the image IDs of children . Child images are images +// with either the same top layer as parent or parent being the true parent +// layer. Furthermore, the history of the parent and child images must match +// with the parent having one history item less. +// If all is true, all images are returned. Otherwise, the first image is +// returned. +func (t *layerTree) children(ctx context.Context, parent *Image, all bool) ([]string, error) { + if parent.TopLayer() == "" { + return nil, nil + } + + var children []string + + parentNode, exists := t.nodes[parent.TopLayer()] + if !exists { + return nil, errors.Errorf("layer not found in layer tree: %q", parent.TopLayer()) + } + + parentID := parent.ID() + parentOCI, err := t.toOCI(ctx, parent) + if err != nil { + return nil, err + } + + // checkParent returns true if child and parent are in such a relation. + checkParent := func(child *Image) (bool, error) { + if parentID == child.ID() { + return false, nil + } + childOCI, err := t.toOCI(ctx, child) + if err != nil { + return false, err + } + // History check. + return areParentAndChild(parentOCI, childOCI), nil + } + + // addChildrenFrom adds child images of parent to children. Returns + // true if any image is a child of parent. + addChildrenFromNode := func(node *layerNode) (bool, error) { + foundChildren := false + for _, childImage := range node.images { + isChild, err := checkParent(childImage) + if err != nil { + return foundChildren, err + } + if isChild { + foundChildren = true + children = append(children, childImage.ID()) + if all { + return foundChildren, nil + } + } + } + return foundChildren, nil + } + + // First check images where parent's top layer is also the parent + // layer. + for _, childNode := range parentNode.children { + found, err := addChildrenFromNode(childNode) + if err != nil { + return nil, err + } + if found && all { + return children, nil + } + } + + // Now check images with the same top layer. + if _, err := addChildrenFromNode(parentNode); err != nil { + return nil, err + } + + return children, nil +} + +// parent returns the parent image or nil if no parent image could be found. +func (t *layerTree) parent(ctx context.Context, child *Image) (*Image, error) { + if child.TopLayer() == "" { + return nil, nil + } + + node, exists := t.nodes[child.TopLayer()] + if !exists { + return nil, errors.Errorf("layer not found in layer tree: %q", child.TopLayer()) + } + + childOCI, err := t.toOCI(ctx, child) + if err != nil { + return nil, err + } + + // Check images from the parent node (i.e., parent layer) and images + // with the same layer (i.e., same top layer). + childID := child.ID() + images := node.images + if node.parent != nil { + images = append(images, node.parent.images...) + } + for _, parent := range images { + if parent.ID() == childID { + continue + } + parentOCI, err := t.toOCI(ctx, parent) + if err != nil { + return nil, err + } + // History check. + if areParentAndChild(parentOCI, childOCI) { + return parent, nil + } + } + + return nil, nil +} + +// hasChildrenAndParent returns true if the specified image has children and a +// parent. +func (t *layerTree) hasChildrenAndParent(ctx context.Context, i *Image) (bool, error) { + children, err := t.children(ctx, i, false) + if err != nil { + return false, err + } + if len(children) == 0 { + return false, nil + } + parent, err := t.parent(ctx, i) + return parent != nil, err +} diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 8c9267650..5a9ca5d8e 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -66,6 +66,12 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I if err != nil { return nil, err } + + tree, err := ir.layerTree() + if err != nil { + return nil, err + } + for _, i := range allImages { // filter the images based on this. for _, filterFunc := range filterFuncs { @@ -85,8 +91,9 @@ func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []I } } - //skip the cache or intermediate images - intermediate, err := i.Intermediate(ctx) + // skip the cache (i.e., with parent) and intermediate (i.e., + // with children) images + intermediate, err := tree.hasChildrenAndParent(ctx, i) if err != nil { return nil, err } diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 2ac05e88d..0de25a6ef 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -106,6 +106,36 @@ func (s *InMemoryState) SetNamespace(ns string) error { return nil } +// GetName retrieves the name associated with a given ID. +// Works with both Container and Pod IDs. +func (s *InMemoryState) GetName(id string) (string, error) { + if id == "" { + return "", define.ErrEmptyID + } + + var idIndex *truncindex.TruncIndex + if s.namespace != "" { + nsIndex, ok := s.namespaceIndexes[s.namespace] + if !ok { + // We have no containers in the namespace + // Return false + return "", define.ErrNoSuchCtr + } + idIndex = nsIndex.idIndex + } else { + idIndex = s.idIndex + } + + fullID, err := idIndex.Get(id) + if err != nil { + if err == truncindex.ErrNotExist { + return "", define.ErrNoSuchCtr + } + return "", errors.Wrapf(err, "error performing truncindex lookup for ID %s", id) + } + return fullID, nil +} + // Container retrieves a container from its full ID func (s *InMemoryState) Container(id string) (*Container, error) { if id == "" { diff --git a/libpod/logs/log.go b/libpod/logs/log.go index c2545e188..a9554088b 100644 --- a/libpod/logs/log.go +++ b/libpod/logs/log.go @@ -101,11 +101,14 @@ func getTailLog(path string, tail int) ([]*LogLine, error) { if err != nil { if errors.Cause(err) == io.EOF { inputs <- []string{leftover} - close(inputs) - break + } else { + logrus.Error(err) } - logrus.Error(err) close(inputs) + if err := f.Close(); err != nil { + logrus.Error(err) + } + break } line := strings.Split(s+leftover, "\n") if len(line) > 1 { @@ -136,9 +139,6 @@ func getTailLog(path string, tail int) ([]*LogLine, error) { } // if we have enough loglines, we can hangup if nllCounter >= tail { - if err := f.Close(); err != nil { - logrus.Error(err) - } break } } diff --git a/libpod/logs/reversereader/reversereader.go b/libpod/logs/reversereader/reversereader.go index 72d9ad975..4fa1a3f88 100644 --- a/libpod/logs/reversereader/reversereader.go +++ b/libpod/logs/reversereader/reversereader.go @@ -60,7 +60,7 @@ func (r *ReverseReader) Read() (string, error) { if int64(n) < r.readSize { b = b[0:n] } - // Set to the next page boundary - r.offset = -r.readSize + // Move the offset one pagesize up + r.offset -= r.readSize return string(b), nil } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 844748970..6f266e5d6 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -171,6 +171,9 @@ type slirpFeatures struct { HasMTU bool HasEnableSandbox bool HasEnableSeccomp bool + HasCIDR bool + HasOutboundAddr bool + HasIPv6 bool } type slirp4netnsCmdArg struct { @@ -197,6 +200,9 @@ func checkSlirpFlags(path string) (*slirpFeatures, error) { HasMTU: strings.Contains(string(out), "--mtu"), HasEnableSandbox: strings.Contains(string(out), "--enable-sandbox"), HasEnableSeccomp: strings.Contains(string(out), "--enable-seccomp"), + HasCIDR: strings.Contains(string(out), "--cidr"), + HasOutboundAddr: strings.Contains(string(out), "--outbound-addr"), + HasIPv6: strings.Contains(string(out), "--enable-ipv6"), }, nil } @@ -223,23 +229,73 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { havePortMapping := len(ctr.Config().PortMappings) > 0 logPath := filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("slirp4netns-%s.log", ctr.config.ID)) + cidr := "" isSlirpHostForward := false disableHostLoopback := true + enableIPv6 := false + outboundAddr := "" + outboundAddr6 := "" + if ctr.config.NetworkOptions != nil { slirpOptions := ctr.config.NetworkOptions["slirp4netns"] for _, o := range slirpOptions { - switch o { - case "port_handler=slirp4netns": - isSlirpHostForward = true - case "port_handler=rootlesskit": - isSlirpHostForward = false - case "allow_host_loopback=true": - disableHostLoopback = false - case "allow_host_loopback=false": - disableHostLoopback = true + parts := strings.Split(o, "=") + option, value := parts[0], parts[1] + + switch option { + case "cidr": + ipv4, _, err := net.ParseCIDR(value) + if err != nil || ipv4.To4() == nil { + return errors.Errorf("invalid cidr %q", value) + } + cidr = value + case "port_handler": + switch value { + case "slirp4netns": + isSlirpHostForward = true + case "rootlesskit": + isSlirpHostForward = false + default: + return errors.Errorf("unknown port_handler for slirp4netns: %q", value) + } + case "allow_host_loopback": + switch value { + case "true": + disableHostLoopback = false + case "false": + disableHostLoopback = true + default: + return errors.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value) + } + case "enable_ipv6": + switch value { + case "true": + enableIPv6 = true + case "false": + enableIPv6 = false + default: + return errors.Errorf("invalid value of enable_ipv6 for slirp4netns: %q", value) + } + case "outbound_addr": + ipv4 := net.ParseIP(value) + if ipv4 == nil || ipv4.To4() == nil { + _, err := net.InterfaceByName(value) + if err != nil { + return errors.Errorf("invalid outbound_addr %q", value) + } + } + outboundAddr = value + case "outbound_addr6": + ipv6 := net.ParseIP(value) + if ipv6 == nil || ipv6.To4() != nil { + _, err := net.InterfaceByName(value) + if err != nil { + return errors.Errorf("invalid outbound_addr6: %q", value) + } + } + outboundAddr6 = value default: return errors.Errorf("unknown option for slirp4netns: %q", o) - } } } @@ -262,6 +318,37 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { cmdArgs = append(cmdArgs, "--enable-seccomp") } + if cidr != "" { + if !slirpFeatures.HasCIDR { + return errors.Errorf("cidr not supported") + } + cmdArgs = append(cmdArgs, fmt.Sprintf("--cidr=%s", cidr)) + } + + if enableIPv6 { + if !slirpFeatures.HasIPv6 { + return errors.Errorf("enable_ipv6 not supported") + } + cmdArgs = append(cmdArgs, "--enable-ipv6") + } + + if outboundAddr != "" { + if !slirpFeatures.HasOutboundAddr { + return errors.Errorf("outbound_addr not supported") + } + cmdArgs = append(cmdArgs, fmt.Sprintf("--outbound-addr=%s", outboundAddr)) + } + + if outboundAddr6 != "" { + if !slirpFeatures.HasOutboundAddr || !slirpFeatures.HasIPv6 { + return errors.Errorf("outbound_addr6 not supported") + } + if !enableIPv6 { + return errors.Errorf("enable_ipv6=true is required for outbound_addr6") + } + cmdArgs = append(cmdArgs, fmt.Sprintf("--outbound-addr6=%s", outboundAddr6)) + } + var apiSocket string if havePortMapping && isSlirpHostForward { apiSocket = filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("%s.net", ctr.config.ID)) diff --git a/libpod/runtime.go b/libpod/runtime.go index 3021ef3f4..8a7053e33 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -732,6 +732,22 @@ func (r *Runtime) GetStore() storage.Store { return r.store } +// GetName retrieves the name associated with a given full ID. +// This works for both containers and pods, and does not distinguish between the +// two. +// If the given ID does not correspond to any existing Pod or Container, +// ErrNoSuchCtr is returned. +func (r *Runtime) GetName(id string) (string, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return "", define.ErrRuntimeStopped + } + + return r.state.GetName(id) +} + // DBConfig is a set of Libpod runtime configuration settings that are saved in // a State when it is first created, and can subsequently be retrieved. type DBConfig struct { diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 4b5129f44..a95cd1d7a 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -17,6 +17,7 @@ import ( "github.com/containers/image/v5/oci/layout" "github.com/containers/image/v5/types" "github.com/containers/podman/v2/libpod/define" + "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/pkg/util" "github.com/containers/storage" @@ -150,9 +151,21 @@ func removeStorageContainers(ctrIDs []string, store storage.Store) error { return nil } +// newBuildEvent creates a new event based on completion of a built image +func (r *Runtime) newImageBuildCompleteEvent(idOrName string) { + e := events.NewEvent(events.Build) + e.Type = events.Image + e.Name = idOrName + if err := r.eventer.Write(e); err != nil { + logrus.Errorf("unable to write build event: %q", err) + } +} + // Build adds the runtime to the imagebuildah call func (r *Runtime) Build(ctx context.Context, options imagebuildah.BuildOptions, dockerfiles ...string) (string, reference.Canonical, error) { id, ref, err := imagebuildah.BuildDockerfiles(ctx, r.store, options, dockerfiles...) + // Write event for build completion + r.newImageBuildCompleteEvent(id) return id, ref, err } diff --git a/libpod/state.go b/libpod/state.go index 6206a2994..44632b02f 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -43,6 +43,12 @@ type State interface { // containers and pods in all namespaces will be returned. SetNamespace(ns string) error + // Resolve an ID into a Name. Since Podman names and IDs are globally + // unique between Pods and Containers, the ID may belong to either a pod + // or container. Despite this, we will always return ErrNoSuchCtr if the + // ID does not exist. + GetName(id string) (string, error) + // Return a container from the database from its full ID. // If the container is not in the set namespace, an error will be // returned. diff --git a/nix/default.nix b/nix/default.nix index 4fe818b39..cc8786ce0 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -7,6 +7,15 @@ let libassuan = (static pkg.libassuan); libgpgerror = (static pkg.libgpgerror); libseccomp = (static pkg.libseccomp); + glib = (static pkg.glib).overrideAttrs(x: { + outputs = [ "bin" "out" "dev" ]; + mesonFlags = [ + "-Ddefault_library=static" + "-Ddevbindir=${placeholder ''dev''}/bin" + "-Dgtk_doc=false" + "-Dnls=disabled" + ]; + }); }; }; }); diff --git a/nix/nixpkgs.json b/nix/nixpkgs.json index 8eeb4f470..976284ed4 100644 --- a/nix/nixpkgs.json +++ b/nix/nixpkgs.json @@ -1,7 +1,7 @@ { "url": "https://github.com/nixos/nixpkgs", - "rev": "b49e7987632e4c7ab3a093fdfc433e1826c4b9d7", - "date": "2020-07-26T09:18:52+02:00", - "sha256": "1mj6fy0p24izmasl653s5z4f2ka9v3b6mys45kjrqmkv889yk2r6", + "rev": "d6a445fe821052861b379d9b6c02d21623c25464", + "date": "2020-08-11T04:28:16+01:00", + "sha256": "064scwaxg8qg4xbmq07hag57saa4bhsb4pgg5h5vfs4nhhwvchg9", "fetchSubmodules": false } diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 47ea6c40d..e343a9e0c 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -41,7 +41,6 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { Last int `schema:"last"` // alias for limit Limit int `schema:"limit"` Namespace bool `schema:"namespace"` - Pod bool `schema:"pod"` Size bool `schema:"size"` Sync bool `schema:"sync"` }{ @@ -72,7 +71,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { Size: query.Size, Sort: "", Namespace: query.Namespace, - Pod: query.Pod, + Pod: true, Sync: query.Sync, } pss, err := ps.GetContainerLists(runtime, opts) diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 28b1dda38..aad00c93b 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -102,20 +102,14 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { if query.All { return images, nil } - returnImages := []*image.Image{} - for _, img := range images { - if len(img.Names()) == 0 { - parent, err := img.IsParent(r.Context()) - if err != nil { - return nil, err - } - if parent { - continue - } - } - returnImages = append(returnImages, img) + + filter, err := runtime.ImageRuntime().IntermediateFilter(r.Context(), images) + if err != nil { + return nil, err } - return returnImages, nil + images = image.FilterImages(images, []image.ResultFilter{filter}) + + return images, nil } func GetImage(r *http.Request, name string) (*image.Image, error) { diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index edc1ee3c8..0ad5d29ea 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -661,11 +661,10 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // type: boolean // description: Include namespace information // default: false - // - in: query // name: pod // type: boolean // default: false - // description: Include Pod ID and Name if applicable + // description: Ignored. Previously included details on pod name and ID that are currently included by default. // - in: query // name: size // type: boolean diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 18b48a3f6..e7c031234 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -2,6 +2,7 @@ package server import ( "context" + "fmt" "log" "net" "net/http" @@ -17,6 +18,7 @@ import ( "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/api/server/idletracker" "github.com/coreos/go-systemd/v22/activation" + "github.com/coreos/go-systemd/v22/daemon" "github.com/gorilla/mux" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -147,8 +149,31 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li return &server, nil } -// Serve starts responding to HTTP requests +// If the NOTIFY_SOCKET is set, communicate the PID and readiness, and +// further unset NOTIFY_SOCKET to prevent containers from sending +// messages and unset INVOCATION_ID so conmon and containers are in +// the correct cgroup. +func setupSystemd() { + if len(os.Getenv("NOTIFY_SOCKET")) == 0 { + return + } + payload := fmt.Sprintf("MAINPID=%d", os.Getpid()) + payload += "\n" + payload += daemon.SdNotifyReady + if sent, err := daemon.SdNotify(true, payload); err != nil { + logrus.Errorf("Error notifying systemd of Conmon PID: %s", err.Error()) + } else if sent { + logrus.Debugf("Notify sent successfully") + } + + if err := os.Unsetenv("INVOCATION_ID"); err != nil { + logrus.Errorf("Error unsetting INVOCATION_ID: %s", err.Error()) + } +} + +// Serve starts responding to HTTP requests. func (s *APIServer) Serve() error { + setupSystemd() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) errChan := make(chan error, 1) diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index e820e1c8b..ef9644de8 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -180,8 +180,9 @@ func pingNewConnection(ctx context.Context) error { } func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) (Connection, error) { + // if you modify the authmethods or their conditionals, you will also need to make similar + // changes in the client (currently cmd/podman/system/connection/add getUDS). authMethods := []ssh.AuthMethod{} - if len(identity) > 0 { auth, err := terminal.PublicKey(identity, []byte(passPhrase)) if err != nil { @@ -205,6 +206,13 @@ func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) ( if pw, found := _url.User.Password(); found { authMethods = append(authMethods, ssh.Password(pw)) } + if len(authMethods) == 0 { + pass, err := terminal.ReadPassword("Login password:") + if err != nil { + return Connection{}, err + } + authMethods = append(authMethods, ssh.Password(string(pass))) + } callback := ssh.InsecureIgnoreHostKey() if secure { diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 9913b773b..c1eb23233 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -24,7 +24,7 @@ var ( // the most recent number of containers. The pod and size booleans indicate that pod information and rootfs // size information should also be included. Finally, the sync bool synchronizes the OCI runtime and // container state. -func List(ctx context.Context, filters map[string][]string, all *bool, last *int, pod, size, sync *bool) ([]entities.ListContainer, error) { // nolint:typecheck +func List(ctx context.Context, filters map[string][]string, all *bool, last *int, size, sync *bool) ([]entities.ListContainer, error) { // nolint:typecheck conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -37,9 +37,6 @@ func List(ctx context.Context, filters map[string][]string, all *bool, last *int if last != nil { params.Set("limit", strconv.Itoa(*last)) } - if pod != nil { - params.Set("pod", strconv.FormatBool(*pod)) - } if size != nil { params.Set("size", strconv.FormatBool(*size)) } diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 9a188e5da..db5be4909 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -510,7 +510,7 @@ var _ = Describe("Podman containers ", func() { Expect(err).To(BeNil()) _, err = bt.RunTopContainer(&name2, bindings.PFalse, nil) Expect(err).To(BeNil()) - containerLatestList, err := containers.List(bt.conn, nil, nil, &latestContainers, nil, nil, nil) + containerLatestList, err := containers.List(bt.conn, nil, nil, &latestContainers, nil, nil) Expect(err).To(BeNil()) err = containers.Kill(bt.conn, containerLatestList[0].Names[0], "SIGTERM") Expect(err).To(BeNil()) @@ -755,8 +755,23 @@ var _ = Describe("Podman containers ", func() { // Validate list container with id filter filters := make(map[string][]string) filters["id"] = []string{cid} - c, err := containers.List(bt.conn, filters, bindings.PTrue, nil, nil, nil, nil) + c, err := containers.List(bt.conn, filters, bindings.PTrue, nil, nil, nil) Expect(err).To(BeNil()) Expect(len(c)).To(Equal(1)) }) + + It("List containers always includes pod information", func() { + podName := "testpod" + ctrName := "testctr" + bt.Podcreate(&podName) + _, err := bt.RunTopContainer(&ctrName, bindings.PTrue, &podName) + Expect(err).To(BeNil()) + + lastNum := 1 + + c, err := containers.List(bt.conn, nil, bindings.PTrue, &lastNum, nil, nil) + Expect(err).To(BeNil()) + Expect(len(c)).To(Equal(1)) + Expect(c[0].PodName).To(Equal(podName)) + }) }) diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go index 50dd4933b..ed40a37ab 100644 --- a/pkg/domain/entities/container_ps.go +++ b/pkg/domain/entities/container_ps.go @@ -56,6 +56,8 @@ type ListContainer struct { StartedAt int64 // State of container State string + // Status is a human-readable approximation of a duration for json output + Status string } // ListContainer Namespaces contains the identifiers of the container's Linux namespaces diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go index 93c4ede49..0b73ddd7e 100644 --- a/pkg/domain/infra/abi/generate.go +++ b/pkg/domain/infra/abi/generate.go @@ -20,9 +20,10 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, if ctrErr == nil { // Generate the unit for the container. s, err := generate.ContainerUnit(ctr, options) - if err == nil { - return &entities.GenerateSystemdReport{Output: s}, nil + if err != nil { + return nil, err } + return &entities.GenerateSystemdReport{Output: s}, nil } // If it's not a container, we either have a pod or garbage. diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 35675e1f3..70d740bb5 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -226,7 +226,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti if err != nil { imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, rawImage)) if err != nil { - return nil, errors.Errorf("invalid image reference %q", rawImage) + return nil, errors.Wrapf(err, "invalid image reference %q", rawImage) } } diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index 11e2ddb39..7ec84246d 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -13,6 +13,14 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) return nil, err } + if !opts.All { + filter, err := ir.Libpod.ImageRuntime().IntermediateFilter(ctx, images) + if err != nil { + return nil, err + } + images = libpodImage.FilterImages(images, []libpodImage.ResultFilter{filter}) + } + summaries := []*entities.ImageSummary{} for _, img := range images { var repoTags []string @@ -32,15 +40,6 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) if err != nil { return nil, err } - if len(img.Names()) == 0 { - parent, err := img.IsParent(ctx) - if err != nil { - return nil, err - } - if parent { - continue - } - } } digests := make([]string, len(img.Digests())) diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index d2221ab7b..cc919561f 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -496,7 +496,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.ContainerListOptions) ([]entities.ListContainer, error) { - return containers.List(ic.ClientCxt, options.Filters, &options.All, &options.Last, &options.Pod, &options.Size, &options.Sync) + return containers.List(ic.ClientCxt, options.Filters, &options.All, &options.Last, &options.Size, &options.Sync) } func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) { diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index 91a49cd02..0c38a3326 100644 --- a/pkg/domain/infra/tunnel/helpers.go +++ b/pkg/domain/infra/tunnel/helpers.go @@ -20,7 +20,7 @@ func getContainersByContext(contextWithConnection context.Context, all bool, nam if all && len(namesOrIDs) > 0 { return nil, errors.New("cannot lookup containers and all") } - c, err := containers.List(contextWithConnection, nil, bindings.PTrue, nil, nil, nil, bindings.PTrue) + c, err := containers.List(contextWithConnection, nil, bindings.PTrue, nil, nil, bindings.PTrue) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 6845d01c0..b255c5da4 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" @@ -73,8 +74,16 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entiti } for i, layer := range results { - hold := entities.ImageHistoryLayer{} - _ = utils.DeepCopy(&hold, layer) + // Created time comes over as an int64 so needs conversion to time.time + t := time.Unix(layer.Created, 0) + hold := entities.ImageHistoryLayer{ + ID: layer.ID, + Created: t.UTC(), + CreatedBy: layer.CreatedBy, + Tags: layer.Tags, + Size: layer.Size, + Comment: layer.Comment, + } history.Layers[i] = hold } return &history, nil @@ -196,7 +205,11 @@ func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) return nil, err } defer f.Close() - return images.Load(ir.ClientCxt, f, &opts.Name) + ref := opts.Name + if len(opts.Tag) > 0 { + ref += ":" + opts.Tag + } + return images.Load(ir.ClientCxt, f, &ref) } func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) { diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index 7831af8f9..c35f68e02 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -91,7 +91,7 @@ func (n UsernsMode) IsHost() bool { return n == hostType } -// IsKeepID indicates whether container uses a mapping where the (uid, gid) on the host is lept inside of the namespace. +// IsKeepID indicates whether container uses a mapping where the (uid, gid) on the host is kept inside of the namespace. func (n UsernsMode) IsKeepID() bool { return n == "keep-id" } diff --git a/pkg/network/network.go b/pkg/network/network.go index b24c72f5f..db625da56 100644 --- a/pkg/network/network.go +++ b/pkg/network/network.go @@ -137,6 +137,15 @@ func networkIntersect(n1, n2 *net.IPNet) bool { // ValidateUserNetworkIsAvailable returns via an error if a network is available // to be used func ValidateUserNetworkIsAvailable(config *config.Config, userNet *net.IPNet) error { + if len(userNet.IP) == 0 || len(userNet.Mask) == 0 { + return errors.Errorf("network %s's ip or mask cannot be empty", userNet.String()) + } + + ones, bit := userNet.Mask.Size() + if ones == 0 || bit == 0 { + return errors.Errorf("network %s's mask is invalid", userNet.String()) + } + networks, err := GetNetworksFromFilesystem(config) if err != nil { return err diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index 8163d7247..4c5f60844 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -175,11 +175,14 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities State: conState.String(), } if opts.Pod && len(conConfig.Pod) > 0 { - pod, err := rt.GetPod(conConfig.Pod) + podName, err := rt.GetName(conConfig.Pod) if err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + return entities.ListContainer{}, errors.Wrapf(define.ErrNoSuchPod, "could not find container %s pod (id %s) in state", conConfig.ID, conConfig.Pod) + } return entities.ListContainer{}, err } - ps.PodName = pod.Name() + ps.PodName = podName } if opts.Namespace { diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index c3f1fc7fa..ecd309d36 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -389,14 +389,12 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st lastErr = nil break } else { - fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) + r, w, err := os.Pipe() if err != nil { lastErr = err continue } - r, w := os.NewFile(uintptr(fds[0]), "read file"), os.NewFile(uintptr(fds[1]), "write file") - defer errorhandling.CloseQuiet(r) if _, _, err := becomeRootInUserNS("", path, w); err != nil { diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 40f9bc029..c49d51fc5 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -125,6 +125,7 @@ type SecurityConfig struct { ReadOnlyRootfs bool //read-only ReadOnlyTmpfs bool //read-only-tmpfs Sysctl map[string]string //sysctl + ProcOpts []string } // CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI diff --git a/pkg/spec/security.go b/pkg/spec/security.go index fc908b49d..e152e3495 100644 --- a/pkg/spec/security.go +++ b/pkg/spec/security.go @@ -76,6 +76,8 @@ func (c *SecurityConfig) SetSecurityOpts(runtime *libpod.Runtime, securityOpts [ } switch con[0] { + case "proc-opts": + c.ProcOpts = strings.Split(con[1], ",") case "label": c.LabelOpts = append(c.LabelOpts, con[1]) case "apparmor": diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 1a1bb4526..8289e2089 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -142,11 +142,6 @@ func (s *SpecGenerator) Validate() error { return err } - // The following are defaults as needed by container creation - if len(s.WorkDir) < 1 { - s.WorkDir = "/" - } - // Set defaults if network info is not provided if s.NetNS.NSMode == "" { s.NetNS.NSMode = Bridge diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 65f8197bc..53d160442 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -135,15 +135,18 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat s.Annotations = annotations // workdir - if newImage != nil { - workingDir, err := newImage.WorkingDir(ctx) - if err != nil { - return nil, err - } - if len(s.WorkDir) < 1 && len(workingDir) > 1 { + if s.WorkDir == "" { + if newImage != nil { + workingDir, err := newImage.WorkingDir(ctx) + if err != nil { + return nil, err + } s.WorkDir = workingDir } } + if s.WorkDir == "" { + s.WorkDir = "/" + } if len(s.SeccompProfilePath) < 1 { p, err := libpod.DefaultSeccompPath() diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index b61ac2c30..fda4c098c 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -164,13 +164,19 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. } if len(command) > 0 { - if command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd") { + useSystemdCommands := map[string]bool{ + "/sbin/init": true, + "/usr/sbin/init": true, + "/usr/local/sbin/init": true, + } + if useSystemdCommands[command[0]] || (filepath.Base(command[0]) == "systemd") { useSystemd = true } } default: return nil, errors.Wrapf(err, "invalid value %q systemd option requires 'true, false, always'", s.Systemd) } + logrus.Debugf("using systemd mode: %t", useSystemd) if useSystemd { // is StopSignal was not set by the user then set it to systemd // expected StopSigal @@ -241,13 +247,7 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. // If the user did not set an workdir but the image did, ensure it is // created. if s.WorkDir == "" && img != nil { - newWD, err := img.WorkingDir(ctx) - if err != nil { - return nil, err - } - if newWD != "" { - options = append(options, libpod.WithCreateWorkingDir()) - } + options = append(options, libpod.WithCreateWorkingDir()) } if s.StopSignal != nil { options = append(options, libpod.WithStopSignal(*s.StopSignal)) diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 78cd32253..fd324c6e1 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -18,6 +18,18 @@ import ( "golang.org/x/sys/unix" ) +func setProcOpts(s *specgen.SpecGenerator, g *generate.Generator) { + if s.ProcOpts == nil { + return + } + for i := range g.Config.Mounts { + if g.Config.Mounts[i].Destination == "/proc" { + g.Config.Mounts[i].Options = s.ProcOpts + return + } + } +} + func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error { var ( isRootless = rootless.IsRootless() @@ -96,8 +108,10 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image finalCommand = append(finalCommand, entrypoint...) + // Only use image command if the user did not manually set an + // entrypoint. command := s.Command - if command == nil && img != nil { + if command == nil && img != nil && s.Entrypoint == nil { newCmd, err := img.Cmd(ctx) if err != nil { return nil, err @@ -339,6 +353,8 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseFalse } + setProcOpts(s, &g) + return configSpec, nil } diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index 4352ef718..5e4cc3399 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -158,8 +158,9 @@ func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, configSpec.Linux.Seccomp = seccompConfig } - // Clear default Seccomp profile from Generator for privileged containers - if s.SeccompProfilePath == "unconfined" || s.Privileged { + // Clear default Seccomp profile from Generator for unconfined containers + // and privileged containers which do not specify a seccomp profile. + if s.SeccompProfilePath == "unconfined" || (s.Privileged && (s.SeccompProfilePath == config.SeccompOverridePath || s.SeccompProfilePath == config.SeccompDefaultPath)) { configSpec.Linux.Seccomp = nil } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 84a6c36a0..a9161071b 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -289,6 +289,8 @@ type ContainerSecurityConfig struct { ReadOnlyFilesystem bool `json:"read_only_filesystem,omittempty"` // Umask is the umask the init process of the container will be run with. Umask string `json:"umask,omitempty"` + // ProcOpts are the options used for the proc mount. + ProcOpts []string `json:"procfs_opts,omitempty"` } // ContainerCgroupConfig contains configuration information about a container's diff --git a/pkg/varlinkapi/create.go b/pkg/varlinkapi/create.go index 2d3e20f67..e9309a2d4 100644 --- a/pkg/varlinkapi/create.go +++ b/pkg/varlinkapi/create.go @@ -704,7 +704,12 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod. if err != nil { return nil, errors.Wrapf(err, "cannot parse bool %s", c.String("systemd")) } - if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) { + useSystemdCommands := map[string]bool{ + "/sbin/init": true, + "/usr/sbin/init": true, + "/usr/local/sbin/init": true, + } + if x && (useSystemdCommands[command[0]] || (filepath.Base(command[0]) == "systemd")) { systemd = true } } diff --git a/test/apiv2/35-networks.at b/test/apiv2/35-networks.at index fff3f3b1f..4c032c072 100644 --- a/test/apiv2/35-networks.at +++ b/test/apiv2/35-networks.at @@ -3,6 +3,32 @@ # network-related tests # -t GET /networks/non-existing-network 404 +t GET networks/non-existing-network 404 \ + .cause='network not found' + +if root; then + t POST libpod/networks/create?name=network1 '' 200 \ + .Filename~.*/network1\\.conflist + + # --data '{"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}}' + t POST libpod/networks/create?name=network2 '"Subnet":{"IP":"10.10.254.0","Mask":[255,255,255,0]}' 200 \ + .Filename~.*/network2\\.conflist + + # test for empty mask + t POST libpod/networks/create '"Subnet":{"IP":"10.10.1.0","Mask":[]}' 500 \ + .cause~'.*cannot be empty' + # test for invalid mask + t POST libpod/networks/create '"Subnet":{"IP":"10.10.1.0","Mask":[0,255,255,0]}' 500 \ + .cause~'.*mask is invalid' + + # clean the network + t DELETE libpod/networks/network1 200 \ + .[0].Name~network1 \ + .[0].Err=null + t DELETE libpod/networks/network2 200 \ + .[0].Name~network2 \ + .[0].Err=null + +fi # vim: filetype=sh diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go index d114744c4..60d9162d1 100644 --- a/test/e2e/generate_systemd_test.go +++ b/test/e2e/generate_systemd_test.go @@ -53,6 +53,18 @@ var _ = Describe("Podman generate systemd", func() { Expect(session).To(ExitWithError()) }) + It("podman generate systemd bad restart-policy value", func() { + session := podmanTest.Podman([]string{"create", "--name", "foobar", "alpine", "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "bogus", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError()) + found, _ := session.ErrorGrepString("Error: bogus is not a valid restart policy") + Expect(found).Should(BeTrue()) + }) + It("podman generate systemd good timeout value", func() { session := podmanTest.Podman([]string{"create", "--name", "foobar", "alpine", "top"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index 381336b8b..e63bce3fe 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -72,16 +72,16 @@ var _ = Describe("Podman logs", func() { Expect(len(results.OutputToStringArray())).To(Equal(0)) }) - It("tail 99 lines", func() { - logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + It("tail 800 lines", func() { + logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "i=1; while [ \"$i\" -ne 1000 ]; do echo \"line $i\"; i=$((i + 1)); done"}) logc.WaitWithDefaultTimeout() Expect(logc).To(Exit(0)) cid := logc.OutputToString() - results := podmanTest.Podman([]string{"logs", "--tail", "99", cid}) + results := podmanTest.Podman([]string{"logs", "--tail", "800", cid}) results.WaitWithDefaultTimeout() Expect(results).To(Exit(0)) - Expect(len(results.OutputToStringArray())).To(Equal(3)) + Expect(len(results.OutputToStringArray())).To(Equal(800)) }) It("tail 2 lines with timestamps", func() { diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index f10ef5c99..a734d399d 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -188,6 +188,21 @@ var _ = Describe("Podman ps", func() { Expect(result.IsJSONOutputValid()).To(BeTrue()) }) + It("podman ps print a human-readable `Status` with json format", func() { + _, ec, _ := podmanTest.RunLsContainer("test1") + Expect(ec).To(Equal(0)) + + result := podmanTest.Podman([]string{"ps", "-a", "--format", "json"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(result.IsJSONOutputValid()).To(BeTrue()) + // must contain "Status" + match, StatusLine := result.GrepString(`Status`) + Expect(match).To(BeTrue()) + // container is running or exit, so it must contain `ago` + Expect(StatusLine[0]).To(ContainSubstring("ago")) + }) + It("podman ps namespace flag with go template format", func() { Skip(v2fail) _, ec, _ := podmanTest.RunLsContainer("test1") diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 0353db9a6..83befe730 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -1,11 +1,14 @@ package integration import ( + "fmt" "os" + "strings" . "github.com/containers/podman/v2/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/uber/jaeger-client-go/utils" ) var _ = Describe("Podman run networking", func() { @@ -290,6 +293,69 @@ var _ = Describe("Podman run networking", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman run slirp4netns network with different cidr", func() { + slirp4netnsHelp := SystemExec("slirp4netns", []string{"--help"}) + Expect(slirp4netnsHelp.ExitCode()).To(Equal(0)) + + networkConfiguration := "slirp4netns:cidr=192.168.0.0/24,allow_host_loopback=true" + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, ALPINE, "ping", "-c1", "192.168.0.2"}) + session.Wait(30) + + if strings.Contains(slirp4netnsHelp.OutputToString(), "cidr") { + Expect(session.ExitCode()).To(Equal(0)) + } else { + Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session.ErrorToString()).To(ContainSubstring("cidr not supported")) + } + }) + + It("podman run network bind to 127.0.0.1", func() { + slirp4netnsHelp := SystemExec("slirp4netns", []string{"--help"}) + Expect(slirp4netnsHelp.ExitCode()).To(Equal(0)) + networkConfiguration := "slirp4netns:outbound_addr=127.0.0.1,allow_host_loopback=true" + + if strings.Contains(slirp4netnsHelp.OutputToString(), "outbound-addr") { + ncListener := StartSystemExec("nc", []string{"-v", "-n", "-l", "-p", "8083"}) + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, "-dt", ALPINE, "nc", "-w", "2", "10.0.2.2", "8083"}) + session.Wait(30) + ncListener.Wait(30) + + Expect(session.ExitCode()).To(Equal(0)) + Expect(ncListener.ExitCode()).To(Equal(0)) + Expect(ncListener.ErrorToString()).To(ContainSubstring("127.0.0.1")) + } else { + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, "-dt", ALPINE, "nc", "-w", "2", "10.0.2.2", "8083"}) + session.Wait(30) + Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session.ErrorToString()).To(ContainSubstring("outbound_addr not supported")) + } + }) + + It("podman run network bind to HostIP", func() { + ip, err := utils.HostIP() + Expect(err).To(BeNil()) + + slirp4netnsHelp := SystemExec("slirp4netns", []string{"--help"}) + Expect(slirp4netnsHelp.ExitCode()).To(Equal(0)) + networkConfiguration := fmt.Sprintf("slirp4netns:outbound_addr=%s,allow_host_loopback=true", ip.String()) + + if strings.Contains(slirp4netnsHelp.OutputToString(), "outbound-addr") { + ncListener := StartSystemExec("nc", []string{"-v", "-n", "-l", "-p", "8084"}) + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, "-dt", ALPINE, "nc", "-w", "2", "10.0.2.2", "8084"}) + session.Wait(30) + ncListener.Wait(30) + + Expect(session.ExitCode()).To(Equal(0)) + Expect(ncListener.ExitCode()).To(Equal(0)) + Expect(ncListener.ErrorToString()).To(ContainSubstring(ip.String())) + } else { + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, "-dt", ALPINE, "nc", "-w", "2", "10.0.2.2", "8084"}) + session.Wait(30) + Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session.ErrorToString()).To(ContainSubstring("outbound_addr not supported")) + } + }) + It("podman run network expose ports in image metadata", func() { session := podmanTest.Podman([]string{"create", "--name", "test", "-dt", "-P", nginx}) session.Wait(90) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 9cb76d1f6..6c65a23e8 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -193,22 +193,46 @@ var _ = Describe("Podman run", func() { Expect(conData[0].Config.Annotations["io.podman.annotations.init"]).To(Equal("FALSE")) }) - It("podman run seccomp test", func() { - + forbidGetCWDSeccompProfile := func() string { in := []byte(`{"defaultAction":"SCMP_ACT_ALLOW","syscalls":[{"name":"getcwd","action":"SCMP_ACT_ERRNO"}]}`) jsonFile, err := podmanTest.CreateSeccompJson(in) if err != nil { fmt.Println(err) Skip("Failed to prepare seccomp.json for test.") } + return jsonFile + } - session := podmanTest.Podman([]string{"run", "-it", "--security-opt", strings.Join([]string{"seccomp=", jsonFile}, ""), ALPINE, "pwd"}) + It("podman run seccomp test", func() { + session := podmanTest.Podman([]string{"run", "-it", "--security-opt", strings.Join([]string{"seccomp=", forbidGetCWDSeccompProfile()}, ""), ALPINE, "pwd"}) session.WaitWithDefaultTimeout() Expect(session).To(ExitWithError()) match, _ := session.GrepString("Operation not permitted") Expect(match).Should(BeTrue()) }) + It("podman run seccomp test --privileged", func() { + session := podmanTest.Podman([]string{"run", "-it", "--privileged", "--security-opt", strings.Join([]string{"seccomp=", forbidGetCWDSeccompProfile()}, ""), ALPINE, "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError()) + match, _ := session.GrepString("Operation not permitted") + Expect(match).Should(BeTrue()) + }) + + It("podman run seccomp test --privileged no profile should be unconfined", func() { + session := podmanTest.Podman([]string{"run", "-it", "--privileged", ALPINE, "grep", "Seccomp", "/proc/self/status"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(ContainSubstring("0")) + Expect(session.ExitCode()).To(Equal(0)) + }) + + It("podman run seccomp test no profile should be default", func() { + session := podmanTest.Podman([]string{"run", "-it", ALPINE, "grep", "Seccomp", "/proc/self/status"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(ContainSubstring("2")) + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman run capabilities test", func() { session := podmanTest.Podman([]string{"run", "--rm", "--cap-add", "all", ALPINE, "cat", "/proc/self/status"}) session.WaitWithDefaultTimeout() @@ -803,6 +827,15 @@ USER mail` Expect(isSharedOnly).Should(BeTrue()) }) + It("podman run --security-opts proc-opts=", func() { + session := podmanTest.Podman([]string{"run", "--security-opt", "proc-opts=nosuid,exec", fedoraMinimal, "findmnt", "-noOPTIONS", "/proc"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + output := session.OutputToString() + Expect(output).To(ContainSubstring("nosuid")) + Expect(output).To(Not(ContainSubstring("exec"))) + }) + It("podman run --mount type=bind,bind-nonrecursive", func() { SkipIfRootless() session := podmanTest.Podman([]string{"run", "--mount", "type=bind,bind-nonrecursive,slave,src=/,target=/host", fedoraMinimal, "findmnt", "-nR", "/host"}) @@ -1143,7 +1176,7 @@ USER mail` Expect(session.ErrorToString()).To(ContainSubstring("Invalid umask")) }) - It("podman run makes entrypoint from image", func() { + It("podman run makes workdir from image", func() { // BuildImage does not seem to work remote SkipIfRemote() dockerfile := `FROM busybox @@ -1154,4 +1187,13 @@ WORKDIR /madethis` Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("/madethis")) }) + + It("podman run --entrypoint does not use image command", func() { + session := podmanTest.Podman([]string{"run", "--entrypoint", "/bin/echo", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + // We can't guarantee the output is completely empty, some + // nonprintables seem to work their way in. + Expect(session.OutputToString()).To(Not(ContainSubstring("/bin/sh"))) + }) }) diff --git a/test/e2e/run_working_dir.go b/test/e2e/run_working_dir.go new file mode 100644 index 000000000..93330deba --- /dev/null +++ b/test/e2e/run_working_dir.go @@ -0,0 +1,69 @@ +package integration + +import ( + "os" + "strings" + + . "github.com/containers/podman/v2/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman run", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.SeedImages() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + + }) + + It("podman run a container without workdir", func() { + session := podmanTest.Podman([]string{"run", ALPINE, "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("/")) + }) + + It("podman run a container using non existing --workdir", func() { + if !strings.Contains(podmanTest.OCIRuntime, "crun") { + Skip("Test only works on crun") + } + session := podmanTest.Podman([]string{"run", "--workdir", "/home/foobar", ALPINE, "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(127)) + }) + + It("podman run a container on an image with a workdir", func() { + SkipIfRemote() + dockerfile := `FROM alpine +RUN mkdir -p /home/foobar +WORKDIR /etc/foobar` + podmanTest.BuildImage(dockerfile, "test", "false") + + session := podmanTest.Podman([]string{"run", "test", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("/etc/foobar")) + + session = podmanTest.Podman([]string{"run", "--workdir", "/home/foobar", "test", "pwd"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal("/home/foobar")) + }) +}) diff --git a/test/system/001-basic.bats b/test/system/001-basic.bats index 71595f419..b23107e79 100644 --- a/test/system/001-basic.bats +++ b/test/system/001-basic.bats @@ -31,6 +31,37 @@ function setup() { run_podman pull $IMAGE } +# PR #7212: allow --remote anywhere before subcommand, not just as 1st flag +@test "podman-remote : really is remote, works as --remote option" { + if ! is_remote; then + skip "only applicable on podman-remote" + fi + + # First things first: make sure our podman-remote actually is remote! + run_podman version + is "$output" ".*Server:" "the given podman path really contacts a server" + + # $PODMAN may be a space-separated string, e.g. if we include a --url. + # Split it into its components; remove "-remote" from the command path; + # and preserve any other args if present. + local -a podman_as_array=($PODMAN) + local podman_path=${podman_as_array[0]} + local podman_non_remote=${podman_path%%-remote} + local -a podman_args=("${podman_as_array[@]:1}") + + # This always worked: running "podman --remote ..." + PODMAN="${podman_non_remote} --remote ${podman_args[@]}" run_podman version + is "$output" ".*Server:" "podman --remote: contacts server" + + # This was failing: "podman --foo --bar --remote". + PODMAN="${podman_non_remote} --tmpdir /var/tmp --log-level=error ${podman_args[@]} --remote" run_podman version + is "$output" ".*Server:" "podman [flags] --remote: contacts server" + + # ...but no matter what, --remote is never allowed after subcommand + PODMAN="${podman_non_remote} ${podman_args[@]}" run_podman 125 version --remote + is "$output" "Error: unknown flag: --remote" "podman version --remote" +} + # This is for development only; it's intended to make sure our timeout # in run_podman continues to work. This test should never run in production # because it will, by definition, fail. diff --git a/test/system/070-build.bats b/test/system/070-build.bats index d2ef9f0f9..0e6e97d40 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -165,6 +165,7 @@ EOF # cd to the dir, so we test relative paths (important for podman-remote) cd $PODMAN_TMPDIR run_podman build -t build_test -f build-test/Containerfile build-test + local iid="${lines[-1]}" # Run without args - should run the above script. Verify its output. export MYENV2="$s_env2" @@ -232,6 +233,22 @@ Labels.$label_name | $label_value run_podman run --rm build_test stat -c'%u:%g:%N' /a/b/c/myfile is "$output" "4:5:/a/b/c/myfile" "file in volume is chowned" + # Hey, as long as we have an image with lots of layers, let's + # confirm that 'image tree' works as expected + run_podman image tree build_test + is "${lines[0]}" "Image ID: ${iid:0:12}" \ + "image tree: first line" + is "${lines[1]}" "Tags: \[localhost/build_test:latest]" \ + "image tree: second line" + is "${lines[2]}" "Size: [0-9.]\+[kM]B" \ + "image tree: third line" + is "${lines[3]}" "Image Layers" \ + "image tree: fourth line" + is "${lines[4]}" "... ID: [0-9a-f]\{12\} Size: .* Top Layer of: \[$IMAGE]" \ + "image tree: first layer line" + is "${lines[-1]}" "... ID: [0-9a-f]\{12\} Size: .* Top Layer of: \[localhost/build_test:latest]" \ + "image tree: last layer line" + # Clean up run_podman rmi -f build_test } diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats index aa6e2bd28..38c6c2312 100644 --- a/test/system/075-exec.bats +++ b/test/system/075-exec.bats @@ -6,6 +6,8 @@ load helpers @test "podman exec - basic test" { + skip_if_remote "FIXME: pending #7241" + rand_filename=$(random_string 20) rand_content=$(random_string 50) diff --git a/test/system/110-history.bats b/test/system/110-history.bats index b83e90fe4..5dc221d61 100644 --- a/test/system/110-history.bats +++ b/test/system/110-history.bats @@ -3,8 +3,6 @@ load helpers @test "podman history - basic tests" { - skip_if_remote "FIXME: pending #7122" - tests=" | .*[0-9a-f]\\\{12\\\} .* CMD .* LABEL --format '{{.ID}} {{.Created}}' | .*[0-9a-f]\\\{12\\\} .* ago diff --git a/test/system/120-load.bats b/test/system/120-load.bats index ccfbc51ca..14dae4c8a 100644 --- a/test/system/120-load.bats +++ b/test/system/120-load.bats @@ -26,10 +26,18 @@ verify_iid_and_name() { is "$new_img_name" "$1" "Name & tag of restored image" } +@test "podman save to pipe and load" { + # We can't use run_podman because that uses the BATS 'run' function + # which redirects stdout and stderr. Here we need to guarantee + # that podman's stdout is a pipe, not any other form of redirection + $PODMAN save --format oci-archive $IMAGE | cat >$PODMAN_TMPDIR/test.tar + [ $status -eq 0 ] + + run_podman load -i $PODMAN_TMPDIR/test.tar +} -@test "podman load - by image ID" { - skip_if_remote "FIXME: pending #7123" +@test "podman load - by image ID" { # FIXME: how to build a simple archive instead? get_iid_and_name @@ -77,8 +85,6 @@ verify_iid_and_name() { } @test "podman load - NAME and NAME:TAG arguments work" { - skip_if_remote "FIXME: pending #7124" - get_iid_and_name run_podman save $iid -o $archive run_podman rmi $iid diff --git a/test/utils/utils.go b/test/utils/utils.go index 2c454f532..a45ce7b36 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -207,6 +207,10 @@ func WaitContainerReady(p PodmanTestCommon, id string, expStr string, timeout in // OutputToString formats session output to string func (s *PodmanSession) OutputToString() string { + if s == nil || s.Out == nil || s.Out.Contents() == nil { + return "" + } + fields := strings.Fields(string(s.Out.Contents())) return strings.Join(fields, " ") } @@ -341,6 +345,16 @@ func SystemExec(command string, args []string) *PodmanSession { return &PodmanSession{session} } +// StartSystemExec is used to start exec a system command +func StartSystemExec(command string, args []string) *PodmanSession { + c := exec.Command(command, args...) + session, err := gexec.Start(c, GinkgoWriter, GinkgoWriter) + if err != nil { + Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " "))) + } + return &PodmanSession{session} +} + // StringInSlice determines if a string is in a string slice, returns bool func StringInSlice(s string, sl []string) bool { for _, i := range sl { diff --git a/transfer.md b/transfer.md index 9aa271c37..49ff687e6 100644 --- a/transfer.md +++ b/transfer.md @@ -38,10 +38,10 @@ There are other equivalents for these tools | Existing Step | `Podman` (and friends) | | :--- | :--- | | `docker attach` | [`podman attach`](./docs/source/markdown/podman-attach.1.md) | -| `docker cp` | [`podman cp`](./docs/source/markdown/podman-cp.1.md) | | `docker build` | [`podman build`](./docs/source/markdown/podman-build.1.md) | | `docker commit` | [`podman commit`](./docs/source/markdown/podman-commit.1.md) | | `docker container`|[`podman container`](./docs/source/markdown/podman-container.1.md) | +| `docker cp` | [`podman cp`](./docs/source/markdown/podman-cp.1.md) | | `docker create` | [`podman create`](./docs/source/markdown/podman-create.1.md) | | `docker diff` | [`podman diff`](./docs/source/markdown/podman-diff.1.md) | | `docker events` | [`podman events`](./docs/source/markdown/podman-events.1.md) | @@ -59,10 +59,10 @@ There are other equivalents for these tools | `docker network ls` | [`podman network ls`](./docs/source/markdown/podman-network-ls.1.md) | | `docker network rm` | [`podman network rm`](./docs.source/markdown/podman-network-rm.1.md) | | `docker pause` | [`podman pause`](./docs/source/markdown/podman-pause.1.md) | +| `docker port` | [`podman port`](./docs/source/markdown/podman-port.1.md) | | `docker ps` | [`podman ps`](./docs/source/markdown/podman-ps.1.md) | | `docker pull` | [`podman pull`](./docs/source/markdown/podman-pull.1.md) | | `docker push` | [`podman push`](./docs/source/markdown/podman-push.1.md) | -| `docker port` | [`podman port`](./docs/source/markdown/podman-port.1.md) | | `docker restart` | [`podman restart`](./docs/source/markdown/podman-restart.1.md) | | `docker rm` | [`podman rm`](./docs/source/markdown/podman-rm.1.md) | | `docker rmi` | [`podman rmi`](./docs/source/markdown/podman-rmi.1.md) | @@ -71,20 +71,20 @@ There are other equivalents for these tools | `docker search` | [`podman search`](./docs/source/markdown/podman-search.1.md) | | `docker start` | [`podman start`](./docs/source/markdown/podman-start.1.md) | | `docker stop` | [`podman stop`](./docs/source/markdown/podman-stop.1.md) | +| `docker system df` | [`podman system df`](./docs/source/markdown/podman-system-df.1.md) | +| `docker system info` | [`podman system info`](./docs/source/markdown/podman-system-info.1.md) | +| `docker system prune` | [`podman system prune`](./docs/source/markdown/podman-system-prune.1.md) | +| `docker system` | [`podman system`](./docs/source/markdown/podman-system.1.md) | | `docker tag` | [`podman tag`](./docs/source/markdown/podman-tag.1.md) | | `docker top` | [`podman top`](./docs/source/markdown/podman-top.1.md) | | `docker unpause` | [`podman unpause`](./docs/source/markdown/podman-unpause.1.md) | | `docker version` | [`podman version`](./docs/source/markdown/podman-version.1.md) | -| `docker volume` | [`podman volume`](./docs/source/markdown/podman-volume.1.md) | | `docker volume create` | [`podman volume create`](./docs/source/markdown/podman-volume-create.1.md) | | `docker volume inspect`| [`podman volume inspect`](./docs/source/markdown/podman-volume-inspect.1.md)| | `docker volume ls` | [`podman volume ls`](./docs/source/markdown/podman-volume-ls.1.md) | | `docker volume prune` | [`podman volume prune`](./docs/source/markdown/podman-volume-prune.1.md) | | `docker volume rm` | [`podman volume rm`](./docs/source/markdown/podman-volume-rm.1.md) | -| `docker system` | [`podman system`](./docs/source/markdown/podman-system.1.md) | -| `docker system df` | [`podman system df`](./docs/source/markdown/podman-system-df.1.md) | -| `docker system prune` | [`podman system prune`](./docs/source/markdown/podman-system-prune.1.md) | -| `docker system info` | [`podman system info`](./docs/source/markdown/podman-system-info.1.md) | +| `docker volume` | [`podman volume`](./docs/source/markdown/podman-volume.1.md) | | `docker wait` | [`podman wait`](./docs/source/markdown/podman-wait.1.md) | **** Use mount to take advantage of the entire linux tool chain rather then just cp. Read [`here`](./docs/podman-cp.1.md) for more information. @@ -95,8 +95,8 @@ Those Docker commands currently do not have equivalents in `podman`: | Missing command | Description| | :--- | :--- | -| `docker container update` | podman does not support altering running containers. We recommend recreating containers with the correct arguments.| | `docker container rename` | podman does not support `container rename` - or the `rename` shorthand. We recommend using `podman rm` and `podman create` to create a container with a specific name.| +| `docker container update` | podman does not support altering running containers. We recommend recreating containers with the correct arguments.| | `docker node` || | `docker plugin` | podman does not support plugins. We recommend you use alternative OCI Runtimes or OCI Runtime Hooks to alter behavior of podman.| | `docker secret` || @@ -108,22 +108,21 @@ Those Docker commands currently do not have equivalents in `podman`: The following podman commands do not have a Docker equivalent: -* [`podman generate`](./docs/source/markdown/podman-generate.1.md) -* [`podman generate kube`](./docs/source/markdown/podman-generate-kube.1.md) * [`podman container checkpoint`](/docs/source/markdown/podman-container-checkpoint.1.md) * [`podman container cleanup`](/docs/source/markdown/podman-container-cleanup.1.md) * [`podman container exists`](/docs/source/markdown/podman-container-exists.1.md) * [`podman container refresh`](/docs/source/markdown/podman-container-refresh.1.md) -* [`podman container runlabel`](/docs/source/markdown/podman-container-runlabel.1.md) * [`podman container restore`](/docs/source/markdown/podman-container-restore.1.md) +* [`podman container runlabel`](/docs/source/markdown/podman-container-runlabel.1.md) +* [`podman generate kube`](./docs/source/markdown/podman-generate-kube.1.md) +* [`podman generate`](./docs/source/markdown/podman-generate.1.md) * [`podman healthcheck run`](/docs/source/markdown/podman-healthcheck-run.1.md) * [`podman image exists`](./docs/source/markdown/podman-image-exists.1.md) * [`podman image sign`](./docs/source/markdown/podman-image-sign.1.md) * [`podman image trust`](./docs/source/markdown/podman-image-trust.1.md) * [`podman mount`](./docs/source/markdown/podman-mount.1.md) -* [`podman play`](./docs/source/markdown/podman-play.1.md) * [`podman play kube`](./docs/source/markdown/podman-play-kube.1.md) -* [`podman pod`](./docs/source/markdown/podman-pod.1.md) +* [`podman play`](./docs/source/markdown/podman-play.1.md) * [`podman pod create`](./docs/source/markdown/podman-pod-create.1.md) * [`podman pod exists`](./docs/source/markdown/podman-pod-exists.1.md) * [`podman pod inspect`](./docs/source/markdown/podman-pod-inspect.1.md) @@ -136,4 +135,5 @@ The following podman commands do not have a Docker equivalent: * [`podman pod stop`](./docs/source/markdown/podman-pod-stop.1.md) * [`podman pod top`](./docs/source/markdown/podman-pod-top.1.md) * [`podman pod unpause`](./docs/source/markdown/podman-pod-unpause.1.md) +* [`podman pod`](./docs/source/markdown/podman-pod.1.md) * [`podman umount`](./docs/source/markdown/podman-umount.1.md) diff --git a/vendor/modules.txt b/vendor/modules.txt index 3f490616a..83ea53197 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -696,7 +696,7 @@ gopkg.in/yaml.v3 # k8s.io/api v0.18.6 k8s.io/api/apps/v1 k8s.io/api/core/v1 -# k8s.io/apimachinery v0.18.6 +# k8s.io/apimachinery v0.18.8 k8s.io/apimachinery/pkg/api/errors k8s.io/apimachinery/pkg/api/resource k8s.io/apimachinery/pkg/apis/meta/v1 |