diff options
108 files changed, 2173 insertions, 1203 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index ebe12ab4a..1eb516d84 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -30,7 +30,7 @@ env: PRIOR_UBUNTU_NAME: "ubuntu-2010" # Google-cloud VM Images - IMAGE_SUFFIX: "c6032583541653504" + IMAGE_SUFFIX: "c5348179051806720" FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${IMAGE_SUFFIX}" UBUNTU_CACHE_IMAGE_NAME: "ubuntu-${IMAGE_SUFFIX}" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 8fd51b5e9..d6f92873e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/stale@v1 + - uses: actions/stale@v3 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'A friendly reminder that this issue had no activity for 30 days.' @@ -573,7 +573,7 @@ remotesystem: .PHONY: localapiv2 localapiv2: env PODMAN=./bin/podman ./test/apiv2/test-apiv2 - env PODMAN=./bin/podman ${PYTHON} -m unittest discover -v ./test/apiv2/rest_api/ + env PODMAN=./bin/podman ${PYTHON} -m unittest discover -v ./test/apiv2/python env PODMAN=./bin/podman ${PYTHON} -m unittest discover -v ./test/python/docker .PHONY: remoteapiv2 diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 4aca79770..de5b2995a 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -11,6 +11,7 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/network" "github.com/containers/podman/v3/pkg/registries" "github.com/containers/podman/v3/pkg/rootless" systemdDefine "github.com/containers/podman/v3/pkg/systemd/define" @@ -243,7 +244,7 @@ func getRegistries() ([]string, cobra.ShellCompDirective) { return regs, cobra.ShellCompDirectiveNoFileComp } -func getNetworks(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCompDirective) { +func getNetworks(cmd *cobra.Command, toComplete string, cType completeType) ([]string, cobra.ShellCompDirective) { suggestions := []string{} networkListOptions := entities.NetworkListOptions{} @@ -259,7 +260,15 @@ func getNetworks(cmd *cobra.Command, toComplete string) ([]string, cobra.ShellCo } for _, n := range networks { - if strings.HasPrefix(n.Name, toComplete) { + id := network.GetNetworkID(n.Name) + // include ids in suggestions if cType == completeIDs or + // more then 2 chars are typed and cType == completeDefault + if ((len(toComplete) > 1 && cType == completeDefault) || + cType == completeIDs) && strings.HasPrefix(id, toComplete) { + suggestions = append(suggestions, id[0:12]) + } + // include name in suggestions + if cType != completeIDs && strings.HasPrefix(n.Name, toComplete) { suggestions = append(suggestions, n.Name) } } @@ -502,7 +511,7 @@ func AutocompleteNetworks(cmd *cobra.Command, args []string, toComplete string) if !validCurrentCmdLine(cmd, args, toComplete) { return nil, cobra.ShellCompDirectiveNoFileComp } - return getNetworks(cmd, toComplete) + return getNetworks(cmd, toComplete, completeDefault) } // AutocompleteDefaultOneArg - Autocomplete path only for the first argument. @@ -588,7 +597,7 @@ func AutocompleteContainerOneArg(cmd *cobra.Command, args []string, toComplete s // AutocompleteNetworkConnectCmd - Autocomplete podman network connect/disconnect command args. func AutocompleteNetworkConnectCmd(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { - return getNetworks(cmd, toComplete) + return getNetworks(cmd, toComplete, completeDefault) } if len(args) == 1 { return getContainers(cmd, toComplete, completeDefault) @@ -624,7 +633,7 @@ func AutocompleteInspect(cmd *cobra.Command, args []string, toComplete string) ( containers, _ := getContainers(cmd, toComplete, completeDefault) images, _ := getImages(cmd, toComplete) pods, _ := getPods(cmd, toComplete, completeDefault) - networks, _ := getNetworks(cmd, toComplete) + networks, _ := getNetworks(cmd, toComplete, completeDefault) volumes, _ := getVolumes(cmd, toComplete) suggestions := append(containers, images...) suggestions = append(suggestions, pods...) @@ -885,7 +894,7 @@ func AutocompleteNetworkFlag(cmd *cobra.Command, args []string, toComplete strin }, } - networks, _ := getNetworks(cmd, toComplete) + networks, _ := getNetworks(cmd, toComplete, completeDefault) suggestions, dir := completeKeyValues(toComplete, kv) // add slirp4netns here it does not work correct if we add it to the kv map suggestions = append(suggestions, "slirp4netns") @@ -1039,7 +1048,10 @@ func AutocompleteNetworkDriver(cmd *cobra.Command, args []string, toComplete str // -> "ipc", "net", "pid", "user", "uts", "cgroup", "none" func AutocompletePodShareNamespace(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { namespaces := []string{"ipc", "net", "pid", "user", "uts", "cgroup", "none"} - return namespaces, cobra.ShellCompDirectiveNoFileComp + split := strings.Split(toComplete, ",") + split[len(split)-1] = "" + toComplete = strings.Join(split, ",") + return prefixSlice(toComplete, namespaces), cobra.ShellCompDirectiveNoFileComp } // AutocompletePodPsSort - Autocomplete images sort options. @@ -1115,7 +1127,7 @@ func AutocompletePsFilters(cmd *cobra.Command, args []string, toComplete string) return []string{define.HealthCheckHealthy, define.HealthCheckUnhealthy}, cobra.ShellCompDirectiveNoFileComp }, - "network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s) }, + "network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeDefault) }, "label=": nil, "exited=": nil, "until=": nil, @@ -1138,7 +1150,7 @@ func AutocompletePodPsFilters(cmd *cobra.Command, args []string, toComplete stri "ctr-status=": func(_ string) ([]string, cobra.ShellCompDirective) { return containerStatuses, cobra.ShellCompDirectiveNoFileComp }, - "network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s) }, + "network=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeDefault) }, "label=": nil, } return completeKeyValues(toComplete, kv) @@ -1158,11 +1170,28 @@ func AutocompleteImageFilters(cmd *cobra.Command, args []string, toComplete stri return completeKeyValues(toComplete, kv) } +// AutocompletePruneFilters - Autocomplete container/image prune --filter options. +func AutocompletePruneFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + kv := keyValueCompletion{ + "until=": nil, + "label=": nil, + } + return completeKeyValues(toComplete, kv) +} + // AutocompleteNetworkFilters - Autocomplete network ls --filter options. func AutocompleteNetworkFilters(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { kv := keyValueCompletion{ - "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s) }, - "plugin=": nil, + "name=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeNames) }, + "id=": func(s string) ([]string, cobra.ShellCompDirective) { return getNetworks(cmd, s, completeIDs) }, + "plugin=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"bridge", "portmap", + "firewall", "tuning", "dnsname", "macvlan"}, cobra.ShellCompDirectiveNoFileComp + }, + "label=": nil, + "driver=": func(_ string) ([]string, cobra.ShellCompDirective) { + return []string{"bridge"}, cobra.ShellCompDirectiveNoFileComp + }, } return completeKeyValues(toComplete, kv) } diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 5dc2ec864..2f45e559d 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -778,20 +778,30 @@ func parseThrottleIOPsDevices(iopsDevices []string) (map[string]specs.LinuxThrot return td, nil } -func parseSecrets(secrets []string) ([]string, map[string]string, error) { +func parseSecrets(secrets []string) ([]specgen.Secret, map[string]string, error) { secretParseError := errors.New("error parsing secret") - var mount []string + var mount []specgen.Secret envs := make(map[string]string) for _, val := range secrets { + // mount only tells if user has set an option that can only be used with mount secret type + mountOnly := false source := "" secretType := "" target := "" + var uid, gid uint32 + // default mode 444 octal = 292 decimal + var mode uint32 = 292 split := strings.Split(val, ",") // --secret mysecret if len(split) == 1 { - source = val - mount = append(mount, source) + mountSecret := specgen.Secret{ + Source: val, + UID: uid, + GID: gid, + Mode: mode, + } + mount = append(mount, mountSecret) continue } // --secret mysecret,opt=opt @@ -799,7 +809,7 @@ func parseSecrets(secrets []string) ([]string, map[string]string, error) { source = split[0] split = split[1:] } - // TODO: implement other secret options + for _, val := range split { kv := strings.SplitN(val, "=", 2) if len(kv) < 2 { @@ -818,6 +828,28 @@ func parseSecrets(secrets []string) ([]string, map[string]string, error) { secretType = kv[1] case "target": target = kv[1] + case "mode": + mountOnly = true + mode64, err := strconv.ParseUint(kv[1], 8, 32) + if err != nil { + return nil, nil, errors.Wrapf(secretParseError, "mode %s invalid", kv[1]) + } + mode = uint32(mode64) + case "uid", "UID": + mountOnly = true + uid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, nil, errors.Wrapf(secretParseError, "UID %s invalid", kv[1]) + } + uid = uint32(uid64) + case "gid", "GID": + mountOnly = true + gid64, err := strconv.ParseUint(kv[1], 10, 32) + if err != nil { + return nil, nil, errors.Wrapf(secretParseError, "GID %s invalid", kv[1]) + } + gid = uint32(gid64) + default: return nil, nil, errors.Wrapf(secretParseError, "option %s invalid", val) } @@ -833,9 +865,18 @@ func parseSecrets(secrets []string) ([]string, map[string]string, error) { if target != "" { return nil, nil, errors.Wrapf(secretParseError, "target option is invalid for mounted secrets") } - mount = append(mount, source) + mountSecret := specgen.Secret{ + Source: source, + UID: uid, + GID: gid, + Mode: mode, + } + mount = append(mount, mountSecret) } if secretType == "env" { + if mountOnly { + return nil, nil, errors.Wrap(secretParseError, "UID, GID, Mode options cannot be set with secret type env") + } if target == "" { target = source } diff --git a/cmd/podman/containers/prune.go b/cmd/podman/containers/prune.go index 837d90f70..94da029b9 100644 --- a/cmd/podman/containers/prune.go +++ b/cmd/podman/containers/prune.go @@ -43,7 +43,7 @@ func init() { flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation. The default is false") filterFlagName := "filter" flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") - _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePruneFilters) } func prune(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index 8231e5c57..db645cc2e 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" "github.com/containers/podman/v3/cmd/podman/validate" @@ -15,10 +16,8 @@ import ( ) var ( - pruneDescription = `Removes all unnamed images from local storage. - - If an image is not being used by a container, it will be removed from the system.` - pruneCmd = &cobra.Command{ + pruneDescription = `Removes dangling or unused images from local storage.` + pruneCmd = &cobra.Command{ Use: "prune [options]", Args: validate.NoArgs, Short: "Remove unused images", @@ -41,13 +40,12 @@ func init() { }) flags := pruneCmd.Flags() - flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all unused images, not just dangling ones") + flags.BoolVarP(&pruneOpts.All, "all", "a", false, "Remove all images not in use by containers, not just dangling ones") flags.BoolVarP(&force, "force", "f", false, "Do not prompt for confirmation") filterFlagName := "filter" flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") - //TODO: add completion for filters - _ = pruneCmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = pruneCmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePruneFilters) } func prune(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/networks/prune.go b/cmd/podman/networks/prune.go index bcc55f0f4..5f1cbda5f 100644 --- a/cmd/podman/networks/prune.go +++ b/cmd/podman/networks/prune.go @@ -6,7 +6,6 @@ import ( "os" "strings" - "github.com/containers/common/pkg/completion" "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" @@ -39,7 +38,7 @@ func networkPruneFlags(cmd *cobra.Command, flags *pflag.FlagSet) { flags.BoolVarP(&force, "force", "f", false, "do not prompt for confirmation") filterFlagName := "filter" flags.StringArrayVar(&filter, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") - _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePruneFilters) } func init() { diff --git a/cmd/podman/networks/reload.go b/cmd/podman/networks/reload.go index 8f2fbf011..035e56a07 100644 --- a/cmd/podman/networks/reload.go +++ b/cmd/podman/networks/reload.go @@ -26,9 +26,6 @@ var ( Example: `podman network reload --latest podman network reload 3c13ef6dd843 podman network reload test1 test2`, - Annotations: map[string]string{ - registry.ParentNSRequired: "", - }, } ) diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index afd5b3a34..44be4ccec 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -78,6 +78,8 @@ func info(cmd *cobra.Command, args []string) error { return err } + info.Host.ServiceIsRemote = registry.IsRemote() + switch { case report.IsJSON(inFormat): b, err := json.MarshalIndent(info, "", " ") diff --git a/cmd/podman/system/prune.go b/cmd/podman/system/prune.go index 3020a541b..0f1285564 100644 --- a/cmd/podman/system/prune.go +++ b/cmd/podman/system/prune.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/parse" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/utils" @@ -50,7 +51,7 @@ func init() { flags.BoolVar(&pruneOptions.Volume, "volumes", false, "Prune volumes") filterFlagName := "filter" flags.StringArrayVar(&filters, filterFlagName, []string{}, "Provide filter values (e.g. 'label=<key>=<value>')") - _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) + _ = pruneCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePruneFilters) } func prune(cmd *cobra.Command, args []string) error { diff --git a/contrib/podmanimage/stable/containers.conf b/contrib/podmanimage/stable/containers.conf index 7f0e36224..220c1f850 100644 --- a/contrib/podmanimage/stable/containers.conf +++ b/contrib/podmanimage/stable/containers.conf @@ -5,7 +5,7 @@ ipcns="host" utsns="host" cgroupns="host" cgroups="disabled" -log_driver = "k8s_file" +log_driver = "k8s-file" [engine] cgroup_manager = "cgroupfs" events_logger="file" diff --git a/contrib/systemd/auto-update/podman-auto-update.service b/contrib/systemd/auto-update/podman-auto-update.service index 068dab95b..9376db225 100644 --- a/contrib/systemd/auto-update/podman-auto-update.service +++ b/contrib/systemd/auto-update/podman-auto-update.service @@ -7,6 +7,7 @@ After=network-online.target [Service] Type=oneshot ExecStart=/usr/bin/podman auto-update +ExecStartPost=/usr/bin/podman image prune -f [Install] WantedBy=multi-user.target default.target diff --git a/docs/source/Introduction.rst b/docs/source/Introduction.rst index 3fa86f868..5c8713d27 100644 --- a/docs/source/Introduction.rst +++ b/docs/source/Introduction.rst @@ -2,7 +2,7 @@ Introduction ================================== -Containers_ simplify the consumption of applications with all of their dependencies and default configuration files. Users test drive or deploy a new application with one or two commands instead of following pages of installation instructions. Here's how to find your first `Container Image`_:: +Containers_ simplify the production, distribution, discoverability, and usage of applications with all of their dependencies and default configuration files. Users test drive or deploy a new application with one or two commands instead of following pages of installation instructions. Here's how to find your first `Container Image`_:: podman search busybox diff --git a/docs/source/conf.py b/docs/source/conf.py index 7a180e5ef..8210022f2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,10 +29,7 @@ author = "team" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ - "sphinx_markdown_tables", - "recommonmark" -] +extensions = ["sphinx_markdown_tables", "recommonmark"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -66,26 +63,27 @@ html_css_files = [ # -- Extension configuration ------------------------------------------------- + def convert_markdown_title(app, docname, source): # Process markdown files only docpath = app.env.doc2path(docname) if docpath.endswith(".md"): # Convert pandoc title line into eval_rst block for recommonmark - source[0] = re.sub( - r"^% (.*)", - r"```eval_rst\n.. title:: \g<1>\n```", - source[0]) + source[0] = re.sub(r"^% (.*)", r"```eval_rst\n.. title:: \g<1>\n```", source[0]) def setup(app): app.connect("source-read", convert_markdown_title) app.add_config_value( - "recommonmark_config", { + "recommonmark_config", + { "enable_eval_rst": True, "enable_auto_doc_ref": False, "enable_auto_toc_tree": False, "enable_math": False, "enable_inline_math": False, - }, True) + }, + True, + ) app.add_transform(AutoStructify) diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index d03d48506..2c51b312d 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -859,6 +859,9 @@ Secret Options - `type=mount|env` : How the secret will be exposed to the container. Default mount. - `target=target` : Target of secret. Defauts to secret name. +- `uid=0` : UID of secret. Defaults to 0. Mount secret type only. +- `gid=0` : GID of secret. Defaults to 0. Mount secret type only. +- `mode=0` : Mode of secret. Defaults to 0444. Mount secret type only. #### **--security-opt**=*option* diff --git a/docs/source/markdown/podman-image-prune.1.md b/docs/source/markdown/podman-image-prune.1.md index 73024ffb8..bd08d18fc 100644 --- a/docs/source/markdown/podman-image-prune.1.md +++ b/docs/source/markdown/podman-image-prune.1.md @@ -8,8 +8,7 @@ podman-image-prune - Remove all unused images from the local store ## DESCRIPTION **podman image prune** removes all dangling images from local storage. With the `all` option, -you can delete all unused images. Unused images are dangling images as well as any image that -does not have any containers based on it. +you can delete all unused images (i.e., images not in use by any container). The image prune command does not prune cache images that only use layers that are necessary for other images. diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index b9cfb01d1..46e15d62f 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -911,6 +911,9 @@ Secret Options - `type=mount|env` : How the secret will be exposed to the container. Default mount. - `target=target` : Target of secret. Defauts to secret name. +- `uid=0` : UID of secret. Defaults to 0. Mount secret type only. +- `gid=0` : GID of secret. Defaults to 0. Mount secret type only. +- `mode=0` : Mode of secret. Defaults to 0444. Mount secret type only. #### **--security-opt**=*option* diff --git a/docs/source/markdown/podman-volume-ls.1.md b/docs/source/markdown/podman-volume-ls.1.md index ab3813cca..489057446 100644 --- a/docs/source/markdown/podman-volume-ls.1.md +++ b/docs/source/markdown/podman-volume-ls.1.md @@ -16,7 +16,14 @@ flag. Use the **--quiet** flag to print only the volume names. #### **--filter**=*filter*, **-f** -Filter volume output. +Volumes can be filtered by the following attributes: + +- dangling +- driver +- label +- name +- opt +- scope #### **--format**=*format* diff --git a/docs/tutorials/basic_networking.md b/docs/tutorials/basic_networking.md index 51dfa7564..850bf6681 100644 --- a/docs/tutorials/basic_networking.md +++ b/docs/tutorials/basic_networking.md @@ -87,12 +87,16 @@ network, and the one will be created as a bridge network. $ podman network create ``` -When rootless containers are run with a CNI networking configuration, a “side-car” -container for running CNI is also run. Do not remove this container while your rootless -containers are running. if you remove this container (e.g by accident) all attached -containers lose network connectivity. In order to restore the network connectivity -all containers with networks must be restarted. This will automatically recreate -the "side-car" container. For rootfull containers, there is no “side-car” container +When rootless containers are run with a CNI networking configuration, CNI operations +will be executed inside an extra network namespace. To join this namespace, use +`podman unshare --rootless-cni`. Podman version 3.1 and earlier use a special “side-car” +container called rootless-cni-infra. Do not remove this container while your rootless +containers are running. If you remove this container (e.g. by accident), all attached +containers lose network connectivity. In order to restore the network connectivity, all +containers with networks must be restarted. This will automatically recreate the "side-car" +container. When you are using version 3.2 or newer the “side-car” container can be +safely removed. Therefore, it is no longer used. +For rootfull containers, there is no extra namespace or “side-car” container as rootfull users have the permissions to create and modify network interfaces on the host. @@ -12,12 +12,12 @@ require ( github.com/containernetworking/cni v0.8.1 github.com/containernetworking/plugins v0.9.1 github.com/containers/buildah v1.20.2-0.20210504130217-903dc56408ac - github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce + github.com/containers/common v0.38.1-0.20210510140555-24645399a050 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.12.0 github.com/containers/ocicrypt v1.1.1 github.com/containers/psgo v1.5.2 - github.com/containers/storage v1.30.2 + github.com/containers/storage v1.30.3 github.com/coreos/go-systemd/v22 v22.3.2 github.com/coreos/stream-metadata-go v0.0.0-20210225230131-70edb9eb47b3 github.com/cri-o/ocicni v0.2.1-0.20210301205850-541cf7c703cf @@ -49,7 +49,7 @@ require ( github.com/opencontainers/runc v1.0.0-rc94 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 github.com/opencontainers/runtime-tools v0.9.0 - github.com/opencontainers/selinux v1.8.0 + github.com/opencontainers/selinux v1.8.1 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.0 github.com/rootless-containers/rootlesskit v0.14.2 @@ -197,8 +197,9 @@ github.com/containernetworking/plugins v0.9.1 h1:FD1tADPls2EEi3flPc2OegIY1M9pUa9 github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= github.com/containers/buildah v1.20.2-0.20210504130217-903dc56408ac h1:rPQTF+1lz+F4uTZgfk2pwqGcEEg9mPSWK58UncsqsrA= github.com/containers/buildah v1.20.2-0.20210504130217-903dc56408ac/go.mod h1:0hqcxPCNk/lit/SwBQoXXymCbp2LUa07U0cwrn/T1c0= -github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce h1:e7VNmGqwfUQkw+D5bms262x1HYqxfN9/+t5SoaFnwTk= github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce/go.mod h1:JjU+yvzIGyx8ZsY8nyf7snzs4VSNh1eIaYsqoSKBoRw= +github.com/containers/common v0.38.1-0.20210510140555-24645399a050 h1:o3UdnXpzCmJwto4y+addBH2NXZObuZ0tXA7COZXNMnQ= +github.com/containers/common v0.38.1-0.20210510140555-24645399a050/go.mod h1:64dWQkAgrd2cxVh9eDOxJ/IgPOulVFg6G7WLRDTrAuA= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/image/v5 v5.11.1/go.mod h1:HC9lhJ/Nz5v3w/5Co7H431kLlgzlVlOC+auD/er3OqE= @@ -215,8 +216,9 @@ github.com/containers/psgo v1.5.2/go.mod h1:2ubh0SsreMZjSXW1Hif58JrEcFudQyIy9EzP github.com/containers/storage v1.23.5/go.mod h1:ha26Q6ngehFNhf3AWoXldvAvwI4jFe3ETQAf/CeZPyM= github.com/containers/storage v1.29.0/go.mod h1:u84RU4CCufGeJBNTRNwMB+FoE+AiFeFw4SsMoqAOeCM= github.com/containers/storage v1.30.1/go.mod h1:NDJkiwxnSHD1Is+4DGcyR3SIEYSDOa0xnAW+uGQFx9E= -github.com/containers/storage v1.30.2 h1:qLYh970iu0x53n7A5nlZHO8XjfROfiyizkFyRy62Lpc= github.com/containers/storage v1.30.2/go.mod h1:hK/eetUA5afAZ+ZFZCXIY4eBAIo7XXmfNW476axZfgQ= +github.com/containers/storage v1.30.3 h1:DwX59C66vXwrwIafiMJbZxGIgrrZtQDAZBT/TsCS6mY= +github.com/containers/storage v1.30.3/go.mod h1:167Jo92jpTH5P+qfGblKFCb9kyYZwP2qxtpGczlFAP8= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= @@ -667,8 +669,9 @@ github.com/opencontainers/runtime-tools v0.9.0 h1:FYgwVsKRI/H9hU32MJ/4MLOzXWodKK github.com/opencontainers/runtime-tools v0.9.0/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.5.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.1 h1:yvEZh7CsfnJNwKzG9ZeXwbvR05RAZsu5RS/3vA6qFTA= +github.com/opencontainers/selinux v1.8.1/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656 h1:WaxyNFpmIDu4i6so9r6LVFIbSaXqsj8oitMitt86ae4= github.com/openshift/imagebuilder v1.2.2-0.20210415181909-87f3e48c2656/go.mod h1:9aJRczxCH0mvT6XQ+5STAQaPWz7OsWcU5/mRkt8IWeo= github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw= diff --git a/hack/bats b/hack/bats new file mode 100755 index 000000000..45b8cf6f2 --- /dev/null +++ b/hack/bats @@ -0,0 +1,109 @@ +#!/bin/bash +# +# bats wrapper - invokes bats, root & rootless, on podman system tests +# + +############################################################################### +# BEGIN usage message + +usage="Usage: $0 [--root] [--rootless] [--filter=filename[:testname]] + +$0 is a wrapper for invoking podman system tests. + + --root Run only as root + --rootless Run only as user (i.e. you) + + --filter=name Run only test files that match 'test/system/*name*', + e.g. '500' or 'net' will match 500-networking.bats. + If ':pattern' is appended, and you have a modern-enough + version of bats installed, runs with '--filter pattern' + which runs only subtests that match 'pattern' + + --help display usage message + +By default, tests ./bin/podman. To test a different podman, do: + + \$ PODMAN=/abs/path/to/podman $0 .... + +To test podman-remote, start your own servers (root and rootless) via: + + /path/to/podman system service --timeout=0 + +...then invoke this script with PODMAN=\$(pwd)/bin/podman-remote + + (You'd think Ed could be bothered to do all that in this script; but then + the flow would be 'sudo start-service; sudo run-bats; sudo stop-service' + and by the time we get to stop-service, the sudo timeout will have lapsed, + and the script will be hanging at the password prompt, and you, who left + your desk for coffee or a walk and expected to come back to completed + root and rootless tests, will be irked because only root tests ran and + now you have to wait for rootless). + +$0 also passes through \$OCI_RUNTIME, should you need to test that. +" + +# END usage message +############################################################################### +# BEGIN initialization and command-line arg checking + +# By default, test the podman in our working directory. +# Some tests cd out of our workdir, so abs path is important +export PODMAN=${PODMAN:-$(pwd)/bin/podman} + +# Because 'make' doesn't do this by default +chcon -t container_runtime_exec_t $PODMAN + +# Directory in which +TESTS=test/system + +REMOTE= +ROOT_ONLY= +ROOTLESS_ONLY= + +declare -a bats_filter=() + +for i;do + value=`expr "$i" : '[^=]*=\(.*\)'` + case "$i" in + -h|--help) echo "$usage"; exit 0;; + --root) ROOT_ONLY=1 ;; + --rootless) ROOTLESS_ONLY=1 ;; + --remote) REMOTE=remote; echo "--remote is TBI"; exit 1;; + */*.bats) TESTS=$i ;; + *) + if [[ $i =~ : ]]; then + tname=${i%:*} # network:localhost -> network + filt=${i#*:} # network:localhost -> localhost + TESTS=$(echo $TESTS/*$tname*.bats) + bats_filter=("--filter" "$filt") + else + TESTS=$(echo $TESTS/*$i*.bats) + fi + ;; + esac +done + +# END initialization and command-line arg checking +############################################################################### + +rc=0 + +# Root +if [ -z "$ROOTLESS_ONLY" ]; then + echo "# bats ${bats_filter[@]} $TESTS" + sudo --preserve-env=PODMAN \ + --preserve-env=PODMAN_TEST_DEBUG \ + --preserve-env=OCI_RUNTIME \ + bats "${bats_filter[@]}" $TESTS + rc=$? +fi + +# Rootless +echo "--------------------------------------------------" +if [ -z "$ROOT_ONLY" ]; then + echo "\$ bats ${bats_filter[@]} $TESTS" + bats "${bats_filter[@]}" $TESTS + rc=$((rc | $?)) +fi + +exit $rc diff --git a/hack/branch_commits.rb b/hack/branch_commits.rb new file mode 100755 index 000000000..f13f8b2d0 --- /dev/null +++ b/hack/branch_commits.rb @@ -0,0 +1,98 @@ +#!/usr/bin/ruby + +require 'set' + +# Get commits in one branch, but not in another, accounting for cherry-picks. +# Accepts two arguments: base branch and old branch. Commits in base branch that +# are not in old branch will be reported. + +# Preface: I know exactly enough ruby to be dangerous with it. +# For anyone reading this who is actually skilled at writing Ruby, I can only +# say I'm very, very sorry. + +# Utility functions: + +# Check if a given Git branch exists +def CheckBranchExists(branch) + return `git branch --list #{branch}`.rstrip.empty? +end + +# Returns author (email) and commit subject for the given hash +def GetCommitInfo(hash) + info = `git log -n 1 --format='%ae%n%s' #{hash}`.split("\n") + if info.length != 2 + puts("Badly-formatted commit with hash #{hash}") + exit(127) + end + return info[0], info[1] +end + +# Actual script begins here + +if ARGV.length != 2 + puts("Must provide exactly 2 arguments, base branch and old branch") + exit(127) +end + +# Both branches must exist +ARGV.each do |branch| + if !CheckBranchExists(branch) + puts("Branch #{branch} does not exist") + exit(127) + end +end + +base = ARGV[0] +old = ARGV[1] + +# Get a base list of commits +commits = `git log --no-merges --format=%H #{base} ^#{old}`.split("\n") + +# Alright, now for the hacky bit. +# We want to remove every commit with a shortlog precisely matching something in +# the old branch. This is an effort to catch cherry-picks, where commit ID has +# almost certainly changed because the committer is different (and possibly +# conflicts needed to be resolved). +# We will match also try and match author, but not committer (which is reset to +# whoever did the cherry-pick). We will *not* match full commit body - I +# routinely edit these when I fix cherry-pick conflicts to indicate that I made +# changes. A more ambitious future committer could attempt to see if the body of +# the commit message in the old branch is a subset of the full commit message +# from the base branch, but there are potential performance implications in that +# due to the size of the string comparison that would be needed. +# This will not catch commits where the shortlog is deliberately altered as part +# of the cherry pick... But we can just ask folks not to do that, I guess? +# (A classic example of something this wouldn't catch: cherry-picking a commit +# to a branch and then prepending the branch name to the commit subject. I see +# this a lot in Github PR subjects, but fortunately not much at all in actual +# commit subjects). + +# Begin by fetching commit author + subject for each commit in old branch. +# Map each author to an array of potential commit subjects. +oldIndex = {} + +# TODO: This could probably be made a whole lot more efficient by unifying the +# GetCommitInfo bits into two big `git log --format` calls. +# But I'm not really ambitious enough to do that... +oldCommits = `git log --no-merges --format=%H #{old}`.split("\n") +oldCommits.each do |hash| + name, subject = GetCommitInfo(hash) + if oldIndex[name] == nil + oldIndex[name] = Set[] + end + oldIndex[name].add(subject) +end + +# Go through our earlier commits list and check for matches. +filtered = commits.reject do |hash| + name, subject = GetCommitInfo(hash) + oldIndex[name] != nil && oldIndex[name].include?(subject) +end + +# We have now filtered out all commits we want to filter. +# Now we just have to print all remaining commits. +# This breaks the default pager, but we can just pipe to less. +filtered.each do |hash| + puts `git log -n 1 #{hash}` + puts "\n" +end diff --git a/libpod/container.go b/libpod/container.go index c49d8feeb..591cf9bc5 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -126,6 +126,8 @@ type Container struct { // This is true if a container is restored from a checkpoint. restoreFromCheckpoint bool + + slirp4netnsSubnet *net.IPNet } // ContainerState contains the current state of the container @@ -235,6 +237,18 @@ type ContainerImageVolume struct { ReadWrite bool `json:"rw"` } +// ContainerSecret is a secret that is mounted in a container +type ContainerSecret struct { + // Secret is the secret + *secrets.Secret + // UID is tbe UID of the secret file + UID uint32 + // GID is the GID of the secret file + GID uint32 + // Mode is the mode of the secret file + Mode uint32 +} + // ContainerNetworkDescriptions describes the relationship between the CNI // network and the ethN where N is an integer type ContainerNetworkDescriptions map[string]int @@ -1124,7 +1138,7 @@ func (c *Container) Umask() string { } //Secrets return the secrets in the container -func (c *Container) Secrets() []*secrets.Secret { +func (c *Container) Secrets() []*ContainerSecret { return c.config.Secrets } diff --git a/libpod/container_config.go b/libpod/container_config.go index 904c03f9b..0de79fde3 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -148,7 +148,7 @@ type ContainerRootFSConfig struct { // default, but others do not. CreateWorkingDir bool `json:"createWorkingDir,omitempty"` // Secrets lists secrets to mount into the container - Secrets []*secrets.Secret `json:"secrets,omitempty"` + Secrets []*ContainerSecret `json:"secrets,omitempty"` // SecretPath is the secrets location in storage SecretsPath string `json:"secretsPath"` // Volatile specifies whether the container storage can be optimized diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 5b2103c92..4210bc581 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -343,11 +343,13 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp ctrConfig.CreateCommand = c.config.CreateCommand ctrConfig.Timezone = c.config.Timezone - for _, secret := range c.config.Secrets { newSec := define.InspectSecret{} newSec.Name = secret.Name newSec.ID = secret.ID + newSec.UID = secret.UID + newSec.GID = secret.GID + newSec.Mode = secret.Mode ctrConfig.Secrets = append(ctrConfig.Secrets, &newSec) } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 53b85a466..f77825efd 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -15,7 +15,7 @@ import ( metadata "github.com/checkpoint-restore/checkpointctl/lib" "github.com/containers/buildah/copier" - "github.com/containers/common/pkg/secrets" + butil "github.com/containers/buildah/util" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" "github.com/containers/podman/v3/pkg/cgroups" @@ -24,6 +24,7 @@ import ( "github.com/containers/podman/v3/pkg/hooks/exec" "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/pkg/selinux" + "github.com/containers/podman/v3/pkg/util" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" @@ -1530,6 +1531,16 @@ func (c *Container) mountStorage() (_ string, deferredErr error) { }() } + // If /etc/mtab does not exist in container image, then we need to + // create it, so that mount command within the container will work. + mtab := filepath.Join(mountPoint, "/etc/mtab") + if err := os.MkdirAll(filepath.Dir(mtab), 0755); err != nil { + return "", errors.Wrap(err, "error creating mtab directory") + } + if err = os.Symlink("/proc/mounts", mtab); err != nil && !os.IsExist(err) { + return "", err + } + // Request a mount of all named volumes for _, v := range c.config.NamedVolumes { vol, err := c.mountNamedVolume(v, mountPoint) @@ -2235,21 +2246,31 @@ func (c *Container) hasNamespace(namespace spec.LinuxNamespaceType) bool { } // extractSecretToStorage copies a secret's data from the secrets manager to the container's static dir -func (c *Container) extractSecretToCtrStorage(name string) error { - manager, err := secrets.NewManager(c.runtime.GetSecretsStorageDir()) +func (c *Container) extractSecretToCtrStorage(secr *ContainerSecret) error { + manager, err := c.runtime.SecretsManager() if err != nil { return err } - secr, data, err := manager.LookupSecretData(name) + _, data, err := manager.LookupSecretData(secr.Name) if err != nil { return err } secretFile := filepath.Join(c.config.SecretsPath, secr.Name) + hostUID, hostGID, err := butil.GetHostIDs(util.IDtoolsToRuntimeSpec(c.config.IDMappings.UIDMap), util.IDtoolsToRuntimeSpec(c.config.IDMappings.GIDMap), secr.UID, secr.GID) + if err != nil { + return errors.Wrap(err, "unable to extract secret") + } err = ioutil.WriteFile(secretFile, data, 0644) if err != nil { return errors.Wrapf(err, "unable to create %s", secretFile) } + if err := os.Lchown(secretFile, int(hostUID), int(hostGID)); err != nil { + return err + } + if err := os.Chmod(secretFile, os.FileMode(secr.Mode)); err != nil { + return err + } if err := label.Relabel(secretFile, c.config.MountLabel, false); err != nil { return err } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 7d57e8965..1b2f5a496 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -29,7 +29,6 @@ import ( "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/chown" "github.com/containers/common/pkg/config" - "github.com/containers/common/pkg/secrets" "github.com/containers/common/pkg/subscriptions" "github.com/containers/common/pkg/umask" "github.com/containers/podman/v3/libpod/define" @@ -359,6 +358,25 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { return nil, err } + // Add named volumes + for _, namedVol := range c.config.NamedVolumes { + volume, err := c.runtime.GetVolume(namedVol.Name) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving volume %s to add to container %s", namedVol.Name, c.ID()) + } + mountPoint, err := volume.MountPoint() + if err != nil { + return nil, err + } + volMount := spec.Mount{ + Type: "bind", + Source: mountPoint, + Destination: namedVol.Dest, + Options: namedVol.Options, + } + g.AddMount(volMount) + } + // Check if the spec file mounts contain the options z, Z or U. // If they have z or Z, relabel the source directory and then remove the option. // If they have U, chown the source directory and them remove the option. @@ -392,25 +410,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { g.SetProcessSelinuxLabel(c.ProcessLabel()) g.SetLinuxMountLabel(c.MountLabel()) - // Add named volumes - for _, namedVol := range c.config.NamedVolumes { - volume, err := c.runtime.GetVolume(namedVol.Name) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving volume %s to add to container %s", namedVol.Name, c.ID()) - } - mountPoint, err := volume.MountPoint() - if err != nil { - return nil, err - } - volMount := spec.Mount{ - Type: "bind", - Source: mountPoint, - Destination: namedVol.Dest, - Options: namedVol.Options, - } - g.AddMount(volMount) - } - // Add bind mounts to container for dstPath, srcPath := range c.state.BindMounts { newMount := spec.Mount{ @@ -759,7 +758,10 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { return nil, errors.Wrapf(err, "error setting up OCI Hooks") } if len(c.config.EnvSecrets) > 0 { - manager, err := secrets.NewManager(c.runtime.GetSecretsStorageDir()) + manager, err := c.runtime.SecretsManager() + if err != nil { + return nil, err + } if err != nil { return nil, err } @@ -1358,6 +1360,34 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti return c.save() } +// Retrieves a container's "root" net namespace container dependency. +func (c *Container) getRootNetNsDepCtr() (depCtr *Container, err error) { + containersVisited := map[string]int{c.config.ID: 1} + nextCtr := c.config.NetNsCtr + for nextCtr != "" { + // Make sure we aren't in a loop + if _, visited := containersVisited[nextCtr]; visited { + return nil, errors.New("loop encountered while determining net namespace container") + } + containersVisited[nextCtr] = 1 + + depCtr, err = c.runtime.state.Container(nextCtr) + if err != nil { + return nil, errors.Wrapf(err, "error fetching dependency %s of container %s", c.config.NetNsCtr, c.ID()) + } + // This should never happen without an error + if depCtr == nil { + break + } + nextCtr = depCtr.config.NetNsCtr + } + + if depCtr == nil { + return nil, errors.New("unexpected error depCtr is nil without reported error from runtime state") + } + return depCtr, nil +} + // Make standard bind mounts to include in the container func (c *Container) makeBindMounts() error { if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil { @@ -1396,24 +1426,9 @@ func (c *Container) makeBindMounts() error { // We want /etc/resolv.conf and /etc/hosts from the // other container. Unless we're not creating both of // them. - var ( - depCtr *Container - nextCtr string - ) - - // I don't like infinite loops, but I don't think there's - // a serious risk of looping dependencies - too many - // protections against that elsewhere. - nextCtr = c.config.NetNsCtr - for { - depCtr, err = c.runtime.state.Container(nextCtr) - if err != nil { - return errors.Wrapf(err, "error fetching dependency %s of container %s", c.config.NetNsCtr, c.ID()) - } - nextCtr = depCtr.config.NetNsCtr - if nextCtr == "" { - break - } + depCtr, err := c.getRootNetNsDepCtr() + if err != nil { + return errors.Wrapf(err, "error fetching network namespace dependency container for container %s", c.ID()) } // We need that container's bind mounts @@ -1698,7 +1713,12 @@ func (c *Container) generateResolvConf() (string, error) { nameservers = resolvconf.GetNameservers(resolv.Content) // slirp4netns has a built in DNS server. if c.config.NetMode.IsSlirp4netns() { - nameservers = append([]string{slirp4netnsDNS}, nameservers...) + slirp4netnsDNS, err := GetSlirp4netnsDNS(c.slirp4netnsSubnet) + if err != nil { + logrus.Warn("failed to determine Slirp4netns DNS: ", err.Error()) + } else { + nameservers = append([]string{slirp4netnsDNS.String()}, nameservers...) + } } } @@ -1779,7 +1799,12 @@ func (c *Container) getHosts() string { if c.Hostname() != "" { if c.config.NetMode.IsSlirp4netns() { // When using slirp4netns, the interface gets a static IP - hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP, c.Hostname(), c.config.Name) + slirp4netnsIP, err := GetSlirp4netnsGateway(c.slirp4netnsSubnet) + if err != nil { + logrus.Warn("failed to determine slirp4netnsIP: ", err.Error()) + } else { + hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP.String(), c.Hostname(), c.config.Name) + } } else { hasNetNS := false netNone := false @@ -1802,6 +1827,36 @@ func (c *Container) getHosts() string { } } } + + // Add gateway entry + var depCtr *Container + if c.config.NetNsCtr != "" { + // ignoring the error because there isn't anything to do + depCtr, _ = c.getRootNetNsDepCtr() + } else if len(c.state.NetworkStatus) != 0 { + depCtr = c + } else { + depCtr = nil + } + + if depCtr != nil { + for _, pluginResultsRaw := range depCtr.state.NetworkStatus { + pluginResult, _ := cnitypes.GetResult(pluginResultsRaw) + for _, ip := range pluginResult.IPs { + hosts += fmt.Sprintf("%s host.containers.internal\n", ip.Gateway) + } + } + } else if c.config.NetMode.IsSlirp4netns() { + gatewayIP, err := GetSlirp4netnsGateway(c.slirp4netnsSubnet) + if err != nil { + logrus.Warn("failed to determine gatewayIP: ", err.Error()) + } else { + hosts += fmt.Sprintf("%s host.containers.internal\n", gatewayIP.String()) + } + } else { + logrus.Debug("network configuration does not support host.containers.internal address") + } + return hosts } @@ -2339,7 +2394,7 @@ func (c *Container) createSecretMountDir() error { oldUmask := umask.Set(0) defer umask.Set(oldUmask) - if err := os.MkdirAll(src, 0644); err != nil { + if err := os.MkdirAll(src, 0755); err != nil { return err } if err := label.Relabel(src, c.config.MountLabel, false); err != nil { diff --git a/libpod/container_log_linux.go b/libpod/container_log_linux.go index 4a541b6e7..ec4fa9724 100644 --- a/libpod/container_log_linux.go +++ b/libpod/container_log_linux.go @@ -8,12 +8,12 @@ import ( "fmt" "io" "math" + "strings" "time" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/logs" journal "github.com/coreos/go-systemd/v22/sdjournal" - "github.com/hpcloud/tail/watch" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -89,21 +89,19 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption } }() go func() { - for { - state, err := c.State() - if err != nil { - until <- time.Time{} - logrus.Error(err) - break - } - time.Sleep(watch.POLL_DURATION) - if state != define.ContainerStateRunning && state != define.ContainerStatePaused { - until <- time.Time{} - break - } - } + // FIXME (#10323): we are facing a terrible + // race condition here. At the time the + // container dies and `c.Wait()` has returned, + // we may not have received all journald logs. + // So far there is no other way than waiting + // for a second. Ultimately, `r.Follow` is + // racy and we may have to implement our custom + // logic here. + c.Wait(ctx) + time.Sleep(time.Second) + until <- time.Time{} }() - follower := FollowBuffer{logChannel} + follower := journaldFollowBuffer{logChannel, options.Multi} err := r.Follow(until, follower) if err != nil { logrus.Debugf(err.Error()) @@ -124,7 +122,7 @@ func (c *Container) readFromJournal(ctx context.Context, options *logs.LogOption // because we are reusing bytes, we need to make // sure the old data doesn't get into the new line bytestr := string(bytes[:ec]) - logLine, err2 := logs.NewLogLine(bytestr) + logLine, err2 := logs.NewJournaldLogLine(bytestr, options.Multi) if err2 != nil { logrus.Error(err2) continue @@ -210,16 +208,18 @@ func formatterMessage(entry *journal.JournalEntry) (string, error) { if !ok { return "", fmt.Errorf("no MESSAGE field present in journal entry") } + msg = strings.TrimSuffix(msg, "\n") return msg, nil } -type FollowBuffer struct { +type journaldFollowBuffer struct { logChannel chan *logs.LogLine + withID bool } -func (f FollowBuffer) Write(p []byte) (int, error) { +func (f journaldFollowBuffer) Write(p []byte) (int, error) { bytestr := string(p) - logLine, err := logs.NewLogLine(bytestr) + logLine, err := logs.NewJournaldLogLine(bytestr, f.withID) if err != nil { return -1, err } diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index 5283946fa..af8ba6ecf 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -713,13 +713,16 @@ type DriverData struct { Data map[string]string `json:"Data"` } -// InspectHostPort provides information on a port on the host that a container's -// port is bound to. +// InspectSecret contains information on secrets mounted inside the container type InspectSecret struct { - // IP on the host we are bound to. "" if not specified (binding to all - // IPs). + // Name is the name of the secret Name string `json:"Name"` - // Port on the host we are bound to. No special formatting - just an - // integer stuffed into a string. + // ID is the ID of the secret ID string `json:"ID"` + // ID is the UID of the mounted secret file + UID uint32 `json:"UID"` + // ID is the GID of the mounted secret file + GID uint32 `json:"GID"` + // ID is the ID of the mode of the mounted secret file + Mode uint32 `json:"Mode"` } diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 64c652eec..81bf5f69c 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -179,6 +179,9 @@ var ( // ErrNoNetwork indicates that a container has no net namespace, like network=none ErrNoNetwork = errors.New("container has no network namespace") + // ErrNetworkModeInvalid indicates that a container has the wrong network mode for an operation + ErrNetworkModeInvalid = errors.New("invalid network mode") + // ErrSetSecurityAttribute indicates that a request to set a container's security attribute // was not possible. ErrSetSecurityAttribute = fmt.Errorf("%w: unable to assign security attribute", ErrOCIRuntime) diff --git a/libpod/define/info.go b/libpod/define/info.go index 87935be2d..c9d6877c0 100644 --- a/libpod/define/info.go +++ b/libpod/define/info.go @@ -21,31 +21,34 @@ type SecurityInfo struct { SELinuxEnabled bool `json:"selinuxEnabled"` } -//HostInfo describes the libpod host +// HostInfo describes the libpod host type HostInfo struct { - Arch string `json:"arch"` - BuildahVersion string `json:"buildahVersion"` - CgroupManager string `json:"cgroupManager"` - CGroupsVersion string `json:"cgroupVersion"` - Conmon *ConmonInfo `json:"conmon"` - CPUs int `json:"cpus"` - Distribution DistributionInfo `json:"distribution"` - EventLogger string `json:"eventLogger"` - Hostname string `json:"hostname"` - IDMappings IDMappings `json:"idMappings,omitempty"` - Kernel string `json:"kernel"` - MemFree int64 `json:"memFree"` - MemTotal int64 `json:"memTotal"` - OCIRuntime *OCIRuntimeInfo `json:"ociRuntime"` - OS string `json:"os"` - RemoteSocket *RemoteSocket `json:"remoteSocket,omitempty"` - RuntimeInfo map[string]interface{} `json:"runtimeInfo,omitempty"` - Security SecurityInfo `json:"security"` - Slirp4NetNS SlirpInfo `json:"slirp4netns,omitempty"` - SwapFree int64 `json:"swapFree"` - SwapTotal int64 `json:"swapTotal"` - Uptime string `json:"uptime"` - Linkmode string `json:"linkmode"` + Arch string `json:"arch"` + BuildahVersion string `json:"buildahVersion"` + CgroupManager string `json:"cgroupManager"` + CGroupsVersion string `json:"cgroupVersion"` + Conmon *ConmonInfo `json:"conmon"` + CPUs int `json:"cpus"` + Distribution DistributionInfo `json:"distribution"` + EventLogger string `json:"eventLogger"` + Hostname string `json:"hostname"` + IDMappings IDMappings `json:"idMappings,omitempty"` + Kernel string `json:"kernel"` + MemFree int64 `json:"memFree"` + MemTotal int64 `json:"memTotal"` + OCIRuntime *OCIRuntimeInfo `json:"ociRuntime"` + OS string `json:"os"` + // RemoteSocket returns the UNIX domain socket the Podman service is listening on + RemoteSocket *RemoteSocket `json:"remoteSocket,omitempty"` + RuntimeInfo map[string]interface{} `json:"runtimeInfo,omitempty"` + // ServiceIsRemote is true when the podman/libpod service is remote to the client + ServiceIsRemote bool `json:"serviceIsRemote"` + Security SecurityInfo `json:"security"` + Slirp4NetNS SlirpInfo `json:"slirp4netns,omitempty"` + SwapFree int64 `json:"swapFree"` + SwapTotal int64 `json:"swapTotal"` + Uptime string `json:"uptime"` + Linkmode string `json:"linkmode"` } // RemoteSocket describes information about the API socket diff --git a/libpod/diff.go b/libpod/diff.go index 6ce8d809a..c5a53478b 100644 --- a/libpod/diff.go +++ b/libpod/diff.go @@ -7,7 +7,7 @@ import ( "github.com/pkg/errors" ) -var containerMounts = map[string]bool{ +var initInodes = map[string]bool{ "/dev": true, "/etc/hostname": true, "/etc/hosts": true, @@ -17,6 +17,7 @@ var containerMounts = map[string]bool{ "/run/.containerenv": true, "/run/secrets": true, "/sys": true, + "/etc/mtab": true, } // GetDiff returns the differences between the two images, layers, or containers @@ -36,7 +37,7 @@ func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) { changes, err := r.store.Changes(fromLayer, toLayer) if err == nil { for _, c := range changes { - if containerMounts[c.Path] { + if initInodes[c.Path] { continue } rchanges = append(rchanges, c) diff --git a/libpod/logs/log.go b/libpod/logs/log.go index bba52408d..308053b47 100644 --- a/libpod/logs/log.go +++ b/libpod/logs/log.go @@ -206,6 +206,36 @@ func NewLogLine(line string) (*LogLine, error) { return &l, nil } +// NewJournaldLogLine creates a LogLine from the specified line from journald. +// Note that if withID is set, the first item of the message is considerred to +// be the container ID and set as such. +func NewJournaldLogLine(line string, withID bool) (*LogLine, error) { + splitLine := strings.Split(line, " ") + if len(splitLine) < 4 { + return nil, errors.Errorf("'%s' is not a valid container log line", line) + } + logTime, err := time.Parse(LogTimeFormat, splitLine[0]) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0]) + } + var msg, id string + if withID { + id = splitLine[3] + msg = strings.Join(splitLine[4:], " ") + } else { + msg = strings.Join(splitLine[3:], " ") + // NO ID + } + l := LogLine{ + Time: logTime, + Device: splitLine[1], + ParseLogType: splitLine[2], + Msg: msg, + CID: id, + } + return &l, nil +} + // Partial returns a bool if the log line is a partial log type func (l *LogLine) Partial() bool { return l.ParseLogType == PartialLogType diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index cfed5a1f2..0e8a4f768 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -23,6 +23,7 @@ import ( "github.com/containers/podman/v3/libpod/events" "github.com/containers/podman/v3/libpod/network" "github.com/containers/podman/v3/pkg/errorhandling" + "github.com/containers/podman/v3/pkg/namespaces" "github.com/containers/podman/v3/pkg/netns" "github.com/containers/podman/v3/pkg/resolvconf" "github.com/containers/podman/v3/pkg/rootless" @@ -37,16 +38,12 @@ import ( ) const ( - // slirp4netnsIP is the IP used by slirp4netns to configure the tap device - // inside the network namespace. - slirp4netnsIP = "10.0.2.100" - - // slirp4netnsDNS is the IP for the built-in DNS server in the slirp network - slirp4netnsDNS = "10.0.2.3" - // slirp4netnsMTU the default MTU override slirp4netnsMTU = 65520 + // default slirp4ns subnet + defaultSlirp4netnsSubnet = "10.0.2.0/24" + // rootlessCNINSName is the file name for the rootless network namespace bind mount rootlessCNINSName = "rootless-cni-ns" ) @@ -360,15 +357,20 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) { } // build a new resolv.conf file which uses the slirp4netns dns server address - resolveIP := slirp4netnsDNS + resolveIP, err := GetSlirp4netnsDNS(nil) + if err != nil { + return nil, errors.Wrap(err, "failed to determine default slirp4netns DNS address") + } + if netOptions.cidr != "" { _, cidr, err := net.ParseCIDR(netOptions.cidr) if err != nil { return nil, errors.Wrap(err, "failed to parse slirp4netns cidr") } - // the slirp dns ip is always the third ip in the subnet - cidr.IP[len(cidr.IP)-1] = cidr.IP[len(cidr.IP)-1] + 3 - resolveIP = cidr.IP.String() + resolveIP, err = GetSlirp4netnsDNS(cidr) + if err != nil { + return nil, errors.Wrapf(err, "failed to determine slirp4netns DNS address from cidr: %s", cidr.String()) + } } conf, err := resolvconf.Get() if err != nil { @@ -377,7 +379,7 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) { searchDomains := resolvconf.GetSearchDomains(conf.Content) dnsOptions := resolvconf.GetOptions(conf.Content) - _, err = resolvconf.Build(filepath.Join(cniDir, "resolv.conf"), []string{resolveIP}, searchDomains, dnsOptions) + _, err = resolvconf.Build(filepath.Join(cniDir, "resolv.conf"), []string{resolveIP.String()}, searchDomains, dnsOptions) if err != nil { return nil, errors.Wrap(err, "failed to create rootless cni resolv.conf") } @@ -577,7 +579,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { // set up port forwarder for CNI-in-slirp4netns netnsPath := ctr.state.NetNS.Path() // TODO: support slirp4netns port forwarder as well - return r.setupRootlessPortMappingViaRLK(ctr, netnsPath, "") + return r.setupRootlessPortMappingViaRLK(ctr, netnsPath) } return nil } @@ -757,6 +759,15 @@ func getContainerNetNS(ctr *Container) (string, error) { return "", nil } +// isBridgeNetMode checks if the given network mode is bridge. +// It returns nil when it is set to bridge and an error otherwise. +func isBridgeNetMode(n namespaces.NetworkMode) error { + if !n.IsBridge() { + return errors.Wrapf(define.ErrNetworkModeInvalid, "%q is not supported", n) + } + return nil +} + // Reload only works with containers with a configured network. // It will tear down, and then reconfigure, the network of the container. // This is mainly used when a reload of firewall rules wipes out existing @@ -770,8 +781,8 @@ func (r *Runtime) reloadContainerNetwork(ctr *Container) ([]*cnitypes.Result, er if ctr.state.NetNS == nil { return nil, errors.Wrapf(define.ErrCtrStateInvalid, "container %s network is not configured, refusing to reload", ctr.ID()) } - if rootless.IsRootless() || ctr.config.NetMode.IsSlirp4netns() { - return nil, errors.Wrapf(define.ErrRootless, "network reload only supported for root containers") + if err := isBridgeNetMode(ctr.config.NetMode); err != nil { + return nil, err } logrus.Infof("Going to reload container %s network", ctr.ID()) @@ -1025,8 +1036,8 @@ func (w *logrusDebugWriter) Write(p []byte) (int, error) { // NetworkDisconnect removes a container from the network func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error { // only the bridge mode supports cni networks - if !c.config.NetMode.IsBridge() { - return errors.Errorf("network mode %q is not supported", c.config.NetMode) + if err := isBridgeNetMode(c.config.NetMode); err != nil { + return err } networks, err := c.networksByNameIndex() @@ -1086,8 +1097,8 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro // ConnectNetwork connects a container to a given network func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) error { // only the bridge mode supports cni networks - if !c.config.NetMode.IsBridge() { - return errors.Errorf("network mode %q is not supported", c.config.NetMode) + if err := isBridgeNetMode(c.config.NetMode); err != nil { + return err } networks, err := c.networksByNameIndex() diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go index c46dc6972..74d390d29 100644 --- a/libpod/networking_slirp4netns.go +++ b/libpod/networking_slirp4netns.go @@ -308,15 +308,89 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error { return err } + // Set a default slirp subnet. Parsing a string with the net helper is easier than building the struct myself + _, ctr.slirp4netnsSubnet, _ = net.ParseCIDR(defaultSlirp4netnsSubnet) + + // Set slirp4netnsSubnet addresses now that we are pretty sure the command executed + if netOptions.cidr != "" { + ipv4, ipv4network, err := net.ParseCIDR(netOptions.cidr) + if err != nil || ipv4.To4() == nil { + return errors.Errorf("invalid cidr %q", netOptions.cidr) + } + ctr.slirp4netnsSubnet = ipv4network + } + if havePortMapping { if netOptions.isSlirpHostForward { return r.setupRootlessPortMappingViaSlirp(ctr, cmd, apiSocket) } - return r.setupRootlessPortMappingViaRLK(ctr, netnsPath, netOptions.cidr) + return r.setupRootlessPortMappingViaRLK(ctr, netnsPath) } + return nil } +// Get expected slirp ipv4 address based on subnet. If subnet is null use default subnet +// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description +func GetSlirp4netnsIP(subnet *net.IPNet) (*net.IP, error) { + _, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet) + if subnet != nil { + slirpSubnet = subnet + } + expectedIP, err := addToIP(slirpSubnet, uint32(100)) + if err != nil { + return nil, errors.Wrapf(err, "error calculating expected ip for slirp4netns") + } + return expectedIP, nil +} + +// Get expected slirp Gateway ipv4 address based on subnet +// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description +func GetSlirp4netnsGateway(subnet *net.IPNet) (*net.IP, error) { + _, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet) + if subnet != nil { + slirpSubnet = subnet + } + expectedGatewayIP, err := addToIP(slirpSubnet, uint32(2)) + if err != nil { + return nil, errors.Wrapf(err, "error calculating expected gateway ip for slirp4netns") + } + return expectedGatewayIP, nil +} + +// Get expected slirp DNS ipv4 address based on subnet +// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description +func GetSlirp4netnsDNS(subnet *net.IPNet) (*net.IP, error) { + _, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet) + if subnet != nil { + slirpSubnet = subnet + } + expectedDNSIP, err := addToIP(slirpSubnet, uint32(3)) + if err != nil { + return nil, errors.Wrapf(err, "error calculating expected dns ip for slirp4netns") + } + return expectedDNSIP, nil +} + +// Helper function to calculate slirp ip address offsets +// Adapted from: https://github.com/signalsciences/ipv4/blob/master/int.go#L12-L24 +func addToIP(subnet *net.IPNet, offset uint32) (*net.IP, error) { + // I have no idea why I have to do this, but if I don't ip is 0 + ipFixed := subnet.IP.To4() + + ipInteger := uint32(ipFixed[3]) | uint32(ipFixed[2])<<8 | uint32(ipFixed[1])<<16 | uint32(ipFixed[0])<<24 + ipNewRaw := ipInteger + offset + // Avoid overflows + if ipNewRaw < ipInteger { + return nil, errors.Errorf("integer overflow while calculating ip address offset, %s + %d", ipFixed, offset) + } + ipNew := net.IPv4(byte(ipNewRaw>>24), byte(ipNewRaw>>16&0xFF), byte(ipNewRaw>>8)&0xFF, byte(ipNewRaw&0xFF)) + if !subnet.Contains(ipNew) { + return nil, errors.Errorf("calculated ip address %s is not within given subnet %s", ipNew.String(), subnet.String()) + } + return &ipNew, nil +} + func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout time.Duration) error { prog := filepath.Base(cmd.Path) if len(cmd.Args) > 0 { @@ -363,7 +437,7 @@ func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout t return nil } -func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath, slirp4CIDR string) error { +func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath string) error { syncR, syncW, err := os.Pipe() if err != nil { return errors.Wrapf(err, "failed to open pipe") @@ -390,17 +464,11 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath, slir } } - childIP := slirp4netnsIP - // set the correct childIP when a custom cidr is set - if slirp4CIDR != "" { - _, cidr, err := net.ParseCIDR(slirp4CIDR) - if err != nil { - return errors.Wrap(err, "failed to parse slirp4netns cidr") - } - // the slirp container ip is always the hundredth ip in the subnet - cidr.IP[len(cidr.IP)-1] = cidr.IP[len(cidr.IP)-1] + 100 - childIP = cidr.IP.String() + slirp4netnsIP, err := GetSlirp4netnsIP(ctr.slirp4netnsSubnet) + if err != nil { + return errors.Wrapf(err, "failed to get slirp4ns ip") } + childIP := slirp4netnsIP.String() outer: for _, r := range ctr.state.NetworkStatus { for _, i := range r.IPs { diff --git a/libpod/options.go b/libpod/options.go index be26ced99..f942d264b 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1695,23 +1695,12 @@ func WithUmask(umask string) CtrCreateOption { } // WithSecrets adds secrets to the container -func WithSecrets(secretNames []string) CtrCreateOption { +func WithSecrets(containerSecrets []*ContainerSecret) CtrCreateOption { return func(ctr *Container) error { if ctr.valid { return define.ErrCtrFinalized } - manager, err := secrets.NewManager(ctr.runtime.GetSecretsStorageDir()) - if err != nil { - return err - } - for _, name := range secretNames { - secr, err := manager.Lookup(name) - if err != nil { - return err - } - ctr.config.Secrets = append(ctr.config.Secrets, secr) - } - + ctr.config.Secrets = containerSecrets return nil } } @@ -1723,7 +1712,7 @@ func WithEnvSecrets(envSecrets map[string]string) CtrCreateOption { if ctr.valid { return define.ErrCtrFinalized } - manager, err := secrets.NewManager(ctr.runtime.GetSecretsStorageDir()) + manager, err := ctr.runtime.SecretsManager() if err != nil { return err } diff --git a/libpod/runtime.go b/libpod/runtime.go index 80fe92b54..d0bdeb574 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -16,6 +16,7 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/pkg/sysregistriesv2" is "github.com/containers/image/v5/storage" "github.com/containers/image/v5/types" @@ -103,6 +104,8 @@ type Runtime struct { // noStore indicates whether we need to interact with a store or not noStore bool + // secretsManager manages secrets + secretsManager *secrets.SecretsManager } // SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set. @@ -1022,6 +1025,18 @@ func (r *Runtime) GetSecretsStorageDir() string { return filepath.Join(r.store.GraphRoot(), "secrets") } +// SecretsManager returns the directory that the secrets manager should take +func (r *Runtime) SecretsManager() (*secrets.SecretsManager, error) { + if r.secretsManager == nil { + manager, err := secrets.NewManager(r.GetSecretsStorageDir()) + if err != nil { + return nil, err + } + r.secretsManager = manager + } + return r.secretsManager, nil +} + func graphRootMounted() bool { f, err := os.OpenFile("/run/.containerenv", os.O_RDONLY, os.ModePerm) if err != nil { diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 7d31e392f..4e4b2a8ab 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -366,7 +366,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai return nil, err } for _, secr := range ctr.config.Secrets { - err = ctr.extractSecretToCtrStorage(secr.Name) + err = ctr.extractSecretToCtrStorage(secr) if err != nil { return nil, err } diff --git a/nix/nixpkgs.json b/nix/nixpkgs.json index 54ddb8978..9b8b8289e 100644 --- a/nix/nixpkgs.json +++ b/nix/nixpkgs.json @@ -1,9 +1,9 @@ { "url": "https://github.com/nixos/nixpkgs", - "rev": "cce26cd83d20356ee96ac9cf1de748e87fcc50b5", - "date": "2021-04-12T22:14:24+02:00", - "path": "/nix/store/0flgsv9kn7q0b2ipqz35lkxq65p5cv83-nixpkgs", - "sha256": "0ji00jz18fvppbi5y98gcalxq2mrsg7dhh9l64yf38jpb5rx3sd4", + "rev": "eb7e1ef185f6c990cda5f71fdc4fb02e76ab06d5", + "date": "2021-05-05T23:16:00+02:00", + "path": "/nix/store/a98lkhjlsqh32ic2kkrv5kkik6jy25wh-nixpkgs", + "sha256": "1ibz204c41g7baqga2iaj11yz9l75cfdylkiqjnk5igm81ivivxg", "fetchSubmodules": false, "deepClone": false, "leaveDotGit": false diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 0364b00a3..79e815490 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -40,25 +40,13 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.Boo } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) ([]*reports.PruneReport, error) { - // NOTE: the terms "dangling" and "intermediate" are not used - // consistently across our code base. In libimage, "dangling" means - // that an image has no tags. "intermediate" means that an image is - // dangling and that no other image depends on it (i.e., has no - // children). - // - // While pruning usually refers to "dangling" images, it has always - // removed "intermediate" ones. - defaultOptions := &libimage.RemoveImagesOptions{ - Filters: append(opts.Filter, "intermediate=true", "containers=false", "readonly=false"), + pruneOptions := &libimage.RemoveImagesOptions{ + Filters: append(opts.Filter, "containers=false", "readonly=false"), WithSize: true, } - // `image prune --all` means to *also* remove images which are not in - // use by any container. Since image filters are chained, we need to - // do two look ups since the default ones are a subset of all. - unusedOptions := &libimage.RemoveImagesOptions{ - Filters: append(opts.Filter, "containers=false", "readonly=false"), - WithSize: true, + if !opts.All { + pruneOptions.Filters = append(pruneOptions.Filters, "dangling=true") } var pruneReports []*reports.PruneReport @@ -66,16 +54,12 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption // Now prune all images until we converge. numPreviouslyRemovedImages := 1 for { - removedDefault, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, defaultOptions) - if rmErrors != nil { - return nil, errorhandling.JoinErrors(rmErrors) - } - removedUnused, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, unusedOptions) + removedImages, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, nil, pruneOptions) if rmErrors != nil { return nil, errorhandling.JoinErrors(rmErrors) } - for _, rmReport := range append(removedDefault, removedUnused...) { + for _, rmReport := range removedImages { r := *rmReport pruneReports = append(pruneReports, &reports.PruneReport{ Id: r.ID, @@ -83,7 +67,7 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption }) } - numRemovedImages := len(removedDefault) + len(removedUnused) + numRemovedImages := len(removedImages) if numRemovedImages+numPreviouslyRemovedImages == 0 { break } diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index 1a833332c..33ab280e5 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -71,7 +71,9 @@ func (ic *ContainerEngine) NetworkReload(ctx context.Context, names []string, op report := new(entities.NetworkReloadReport) report.Id = ctr.ID() report.Err = ctr.ReloadNetwork() - if options.All && errors.Cause(report.Err) == define.ErrCtrStateInvalid { + // ignore errors for invalid ctr state and network mode when --all is used + if options.All && (errors.Cause(report.Err) == define.ErrCtrStateInvalid || + errors.Cause(report.Err) == define.ErrNetworkModeInvalid) { continue } reports = append(reports, report) diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index a94c5f5c5..0ac9b5d8d 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -12,7 +12,6 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" - "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" @@ -161,7 +160,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY ) // Create the secret manager before hand - secretsManager, err := secrets.NewManager(ic.Libpod.GetSecretsStorageDir()) + secretsManager, err := ic.Libpod.SecretsManager() if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go index 764f4a9dc..1e1cbc70f 100644 --- a/pkg/domain/infra/abi/secrets.go +++ b/pkg/domain/infra/abi/secrets.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "path/filepath" - "github.com/containers/common/pkg/secrets" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/pkg/errors" ) @@ -14,7 +13,7 @@ import ( func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader io.Reader, options entities.SecretCreateOptions) (*entities.SecretCreateReport, error) { data, _ := ioutil.ReadAll(reader) secretsPath := ic.Libpod.GetSecretsStorageDir() - manager, err := secrets.NewManager(secretsPath) + manager, err := ic.Libpod.SecretsManager() if err != nil { return nil, err } @@ -36,8 +35,7 @@ func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader } func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string) ([]*entities.SecretInfoReport, []error, error) { - secretsPath := ic.Libpod.GetSecretsStorageDir() - manager, err := secrets.NewManager(secretsPath) + manager, err := ic.Libpod.SecretsManager() if err != nil { return nil, nil, err } @@ -71,8 +69,7 @@ func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string } func (ic *ContainerEngine) SecretList(ctx context.Context) ([]*entities.SecretInfoReport, error) { - secretsPath := ic.Libpod.GetSecretsStorageDir() - manager, err := secrets.NewManager(secretsPath) + manager, err := ic.Libpod.SecretsManager() if err != nil { return nil, err } @@ -105,8 +102,7 @@ func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, opt toRemove []string reports = []*entities.SecretRmReport{} ) - secretsPath := ic.Libpod.GetSecretsStorageDir() - manager, err := secrets.NewManager(secretsPath) + manager, err := ic.Libpod.SecretsManager() if err != nil { return nil, err } diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go index d9f34057f..68bb551dc 100644 --- a/pkg/machine/pull.go +++ b/pkg/machine/pull.go @@ -162,7 +162,11 @@ func Decompress(localPath, uncompressedPath string) error { return err } - if compressionType := archive.DetectCompression(sourceFile); compressionType.Extension() == "tar.xz" { + compressionType := archive.DetectCompression(sourceFile) + if compressionType != archive.Uncompressed { + fmt.Println("Extracting compressed file") + } + if compressionType == archive.Xz { return decompressXZ(localPath, uncompressedFileWriter) } return decompressEverythingElse(localPath, uncompressedFileWriter) @@ -172,7 +176,6 @@ func Decompress(localPath, uncompressedPath string) error { // Maybe extracting then renameing is a good idea here.. // depends on xz: not pre-installed on mac, so it becomes a brew dependency func decompressXZ(src string, output io.Writer) error { - fmt.Println("Extracting compressed file") cmd := exec.Command("xzcat", "-k", src) //cmd := exec.Command("xz", "-d", "-k", "-v", src) stdOut, err := cmd.StdoutPipe() @@ -190,7 +193,6 @@ func decompressXZ(src string, output io.Writer) error { } func decompressEverythingElse(src string, output io.Writer) error { - fmt.Println("Extracting compressed file") f, err := os.Open(src) if err != nil { return err diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 7682367b7..a0f5cc7e6 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -400,7 +400,24 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. } if len(s.Secrets) != 0 { - options = append(options, libpod.WithSecrets(s.Secrets)) + manager, err := rt.SecretsManager() + if err != nil { + return nil, err + } + var secrs []*libpod.ContainerSecret + for _, s := range s.Secrets { + secr, err := manager.Lookup(s.Source) + if err != nil { + return nil, err + } + secrs = append(secrs, &libpod.ContainerSecret{ + Secret: secr, + UID: s.UID, + GID: s.GID, + Mode: s.Mode, + }) + } + options = append(options, libpod.WithSecrets(secrs)) } if len(s.EnvSecrets) != 0 { diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 4e41061a5..054388384 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -250,27 +250,26 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener if !exists { return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name) } + + dest, options, err := parseMountPath(volume.MountPath, volume.ReadOnly) + if err != nil { + return nil, err + } + switch volumeSource.Type { case KubeVolumeTypeBindMount: - if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil { - return nil, errors.Wrapf(err, "error in parsing MountPath") - } mount := spec.Mount{ - Destination: volume.MountPath, + Destination: dest, Source: volumeSource.Source, Type: "bind", - } - if volume.ReadOnly { - mount.Options = []string{"ro"} + Options: options, } s.Mounts = append(s.Mounts, mount) case KubeVolumeTypeNamed: namedVolume := specgen.NamedVolume{ - Dest: volume.MountPath, - Name: volumeSource.Source, - } - if volume.ReadOnly { - namedVolume.Options = []string{"ro"} + Dest: dest, + Name: volumeSource.Source, + Options: options, } s.Volumes = append(s.Volumes, &namedVolume) default: @@ -300,6 +299,25 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener return s, nil } +func parseMountPath(mountPath string, readOnly bool) (string, []string, error) { + options := []string{} + splitVol := strings.Split(mountPath, ":") + if len(splitVol) > 2 { + return "", options, errors.Errorf("%q incorrect volume format, should be ctr-dir[:option]", mountPath) + } + dest := splitVol[0] + if len(splitVol) > 1 { + options = strings.Split(splitVol[1], ",") + } + if err := parse.ValidateVolumeCtrDir(dest); err != nil { + return "", options, errors.Wrapf(err, "error in parsing MountPath") + } + if readOnly { + options = append(options, "ro") + } + return dest, options, nil +} + func setupSecurityContext(s *specgen.SpecGenerator, containerYAML v1.Container) { if containerYAML.SecurityContext == nil { return diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 2e01d1535..2815bdebb 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -258,7 +258,7 @@ type ContainerStorageConfig struct { RootfsPropagation string `json:"rootfs_propagation,omitempty"` // Secrets are the secrets that will be added to the container // Optional. - Secrets []string `json:"secrets,omitempty"` + Secrets []Secret `json:"secrets,omitempty"` // Volatile specifies whether the container storage can be optimized // at the cost of not syncing all the dirty files in memory. Volatile bool `json:"volatile,omitempty"` @@ -521,6 +521,13 @@ type PortMapping struct { Protocol string `json:"protocol,omitempty"` } +type Secret struct { + Source string + UID uint32 + GID uint32 + Mode uint32 +} + var ( // ErrNoStaticIPRootless is used when a rootless user requests to assign a static IP address // to a pod or container diff --git a/test/apiv2/python/__init__.py b/test/apiv2/python/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/test/apiv2/python/__init__.py diff --git a/test/apiv2/python/rest_api/__init__.py b/test/apiv2/python/rest_api/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/test/apiv2/python/rest_api/__init__.py diff --git a/test/apiv2/python/rest_api/fixtures/__init__.py b/test/apiv2/python/rest_api/fixtures/__init__.py new file mode 100644 index 000000000..5d763e454 --- /dev/null +++ b/test/apiv2/python/rest_api/fixtures/__init__.py @@ -0,0 +1,3 @@ +from .api_testcase import APITestCase + +__all__ = ["APITestCase"] diff --git a/test/apiv2/python/rest_api/fixtures/api_testcase.py b/test/apiv2/python/rest_api/fixtures/api_testcase.py new file mode 100644 index 000000000..8b771774b --- /dev/null +++ b/test/apiv2/python/rest_api/fixtures/api_testcase.py @@ -0,0 +1,103 @@ +import json +import subprocess +import unittest + +import requests +import sys +import time + +from .podman import Podman + + +class APITestCase(unittest.TestCase): + PODMAN_URL = "http://localhost:8080" + podman = None # initialized podman configuration for tests + service = None # podman service instance + + @classmethod + def setUpClass(cls): + super().setUpClass() + + APITestCase.podman = Podman() + APITestCase.service = APITestCase.podman.open( + "system", "service", "tcp:localhost:8080", "--time=0" + ) + # give the service some time to be ready... + time.sleep(2) + + returncode = APITestCase.service.poll() + if returncode is not None: + raise subprocess.CalledProcessError(returncode, "podman system service") + + r = requests.post( + APITestCase.uri("/images/pull?reference=quay.io%2Flibpod%2Falpine%3Alatest") + ) + if r.status_code != 200: + raise subprocess.CalledProcessError( + r.status_code, f"podman images pull quay.io/libpod/alpine:latest {r.text}" + ) + + @classmethod + def tearDownClass(cls): + APITestCase.service.terminate() + stdout, stderr = APITestCase.service.communicate(timeout=0.5) + if stdout: + sys.stdout.write("\nService Stdout:\n" + stdout.decode("utf-8")) + if stderr: + sys.stderr.write("\nService Stderr:\n" + stderr.decode("utf-8")) + return super().tearDownClass() + + def setUp(self): + super().setUp() + APITestCase.podman.run("run", "alpine", "/bin/ls", check=True) + + def tearDown(self) -> None: + APITestCase.podman.run("pod", "rm", "--all", "--force", check=True) + APITestCase.podman.run("rm", "--all", "--force", check=True) + super().tearDown() + + @property + def podman_url(self): + return "http://localhost:8080" + + @staticmethod + def uri(path): + return APITestCase.PODMAN_URL + "/v2.0.0/libpod" + path + + def resolve_container(self, path): + """Find 'first' container and return 'Id' formatted into given URI path.""" + + try: + r = requests.get(self.uri("/containers/json?all=true")) + containers = r.json() + except Exception as e: + msg = f"Bad container response: {e}" + if r is not None: + msg += ": " + r.text + raise self.failureException(msg) + return path.format(containers[0]["Id"]) + + def assertContainerExists(self, member, msg=None): # pylint: disable=invalid-name + r = requests.get(self.uri(f"/containers/{member}/exists")) + if r.status_code == 404: + if msg is None: + msg = f"Container '{member}' does not exist." + self.failureException(msg) + + def assertContainerNotExists(self, member, msg=None): # pylint: disable=invalid-name + r = requests.get(self.uri(f"/containers/{member}/exists")) + if r.status_code == 204: + if msg is None: + msg = f"Container '{member}' exists." + self.failureException(msg) + + def assertId(self, content): # pylint: disable=invalid-name + objects = json.loads(content) + try: + if isinstance(objects, dict): + _ = objects["Id"] + else: + for item in objects: + _ = item["Id"] + except KeyError: + self.failureException("Failed in find 'Id' in return value.") diff --git a/test/apiv2/rest_api/__init__.py b/test/apiv2/python/rest_api/fixtures/podman.py index 0ad6b51b3..bae04f87d 100644 --- a/test/apiv2/rest_api/__init__.py +++ b/test/apiv2/python/rest_api/fixtures/podman.py @@ -7,7 +7,7 @@ import sys import tempfile -class Podman(object): +class Podman: """ Instances hold the configuration and setup for running podman commands """ @@ -34,7 +34,7 @@ class Podman(object): p = configparser.ConfigParser() p.read_dict( { - "registries.search": {"registries": "['docker.io']"}, + "registries.search": {"registries": "['quay.io']"}, "registries.insecure": {"registries": "[]"}, "registries.block": {"registries": "[]"}, } @@ -102,7 +102,7 @@ class Podman(object): ) def run(self, command, *args, **kwargs): - """Podman initialized instance to run a given command + """Run given podman command :param self: Podman instance :param command: podman sub-command to run diff --git a/test/apiv2/python/rest_api/test_v2_0_0_container.py b/test/apiv2/python/rest_api/test_v2_0_0_container.py new file mode 100644 index 000000000..70c07d47f --- /dev/null +++ b/test/apiv2/python/rest_api/test_v2_0_0_container.py @@ -0,0 +1,192 @@ +import random +import unittest + +import requests +from dateutil.parser import parse + +from .fixtures import APITestCase + + +class ContainerTestCase(APITestCase): + def test_list(self): + r = requests.get(self.uri("/containers/json"), timeout=5) + self.assertEqual(r.status_code, 200, r.text) + obj = r.json() + self.assertEqual(len(obj), 0) + + def test_list_all(self): + r = requests.get(self.uri("/containers/json?all=true")) + self.assertEqual(r.status_code, 200, r.text) + self.assertId(r.content) + + def test_inspect(self): + r = requests.get(self.uri(self.resolve_container("/containers/{}/json"))) + self.assertEqual(r.status_code, 200, r.text) + self.assertId(r.content) + _ = parse(r.json()["Created"]) + + def test_stats(self): + r = requests.get(self.uri(self.resolve_container("/containers/{}/stats?stream=false"))) + self.assertIn(r.status_code, (200, 409), r.text) + if r.status_code == 200: + self.assertId(r.content) + + def test_delete(self): + r = requests.delete(self.uri(self.resolve_container("/containers/{}"))) + self.assertEqual(r.status_code, 204, r.text) + + def test_stop(self): + r = requests.post(self.uri(self.resolve_container("/containers/{}/start"))) + self.assertIn(r.status_code, (204, 304), r.text) + + r = requests.post(self.uri(self.resolve_container("/containers/{}/stop"))) + self.assertIn(r.status_code, (204, 304), r.text) + + def test_start(self): + r = requests.post(self.uri(self.resolve_container("/containers/{}/stop"))) + self.assertIn(r.status_code, (204, 304), r.text) + + r = requests.post(self.uri(self.resolve_container("/containers/{}/start"))) + self.assertIn(r.status_code, (204, 304), r.text) + + def test_restart(self): + r = requests.post(self.uri(self.resolve_container("/containers/{}/start"))) + self.assertIn(r.status_code, (204, 304), r.text) + + r = requests.post(self.uri(self.resolve_container("/containers/{}/restart")), timeout=5) + self.assertEqual(r.status_code, 204, r.text) + + def test_resize(self): + r = requests.post(self.uri(self.resolve_container("/containers/{}/resize?h=43&w=80"))) + self.assertIn(r.status_code, (200, 409), r.text) + if r.status_code == 200: + self.assertEqual(r.text, "", r.text) + + def test_attach(self): + self.skipTest("FIXME: Test timeouts") + r = requests.post(self.uri(self.resolve_container("/containers/{}/attach")), timeout=5) + self.assertIn(r.status_code, (101, 500), r.text) + + def test_logs(self): + r = requests.get(self.uri(self.resolve_container("/containers/{}/logs?stdout=true"))) + self.assertEqual(r.status_code, 200, r.text) + + def test_commit(self): + r = requests.post(self.uri(self.resolve_container("/commit?container={}"))) + self.assertEqual(r.status_code, 200, r.text) + self.assertId(r.content) + + obj = r.json() + self.assertIsInstance(obj, dict) + + def test_prune(self): + name = f"Container_{random.getrandbits(160):x}" + + r = requests.post( + self.podman_url + f"/v1.40/containers/create?name={name}", + json={ + "Cmd": ["cp", "/etc/motd", "/motd.size_test"], + "Image": "alpine:latest", + "NetworkDisabled": True, + }, + ) + self.assertEqual(r.status_code, 201, r.text) + create = r.json() + + r = requests.post(self.podman_url + f"/v1.40/containers/{create['Id']}/start") + self.assertEqual(r.status_code, 204, r.text) + + r = requests.post(self.podman_url + f"/v1.40/containers/{create['Id']}/wait") + self.assertEqual(r.status_code, 200, r.text) + wait = r.json() + self.assertEqual(wait["StatusCode"], 0, wait["Error"]["Message"]) + + prune = requests.post(self.podman_url + "/v1.40/containers/prune") + self.assertEqual(prune.status_code, 200, prune.status_code) + prune_payload = prune.json() + self.assertGreater(prune_payload["SpaceReclaimed"], 0) + self.assertIn(create["Id"], prune_payload["ContainersDeleted"]) + + # Delete any orphaned containers + r = requests.get(self.podman_url + "/v1.40/containers/json?all=true") + self.assertEqual(r.status_code, 200, r.text) + for self.resolve_container in r.json(): + requests.delete( + self.podman_url + f"/v1.40/containers/{self.resolve_container['Id']}?force=true" + ) + + # Image prune here tied to containers freeing up + prune = requests.post(self.podman_url + "/v1.40/images/prune") + self.assertEqual(prune.status_code, 200, prune.text) + prune_payload = prune.json() + self.assertGreater(prune_payload["SpaceReclaimed"], 0) + + # FIXME need method to determine which image is going to be "pruned" to fix test + # TODO should handler be recursive when deleting images? + # self.assertIn(img["Id"], prune_payload["ImagesDeleted"][1]["Deleted"]) + + # FIXME (@vrothberg): I commented this line out during the `libimage` migration. + # It doesn't make sense to report anything to be deleted if the reclaimed space + # is zero. I think the test needs some rewrite. + # self.assertIsNotNone(prune_payload["ImagesDeleted"][1]["Deleted"]) + + def test_status(self): + r = requests.post( + self.podman_url + "/v1.40/containers/create?name=topcontainer", + json={"Cmd": ["top"], "Image": "alpine:latest"}, + ) + self.assertEqual(r.status_code, 201, r.text) + payload = r.json() + container_id = payload["Id"] + self.assertIsNotNone(container_id) + + r = requests.get( + self.podman_url + "/v1.40/containers/json", + params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, + ) + self.assertEqual(r.status_code, 200, r.text) + payload = r.json() + self.assertEqual(payload[0]["Status"], "Created") + + r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/start") + self.assertEqual(r.status_code, 204, r.text) + + r = requests.get( + self.podman_url + "/v1.40/containers/json", + params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, + ) + self.assertEqual(r.status_code, 200, r.text) + payload = r.json() + self.assertTrue(str(payload[0]["Status"]).startswith("Up")) + + r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/pause") + self.assertEqual(r.status_code, 204, r.text) + + r = requests.get( + self.podman_url + "/v1.40/containers/json", + params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, + ) + self.assertEqual(r.status_code, 200, r.text) + payload = r.json() + self.assertTrue(str(payload[0]["Status"]).startswith("Up")) + self.assertTrue(str(payload[0]["Status"]).endswith("(Paused)")) + + r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/unpause") + self.assertEqual(r.status_code, 204, r.text) + r = requests.post(self.podman_url + f"/v1.40/containers/{container_id}/stop") + self.assertEqual(r.status_code, 204, r.text) + + r = requests.get( + self.podman_url + "/v1.40/containers/json", + params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, + ) + self.assertEqual(r.status_code, 200, r.text) + payload = r.json() + self.assertTrue(str(payload[0]["Status"]).startswith("Exited")) + + r = requests.delete(self.podman_url + f"/v1.40/containers/{container_id}") + self.assertEqual(r.status_code, 204, r.text) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/apiv2/python/rest_api/test_v2_0_0_image.py b/test/apiv2/python/rest_api/test_v2_0_0_image.py new file mode 100644 index 000000000..99f513608 --- /dev/null +++ b/test/apiv2/python/rest_api/test_v2_0_0_image.py @@ -0,0 +1,165 @@ +import json +import unittest +from multiprocessing import Process + +import requests +from dateutil.parser import parse +from .fixtures import APITestCase + + +class ImageTestCase(APITestCase): + def test_list(self): + r = requests.get(self.podman_url + "/v1.40/images/json") + self.assertEqual(r.status_code, 200, r.text) + + # See https://docs.docker.com/engine/api/v1.40/#operation/ImageList + required_keys = ( + "Id", + "ParentId", + "RepoTags", + "RepoDigests", + "Created", + "Size", + "SharedSize", + "VirtualSize", + "Labels", + "Containers", + ) + images = r.json() + self.assertIsInstance(images, list) + for item in images: + self.assertIsInstance(item, dict) + for k in required_keys: + self.assertIn(k, item) + + def test_inspect(self): + r = requests.get(self.podman_url + "/v1.40/images/alpine/json") + self.assertEqual(r.status_code, 200, r.text) + + # See https://docs.docker.com/engine/api/v1.40/#operation/ImageInspect + required_keys = ( + "Id", + "Parent", + "Comment", + "Created", + "Container", + "DockerVersion", + "Author", + "Architecture", + "Os", + "Size", + "VirtualSize", + "GraphDriver", + "RootFS", + "Metadata", + ) + + image = r.json() + self.assertIsInstance(image, dict) + for item in required_keys: + self.assertIn(item, image) + _ = parse(image["Created"]) + + def test_delete(self): + r = requests.delete(self.podman_url + "/v1.40/images/alpine?force=true") + self.assertEqual(r.status_code, 200, r.text) + self.assertIsInstance(r.json(), list) + + def test_pull(self): + r = requests.post(self.uri("/images/pull?reference=alpine"), timeout=15) + self.assertEqual(r.status_code, 200, r.status_code) + text = r.text + keys = { + "error": False, + "id": False, + "images": False, + "stream": False, + } + # Read and record stanza's from pull + for line in str.splitlines(text): + obj = json.loads(line) + key_list = list(obj.keys()) + for k in key_list: + keys[k] = True + + self.assertFalse(keys["error"], "Expected no errors") + self.assertTrue(keys["id"], "Expected to find id stanza") + self.assertTrue(keys["images"], "Expected to find images stanza") + self.assertTrue(keys["stream"], "Expected to find stream progress stanza's") + + def test_search_compat(self): + url = self.podman_url + "/v1.40/images/search" + + # Had issues with this test hanging when repositories not happy + def do_search1(): + payload = {"term": "alpine"} + r = requests.get(url, params=payload, timeout=5) + self.assertEqual(r.status_code, 200, f"#1: {r.text}") + self.assertIsInstance(r.json(), list) + + def do_search2(): + payload = {"term": "alpine", "limit": 1} + r = requests.get(url, params=payload, timeout=5) + self.assertEqual(r.status_code, 200, f"#2: {r.text}") + + results = r.json() + self.assertIsInstance(results, list) + self.assertEqual(len(results), 1) + + def do_search3(): + # FIXME: Research if quay.io supports is-official and which image is "official" + return + payload = {"term": "thanos", "filters": '{"is-official":["true"]}'} + r = requests.get(url, params=payload, timeout=5) + self.assertEqual(r.status_code, 200, f"#3: {r.text}") + + results = r.json() + self.assertIsInstance(results, list) + + # There should be only one official image + self.assertEqual(len(results), 1) + + def do_search4(): + headers = {"X-Registry-Auth": "null"} + payload = {"term": "alpine"} + r = requests.get(url, params=payload, headers=headers, timeout=5) + self.assertEqual(r.status_code, 200, f"#4: {r.text}") + + def do_search5(): + headers = {"X-Registry-Auth": "invalid value"} + payload = {"term": "alpine"} + r = requests.get(url, params=payload, headers=headers, timeout=5) + self.assertEqual(r.status_code, 400, f"#5: {r.text}") + + i = 1 + for fn in [do_search1, do_search2, do_search3, do_search4, do_search5]: + with self.subTest(i=i): + search = Process(target=fn) + search.start() + search.join(timeout=10) + self.assertFalse(search.is_alive(), f"#{i} /images/search took too long") + + # search_methods = [do_search1, do_search2, do_search3, do_search4, do_search5] + # for search_method in search_methods: + # search = Process(target=search_method) + # search.start() + # search.join(timeout=10) + # self.assertFalse(search.is_alive(), "/images/search took too long") + + def test_history(self): + r = requests.get(self.podman_url + "/v1.40/images/alpine/history") + self.assertEqual(r.status_code, 200, r.text) + + # See https://docs.docker.com/engine/api/v1.40/#operation/ImageHistory + required_keys = ("Id", "Created", "CreatedBy", "Tags", "Size", "Comment") + + changes = r.json() + self.assertIsInstance(changes, list) + for change in changes: + self.assertIsInstance(change, dict) + for k in required_keys: + self.assertIn(k, change) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/apiv2/python/rest_api/test_v2_0_0_manifest.py b/test/apiv2/python/rest_api/test_v2_0_0_manifest.py new file mode 100644 index 000000000..c28c63bcb --- /dev/null +++ b/test/apiv2/python/rest_api/test_v2_0_0_manifest.py @@ -0,0 +1,14 @@ +import unittest + +import requests +from .fixtures import APITestCase + + +class ManifestTestCase(APITestCase): + def test_manifest_409(self): + r = requests.post(self.uri("/manifests/create"), params={"name": "ThisIsAnInvalidImage"}) + self.assertEqual(r.status_code, 400, r.text) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/apiv2/python/rest_api/test_v2_0_0_network.py b/test/apiv2/python/rest_api/test_v2_0_0_network.py new file mode 100644 index 000000000..3888123fb --- /dev/null +++ b/test/apiv2/python/rest_api/test_v2_0_0_network.py @@ -0,0 +1,155 @@ +import random +import unittest + +import requests + +from .fixtures import APITestCase + + +class NetworkTestCase(APITestCase): + # TODO Need to support Docker-py order of network/container creates + def test_connect(self): + """Create network and container then connect to network""" + net_default = requests.post( + self.podman_url + "/v1.40/networks/create", json={"Name": "TestDefaultNetwork"} + ) + self.assertEqual(net_default.status_code, 201, net_default.text) + + create = requests.post( + self.podman_url + "/v1.40/containers/create?name=postCreateConnect", + json={ + "Cmd": ["top"], + "Image": "alpine:latest", + "NetworkDisabled": False, + # FIXME adding these 2 lines cause: (This is sampled from docker-py) + # "network already exists","message":"container + # 01306e499df5441560d70071a54342611e422a94de20865add50a9565fd79fb9 is already connected to CNI + # network \"TestDefaultNetwork\": network already exists" + # "HostConfig": {"NetworkMode": "TestDefaultNetwork"}, + # "NetworkingConfig": {"EndpointsConfig": {"TestDefaultNetwork": None}}, + # FIXME These two lines cause: + # CNI network \"TestNetwork\" not found","message":"error configuring network namespace for container + # 369ddfa7d3211ebf1fbd5ddbff91bd33fa948858cea2985c133d6b6507546dff: CNI network \"TestNetwork\" not + # found" + # "HostConfig": {"NetworkMode": "TestNetwork"}, + # "NetworkingConfig": {"EndpointsConfig": {"TestNetwork": None}}, + # FIXME no networking defined cause: (note this error is from the container inspect below) + # "internal libpod error","message":"network inspection mismatch: asked to join 2 CNI network(s) [ + # TestDefaultNetwork podman], but have information on 1 network(s): internal libpod error" + }, + ) + self.assertEqual(create.status_code, 201, create.text) + self.assertId(create.content) + + payload = create.json() + start = requests.post(self.podman_url + f"/v1.40/containers/{payload['Id']}/start") + self.assertEqual(start.status_code, 204, start.text) + + connect = requests.post( + self.podman_url + "/v1.40/networks/TestDefaultNetwork/connect", + json={"Container": payload["Id"]}, + ) + self.assertEqual(connect.status_code, 200, connect.text) + self.assertEqual(connect.text, "OK\n") + + inspect = requests.get(f"{self.podman_url}/v1.40/containers/{payload['Id']}/json") + self.assertEqual(inspect.status_code, 200, inspect.text) + + payload = inspect.json() + self.assertFalse(payload["Config"].get("NetworkDisabled", False)) + + self.assertEqual( + "TestDefaultNetwork", + payload["NetworkSettings"]["Networks"]["TestDefaultNetwork"]["NetworkID"], + ) + # TODO restore this to test, when joining multiple networks possible + # self.assertEqual( + # "TestNetwork", + # payload["NetworkSettings"]["Networks"]["TestNetwork"]["NetworkID"], + # ) + # TODO Need to support network aliases + # self.assertIn( + # "test_post_create", + # payload["NetworkSettings"]["Networks"]["TestNetwork"]["Aliases"], + # ) + + def test_create(self): + """Create network and connect container during create""" + net = requests.post( + self.podman_url + "/v1.40/networks/create", json={"Name": "TestNetwork"} + ) + self.assertEqual(net.status_code, 201, net.text) + + create = requests.post( + self.podman_url + "/v1.40/containers/create?name=postCreate", + json={ + "Cmd": ["date"], + "Image": "alpine:latest", + "NetworkDisabled": False, + "HostConfig": {"NetworkMode": "TestNetwork"}, + }, + ) + self.assertEqual(create.status_code, 201, create.text) + self.assertId(create.content) + + payload = create.json() + inspect = requests.get(f"{self.podman_url}/v1.40/containers/{payload['Id']}/json") + self.assertEqual(inspect.status_code, 200, inspect.text) + + payload = inspect.json() + self.assertFalse(payload["Config"].get("NetworkDisabled", False)) + self.assertEqual( + "TestNetwork", + payload["NetworkSettings"]["Networks"]["TestNetwork"]["NetworkID"], + ) + + def test_crud(self): + name = f"Network_{random.getrandbits(160):x}" + + # Cannot test for 0 existing networks because default "podman" network always exists + + create = requests.post(self.podman_url + "/v1.40/networks/create", json={"Name": name}) + self.assertEqual(create.status_code, 201, create.text) + self.assertId(create.content) + + net = create.json() + self.assertIsInstance(net, dict) + self.assertNotEqual(net["Id"], name) + ident = net["Id"] + + ls = requests.get(self.podman_url + "/v1.40/networks") + self.assertEqual(ls.status_code, 200, ls.text) + + networks = ls.json() + self.assertIsInstance(networks, list) + + found = False + for net in networks: + if net["Name"] == name: + found = True + break + self.assertTrue(found, f"Network '{name}' not found") + + inspect = requests.get(self.podman_url + f"/v1.40/networks/{ident}") + self.assertEqual(inspect.status_code, 200, inspect.text) + self.assertIsInstance(inspect.json(), dict) + + inspect = requests.delete(self.podman_url + f"/v1.40/networks/{ident}") + self.assertEqual(inspect.status_code, 204, inspect.text) + inspect = requests.get(self.podman_url + f"/v1.40/networks/{ident}") + self.assertEqual(inspect.status_code, 404, inspect.text) + + # network prune + prune_name = f"Network_{random.getrandbits(160):x}" + prune_create = requests.post( + self.podman_url + "/v1.40/networks/create", json={"Name": prune_name} + ) + self.assertEqual(create.status_code, 201, prune_create.text) + + prune = requests.post(self.podman_url + "/v1.40/networks/prune") + self.assertEqual(prune.status_code, 200, prune.text) + self.assertTrue(prune_name in prune.json()["NetworksDeleted"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/apiv2/python/rest_api/test_v2_0_0_pod.py b/test/apiv2/python/rest_api/test_v2_0_0_pod.py new file mode 100644 index 000000000..9155ad19c --- /dev/null +++ b/test/apiv2/python/rest_api/test_v2_0_0_pod.py @@ -0,0 +1,65 @@ +import random +import unittest + +import requests +from .fixtures import APITestCase + + +class TestApi(APITestCase): + def test_pod_start_conflict(self): + """Verify issue #8865""" + + pod_name = list() + pod_name.append(f"Pod_{random.getrandbits(160):x}") + pod_name.append(f"Pod_{random.getrandbits(160):x}") + + r = requests.post( + self.uri("/pods/create"), + json={ + "name": pod_name[0], + "no_infra": False, + "portmappings": [{"host_ip": "127.0.0.1", "host_port": 8889, "container_port": 89}], + }, + ) + self.assertEqual(r.status_code, 201, r.text) + r = requests.post( + self.uri("/containers/create"), + json={ + "pod": pod_name[0], + "image": "quay.io/libpod/alpine:latest", + "command": ["top"], + }, + ) + self.assertEqual(r.status_code, 201, r.text) + + r = requests.post( + self.uri("/pods/create"), + json={ + "name": pod_name[1], + "no_infra": False, + "portmappings": [{"host_ip": "127.0.0.1", "host_port": 8889, "container_port": 89}], + }, + ) + self.assertEqual(r.status_code, 201, r.text) + r = requests.post( + self.uri("/containers/create"), + json={ + "pod": pod_name[1], + "image": "quay.io/libpod/alpine:latest", + "command": ["top"], + }, + ) + self.assertEqual(r.status_code, 201, r.text) + + r = requests.post(self.uri(f"/pods/{pod_name[0]}/start")) + self.assertEqual(r.status_code, 200, r.text) + + r = requests.post(self.uri(f"/pods/{pod_name[1]}/start")) + self.assertEqual(r.status_code, 409, r.text) + + start = r.json() + self.assertGreater(len(start["Errs"]), 0, r.text) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/apiv2/python/rest_api/test_v2_0_0_system.py b/test/apiv2/python/rest_api/test_v2_0_0_system.py new file mode 100644 index 000000000..3628b5af1 --- /dev/null +++ b/test/apiv2/python/rest_api/test_v2_0_0_system.py @@ -0,0 +1,88 @@ +import json +import unittest + +import requests +from .fixtures import APITestCase + + +class SystemTestCase(APITestCase): + def test_info(self): + r = requests.get(self.uri("/info")) + self.assertEqual(r.status_code, 200, r.text) + self.assertIsNotNone(r.content) + _ = r.json() + + r = requests.get(self.podman_url + "/v1.40/info") + self.assertEqual(r.status_code, 200, r.text) + self.assertIsNotNone(r.content) + _ = r.json() + + def test_events(self): + r = requests.get(self.uri("/events?stream=false")) + self.assertEqual(r.status_code, 200, r.text) + self.assertIsNotNone(r.content) + + report = r.text.splitlines() + self.assertGreater(len(report), 0, "No events found!") + for line in report: + obj = json.loads(line) + # Actor.ID is uppercase for compatibility + self.assertIn("ID", obj["Actor"]) + + def test_ping(self): + required_headers = ( + "API-Version", + "Builder-Version", + "Docker-Experimental", + "Cache-Control", + "Pragma", + "Pragma", + ) + + def check_headers(req): + for k in required_headers: + self.assertIn(k, req.headers) + + r = requests.get(self.podman_url + "/_ping") + self.assertEqual(r.status_code, 200, r.text) + self.assertEqual(r.text, "OK") + check_headers(r) + + r = requests.head(self.podman_url + "/_ping") + self.assertEqual(r.status_code, 200, r.text) + self.assertEqual(r.text, "") + check_headers(r) + + r = requests.get(self.uri("/_ping")) + self.assertEqual(r.status_code, 200, r.text) + self.assertEqual(r.text, "OK") + check_headers(r) + + r = requests.head(self.uri("/_ping")) + self.assertEqual(r.status_code, 200, r.text) + self.assertEqual(r.text, "") + check_headers(r) + + def test_version(self): + r = requests.get(self.podman_url + "/v1.40/version") + self.assertEqual(r.status_code, 200, r.text) + + r = requests.get(self.uri("/version")) + self.assertEqual(r.status_code, 200, r.text) + + def test_df(self): + r = requests.get(self.podman_url + "/v1.40/system/df") + self.assertEqual(r.status_code, 200, r.text) + + obj = r.json() + self.assertIn("Images", obj) + self.assertIn("Containers", obj) + self.assertIn("Volumes", obj) + self.assertIn("BuildCache", obj) + + r = requests.get(self.uri("/system/df")) + self.assertEqual(r.status_code, 200, r.text) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/apiv2/python/rest_api/test_v2_0_0_volume.py b/test/apiv2/python/rest_api/test_v2_0_0_volume.py new file mode 100644 index 000000000..f5231e17c --- /dev/null +++ b/test/apiv2/python/rest_api/test_v2_0_0_volume.py @@ -0,0 +1,75 @@ +import os +import random +import unittest + +import requests +from .fixtures import APITestCase + + +class VolumeTestCase(APITestCase): + def test_volume(self): + name = f"Volume_{random.getrandbits(160):x}" + + ls = requests.get(self.podman_url + "/v1.40/volumes") + self.assertEqual(ls.status_code, 200, ls.text) + + # See https://docs.docker.com/engine/api/v1.40/#operation/VolumeList + required_keys = ( + "Volumes", + "Warnings", + ) + + volumes = ls.json() + self.assertIsInstance(volumes, dict) + for key in required_keys: + self.assertIn(key, volumes) + + create = requests.post(self.podman_url + "/v1.40/volumes/create", json={"Name": name}) + self.assertEqual(create.status_code, 201, create.text) + + # See https://docs.docker.com/engine/api/v1.40/#operation/VolumeCreate + # and https://docs.docker.com/engine/api/v1.40/#operation/VolumeInspect + required_keys = ( + "Name", + "Driver", + "Mountpoint", + "Labels", + "Scope", + "Options", + ) + + volume = create.json() + self.assertIsInstance(volume, dict) + for k in required_keys: + self.assertIn(k, volume) + self.assertEqual(volume["Name"], name) + + inspect = requests.get(self.podman_url + f"/v1.40/volumes/{name}") + self.assertEqual(inspect.status_code, 200, inspect.text) + + volume = inspect.json() + self.assertIsInstance(volume, dict) + for k in required_keys: + self.assertIn(k, volume) + + rm = requests.delete(self.podman_url + f"/v1.40/volumes/{name}") + self.assertEqual(rm.status_code, 204, rm.text) + + # recreate volume with data and then prune it + r = requests.post(self.podman_url + "/v1.40/volumes/create", json={"Name": name}) + self.assertEqual(create.status_code, 201, create.text) + + create = r.json() + with open(os.path.join(create["Mountpoint"], "test_prune"), "w") as file: + file.writelines(["This is a test\n", "This is a good test\n"]) + + prune = requests.post(self.podman_url + "/v1.40/volumes/prune") + self.assertEqual(prune.status_code, 200, prune.text) + + payload = prune.json() + self.assertIn(name, payload["VolumesDeleted"]) + self.assertGreater(payload["SpaceReclaimed"], 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/apiv2/rest_api/v1_test_rest_v1_0_0.py b/test/apiv2/python/rest_api/v1_test_rest_v1_0_0.py index 23528a246..905c29683 100644 --- a/test/apiv2/rest_api/v1_test_rest_v1_0_0.py +++ b/test/apiv2/python/rest_api/v1_test_rest_v1_0_0.py @@ -45,7 +45,7 @@ class TestApi(unittest.TestCase): if TestApi.podman.poll() is not None: sys.stderr.write("podman service returned {}", TestApi.podman.returncode) sys.exit(2) - requests.get(_url("/images/create?fromSrc=docker.io%2Falpine%3Alatest")) + requests.get(_url("/images/create?fromSrc=quay.io%2Flibpod%2Falpine%3Alatest")) # calling out to podman is easier than the API for running a container subprocess.run( [podman(), "run", "alpine", "/bin/ls"], diff --git a/test/apiv2/rest_api/test_rest_v2_0_0.py b/test/apiv2/rest_api/test_rest_v2_0_0.py deleted file mode 100644 index f66e2b120..000000000 --- a/test/apiv2/rest_api/test_rest_v2_0_0.py +++ /dev/null @@ -1,744 +0,0 @@ -import json -import os -import random -import string -import subprocess -import sys -import time -import unittest -from multiprocessing import Process - -import requests -from dateutil.parser import parse - -from test.apiv2.rest_api import Podman - -PODMAN_URL = "http://localhost:8080" - - -def _url(path): - return PODMAN_URL + "/v2.0.0/libpod" + path - - -def ctnr(path): - try: - r = requests.get(_url("/containers/json?all=true")) - ctnrs = json.loads(r.text) - except Exception as e: - msg = f"Bad container response: {e}" - if r is not None: - msg = msg + " " + r.text - sys.stderr.write(msg + "\n") - raise - return path.format(ctnrs[0]["Id"]) - - -def validateObjectFields(buffer): - objs = json.loads(buffer) - if not isinstance(objs, dict): - for o in objs: - _ = o["Id"] - else: - _ = objs["Id"] - return objs - - -class TestApi(unittest.TestCase): - podman = None # initialized podman configuration for tests - service = None # podman service instance - - def setUp(self): - super().setUp() - - TestApi.podman.run("run", "alpine", "/bin/ls", check=True) - - def tearDown(self) -> None: - super().tearDown() - - TestApi.podman.run("pod", "rm", "--all", "--force", check=True) - TestApi.podman.run("rm", "--all", "--force", check=True) - - @classmethod - def setUpClass(cls): - super().setUpClass() - - TestApi.podman = Podman() - TestApi.service = TestApi.podman.open("system", "service", "tcp:localhost:8080", "--time=0") - # give the service some time to be ready... - time.sleep(2) - - returncode = TestApi.service.poll() - if returncode is not None: - raise subprocess.CalledProcessError(returncode, "podman system service") - - r = requests.post(_url("/images/pull?reference=docker.io%2Falpine%3Alatest")) - if r.status_code != 200: - raise subprocess.CalledProcessError( - r.status_code, f"podman images pull docker.io/alpine:latest {r.text}" - ) - - @classmethod - def tearDownClass(cls): - TestApi.service.terminate() - stdout, stderr = TestApi.service.communicate(timeout=0.5) - if stdout: - sys.stdout.write("\nService Stdout:\n" + stdout.decode("utf-8")) - if stderr: - sys.stderr.write("\nService Stderr:\n" + stderr.decode("utf-8")) - return super().tearDownClass() - - def test_info(self): - r = requests.get(_url("/info")) - self.assertEqual(r.status_code, 200) - self.assertIsNotNone(r.content) - _ = json.loads(r.text) - - info = requests.get(PODMAN_URL + "/v1.40/info") - self.assertEqual(info.status_code, 200, info.content) - _ = json.loads(info.text) - - def test_events(self): - r = requests.get(_url("/events?stream=false")) - self.assertEqual(r.status_code, 200, r.text) - self.assertIsNotNone(r.content) - - report = r.text.splitlines() - self.assertGreater(len(report), 0, "No events found!") - for line in report: - obj = json.loads(line) - # Actor.ID is uppercase for compatibility - self.assertIn("ID", obj["Actor"]) - - def test_containers(self): - r = requests.get(_url("/containers/json"), timeout=5) - self.assertEqual(r.status_code, 200, r.text) - obj = json.loads(r.text) - self.assertEqual(len(obj), 0) - - def test_containers_all(self): - r = requests.get(_url("/containers/json?all=true")) - self.assertEqual(r.status_code, 200, r.text) - validateObjectFields(r.text) - - def test_inspect_container(self): - r = requests.get(_url(ctnr("/containers/{}/json"))) - self.assertEqual(r.status_code, 200, r.text) - obj = validateObjectFields(r.content) - _ = parse(obj["Created"]) - - def test_stats(self): - r = requests.get(_url(ctnr("/containers/{}/stats?stream=false"))) - self.assertIn(r.status_code, (200, 409), r.text) - if r.status_code == 200: - validateObjectFields(r.text) - - def test_delete_containers(self): - r = requests.delete(_url(ctnr("/containers/{}"))) - self.assertEqual(r.status_code, 204, r.text) - - def test_stop_containers(self): - r = requests.post(_url(ctnr("/containers/{}/start"))) - self.assertIn(r.status_code, (204, 304), r.text) - - r = requests.post(_url(ctnr("/containers/{}/stop"))) - self.assertIn(r.status_code, (204, 304), r.text) - - def test_start_containers(self): - r = requests.post(_url(ctnr("/containers/{}/stop"))) - self.assertIn(r.status_code, (204, 304), r.text) - - r = requests.post(_url(ctnr("/containers/{}/start"))) - self.assertIn(r.status_code, (204, 304), r.text) - - def test_restart_containers(self): - r = requests.post(_url(ctnr("/containers/{}/start"))) - self.assertIn(r.status_code, (204, 304), r.text) - - r = requests.post(_url(ctnr("/containers/{}/restart")), timeout=5) - self.assertEqual(r.status_code, 204, r.text) - - def test_resize(self): - r = requests.post(_url(ctnr("/containers/{}/resize?h=43&w=80"))) - self.assertIn(r.status_code, (200, 409), r.text) - if r.status_code == 200: - self.assertEqual(r.text, "", r.text) - - def test_attach_containers(self): - self.skipTest("FIXME: Test timeouts") - r = requests.post(_url(ctnr("/containers/{}/attach")), timeout=5) - self.assertIn(r.status_code, (101, 500), r.text) - - def test_logs_containers(self): - r = requests.get(_url(ctnr("/containers/{}/logs?stdout=true"))) - self.assertEqual(r.status_code, 200, r.text) - - # TODO Need to support Docker-py order of network/container creates - def test_post_create_compat_connect(self): - """Create network and container then connect to network""" - net_default = requests.post( - PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestDefaultNetwork"} - ) - self.assertEqual(net_default.status_code, 201, net_default.text) - - create = requests.post( - PODMAN_URL + "/v1.40/containers/create?name=postCreateConnect", - json={ - "Cmd": ["top"], - "Image": "alpine:latest", - "NetworkDisabled": False, - # FIXME adding these 2 lines cause: (This is sampled from docker-py) - # "network already exists","message":"container - # 01306e499df5441560d70071a54342611e422a94de20865add50a9565fd79fb9 is already connected to CNI - # network \"TestDefaultNetwork\": network already exists" - # "HostConfig": {"NetworkMode": "TestDefaultNetwork"}, - # "NetworkingConfig": {"EndpointsConfig": {"TestDefaultNetwork": None}}, - # FIXME These two lines cause: - # CNI network \"TestNetwork\" not found","message":"error configuring network namespace for container - # 369ddfa7d3211ebf1fbd5ddbff91bd33fa948858cea2985c133d6b6507546dff: CNI network \"TestNetwork\" not - # found" - # "HostConfig": {"NetworkMode": "TestNetwork"}, - # "NetworkingConfig": {"EndpointsConfig": {"TestNetwork": None}}, - # FIXME no networking defined cause: (note this error is from the container inspect below) - # "internal libpod error","message":"network inspection mismatch: asked to join 2 CNI network(s) [ - # TestDefaultNetwork podman], but have information on 1 network(s): internal libpod error" - }, - ) - self.assertEqual(create.status_code, 201, create.text) - payload = json.loads(create.text) - self.assertIsNotNone(payload["Id"]) - - start = requests.post(PODMAN_URL + f"/v1.40/containers/{payload['Id']}/start") - self.assertEqual(start.status_code, 204, start.text) - - connect = requests.post( - PODMAN_URL + "/v1.40/networks/TestDefaultNetwork/connect", - json={"Container": payload["Id"]}, - ) - self.assertEqual(connect.status_code, 200, connect.text) - self.assertEqual(connect.text, "OK\n") - - inspect = requests.get(f"{PODMAN_URL}/v1.40/containers/{payload['Id']}/json") - self.assertEqual(inspect.status_code, 200, inspect.text) - - payload = json.loads(inspect.text) - self.assertFalse(payload["Config"].get("NetworkDisabled", False)) - - self.assertEqual( - "TestDefaultNetwork", - payload["NetworkSettings"]["Networks"]["TestDefaultNetwork"]["NetworkID"], - ) - # TODO restore this to test, when joining multiple networks possible - # self.assertEqual( - # "TestNetwork", - # payload["NetworkSettings"]["Networks"]["TestNetwork"]["NetworkID"], - # ) - # TODO Need to support network aliases - # self.assertIn( - # "test_post_create", - # payload["NetworkSettings"]["Networks"]["TestNetwork"]["Aliases"], - # ) - - def test_post_create_compat(self): - """Create network and connect container during create""" - net = requests.post(PODMAN_URL + "/v1.40/networks/create", json={"Name": "TestNetwork"}) - self.assertEqual(net.status_code, 201, net.text) - - create = requests.post( - PODMAN_URL + "/v1.40/containers/create?name=postCreate", - json={ - "Cmd": ["date"], - "Image": "alpine:latest", - "NetworkDisabled": False, - "HostConfig": {"NetworkMode": "TestNetwork"}, - }, - ) - self.assertEqual(create.status_code, 201, create.text) - payload = json.loads(create.text) - self.assertIsNotNone(payload["Id"]) - - inspect = requests.get(f"{PODMAN_URL}/v1.40/containers/{payload['Id']}/json") - self.assertEqual(inspect.status_code, 200, inspect.text) - payload = json.loads(inspect.text) - self.assertFalse(payload["Config"].get("NetworkDisabled", False)) - self.assertEqual( - "TestNetwork", - payload["NetworkSettings"]["Networks"]["TestNetwork"]["NetworkID"], - ) - - def test_commit(self): - r = requests.post(_url(ctnr("/commit?container={}"))) - self.assertEqual(r.status_code, 200, r.text) - - obj = json.loads(r.content) - self.assertIsInstance(obj, dict) - self.assertIn("Id", obj) - - def test_images_compat(self): - r = requests.get(PODMAN_URL + "/v1.40/images/json") - self.assertEqual(r.status_code, 200, r.text) - - # See https://docs.docker.com/engine/api/v1.40/#operation/ImageList - required_keys = ( - "Id", - "ParentId", - "RepoTags", - "RepoDigests", - "Created", - "Size", - "SharedSize", - "VirtualSize", - "Labels", - "Containers", - ) - objs = json.loads(r.content) - self.assertIn(type(objs), (list,)) - for o in objs: - self.assertIsInstance(o, dict) - for k in required_keys: - self.assertIn(k, o) - - def test_inspect_image_compat(self): - r = requests.get(PODMAN_URL + "/v1.40/images/alpine/json") - self.assertEqual(r.status_code, 200, r.text) - - # See https://docs.docker.com/engine/api/v1.40/#operation/ImageInspect - required_keys = ( - "Id", - "Parent", - "Comment", - "Created", - "Container", - "DockerVersion", - "Author", - "Architecture", - "Os", - "Size", - "VirtualSize", - "GraphDriver", - "RootFS", - "Metadata", - ) - - obj = json.loads(r.content) - self.assertIn(type(obj), (dict,)) - for k in required_keys: - self.assertIn(k, obj) - _ = parse(obj["Created"]) - - def test_delete_image_compat(self): - r = requests.delete(PODMAN_URL + "/v1.40/images/alpine?force=true") - self.assertEqual(r.status_code, 200, r.text) - obj = json.loads(r.content) - self.assertIn(type(obj), (list,)) - - def test_pull(self): - r = requests.post(_url("/images/pull?reference=alpine"), timeout=15) - self.assertEqual(r.status_code, 200, r.status_code) - text = r.text - keys = { - "error": False, - "id": False, - "images": False, - "stream": False, - } - # Read and record stanza's from pull - for line in str.splitlines(text): - obj = json.loads(line) - key_list = list(obj.keys()) - for k in key_list: - keys[k] = True - - self.assertFalse(keys["error"], "Expected no errors") - self.assertTrue(keys["id"], "Expected to find id stanza") - self.assertTrue(keys["images"], "Expected to find images stanza") - self.assertTrue(keys["stream"], "Expected to find stream progress stanza's") - - def test_search_compat(self): - url = PODMAN_URL + "/v1.40/images/search" - - # Had issues with this test hanging when repositories not happy - def do_search1(): - payload = {"term": "alpine"} - r = requests.get(url, params=payload, timeout=5) - self.assertEqual(r.status_code, 200, r.text) - objs = json.loads(r.text) - self.assertIn(type(objs), (list,)) - - def do_search2(): - payload = {"term": "alpine", "limit": 1} - r = requests.get(url, params=payload, timeout=5) - self.assertEqual(r.status_code, 200, r.text) - objs = json.loads(r.text) - self.assertIn(type(objs), (list,)) - self.assertEqual(len(objs), 1) - - def do_search3(): - payload = {"term": "alpine", "filters": '{"is-official":["true"]}'} - r = requests.get(url, params=payload, timeout=5) - self.assertEqual(r.status_code, 200, r.text) - objs = json.loads(r.text) - self.assertIn(type(objs), (list,)) - # There should be only one official image - self.assertEqual(len(objs), 1) - - def do_search4(): - headers = {"X-Registry-Auth": "null"} - payload = {"term": "alpine"} - r = requests.get(url, params=payload, headers=headers, timeout=5) - self.assertEqual(r.status_code, 200, r.text) - - def do_search5(): - headers = {"X-Registry-Auth": "invalid value"} - payload = {"term": "alpine"} - r = requests.get(url, params=payload, headers=headers, timeout=5) - self.assertEqual(r.status_code, 400, r.text) - - search_methods = [do_search1, do_search2, do_search3, do_search4, do_search5] - for search_method in search_methods: - search = Process(target=search_method) - search.start() - search.join(timeout=10) - self.assertFalse(search.is_alive(), "/images/search took too long") - - def test_ping(self): - required_headers = ( - "API-Version", - "Builder-Version", - "Docker-Experimental", - "Cache-Control", - "Pragma", - "Pragma", - ) - - def check_headers(req): - for k in required_headers: - self.assertIn(k, req.headers) - - r = requests.get(PODMAN_URL + "/_ping") - self.assertEqual(r.status_code, 200, r.text) - self.assertEqual(r.text, "OK") - check_headers(r) - - r = requests.head(PODMAN_URL + "/_ping") - self.assertEqual(r.status_code, 200, r.text) - self.assertEqual(r.text, "") - check_headers(r) - - r = requests.get(_url("/_ping")) - self.assertEqual(r.status_code, 200, r.text) - self.assertEqual(r.text, "OK") - check_headers(r) - - r = requests.head(_url("/_ping")) - self.assertEqual(r.status_code, 200, r.text) - self.assertEqual(r.text, "") - check_headers(r) - - def test_history_compat(self): - r = requests.get(PODMAN_URL + "/v1.40/images/alpine/history") - self.assertEqual(r.status_code, 200, r.text) - - # See https://docs.docker.com/engine/api/v1.40/#operation/ImageHistory - required_keys = ("Id", "Created", "CreatedBy", "Tags", "Size", "Comment") - - objs = json.loads(r.content) - self.assertIn(type(objs), (list,)) - for o in objs: - self.assertIsInstance(o, dict) - for k in required_keys: - self.assertIn(k, o) - - def test_network_compat(self): - name = "Network_" + "".join(random.choice(string.ascii_letters) for i in range(10)) - - # Cannot test for 0 existing networks because default "podman" network always exists - - create = requests.post(PODMAN_URL + "/v1.40/networks/create", json={"Name": name}) - self.assertEqual(create.status_code, 201, create.content) - obj = json.loads(create.content) - self.assertIn(type(obj), (dict,)) - self.assertIn("Id", obj) - ident = obj["Id"] - self.assertNotEqual(name, ident) - - ls = requests.get(PODMAN_URL + "/v1.40/networks") - self.assertEqual(ls.status_code, 200, ls.content) - objs = json.loads(ls.content) - self.assertIn(type(objs), (list,)) - - found = False - for network in objs: - if network["Name"] == name: - found = True - self.assertTrue(found, f"Network {name} not found") - - inspect = requests.get(PODMAN_URL + f"/v1.40/networks/{ident}") - self.assertEqual(inspect.status_code, 200, inspect.content) - obj = json.loads(create.content) - self.assertIn(type(obj), (dict,)) - - inspect = requests.delete(PODMAN_URL + f"/v1.40/networks/{ident}") - self.assertEqual(inspect.status_code, 204, inspect.content) - inspect = requests.get(PODMAN_URL + f"/v1.40/networks/{ident}") - self.assertEqual(inspect.status_code, 404, inspect.content) - - # network prune - prune_name = "Network_" + "".join(random.choice(string.ascii_letters) for i in range(10)) - prune_create = requests.post( - PODMAN_URL + "/v1.40/networks/create", json={"Name": prune_name} - ) - self.assertEqual(create.status_code, 201, prune_create.content) - - prune = requests.post(PODMAN_URL + "/v1.40/networks/prune") - self.assertEqual(prune.status_code, 200, prune.content) - obj = json.loads(prune.content) - self.assertTrue(prune_name in obj["NetworksDeleted"]) - - def test_volumes_compat(self): - name = "Volume_" + "".join(random.choice(string.ascii_letters) for i in range(10)) - - ls = requests.get(PODMAN_URL + "/v1.40/volumes") - self.assertEqual(ls.status_code, 200, ls.content) - - # See https://docs.docker.com/engine/api/v1.40/#operation/VolumeList - required_keys = ( - "Volumes", - "Warnings", - ) - - obj = json.loads(ls.content) - self.assertIn(type(obj), (dict,)) - for k in required_keys: - self.assertIn(k, obj) - - create = requests.post(PODMAN_URL + "/v1.40/volumes/create", json={"Name": name}) - self.assertEqual(create.status_code, 201, create.content) - - # See https://docs.docker.com/engine/api/v1.40/#operation/VolumeCreate - # and https://docs.docker.com/engine/api/v1.40/#operation/VolumeInspect - required_keys = ( - "Name", - "Driver", - "Mountpoint", - "Labels", - "Scope", - "Options", - ) - - obj = json.loads(create.content) - self.assertIn(type(obj), (dict,)) - for k in required_keys: - self.assertIn(k, obj) - self.assertEqual(obj["Name"], name) - - inspect = requests.get(PODMAN_URL + f"/v1.40/volumes/{name}") - self.assertEqual(inspect.status_code, 200, inspect.content) - - obj = json.loads(create.content) - self.assertIn(type(obj), (dict,)) - for k in required_keys: - self.assertIn(k, obj) - - rm = requests.delete(PODMAN_URL + f"/v1.40/volumes/{name}") - self.assertEqual(rm.status_code, 204, rm.content) - - # recreate volume with data and then prune it - r = requests.post(PODMAN_URL + "/v1.40/volumes/create", json={"Name": name}) - self.assertEqual(create.status_code, 201, create.content) - create = json.loads(r.content) - with open(os.path.join(create["Mountpoint"], "test_prune"), "w") as file: - file.writelines(["This is a test\n", "This is a good test\n"]) - - prune = requests.post(PODMAN_URL + "/v1.40/volumes/prune") - self.assertEqual(prune.status_code, 200, prune.content) - payload = json.loads(prune.content) - self.assertIn(name, payload["VolumesDeleted"]) - self.assertGreater(payload["SpaceReclaimed"], 0) - - def test_version(self): - r = requests.get(PODMAN_URL + "/v1.40/version") - self.assertEqual(r.status_code, 200, r.content) - - r = requests.get(_url("/version")) - self.assertEqual(r.status_code, 200, r.content) - - def test_df_compat(self): - r = requests.get(PODMAN_URL + "/v1.40/system/df") - self.assertEqual(r.status_code, 200, r.content) - - obj = json.loads(r.content) - self.assertIn("Images", obj) - self.assertIn("Containers", obj) - self.assertIn("Volumes", obj) - self.assertIn("BuildCache", obj) - - def test_prune_compat(self): - name = "Ctnr_" + "".join(random.choice(string.ascii_letters) for i in range(10)) - - r = requests.post( - PODMAN_URL + f"/v1.40/containers/create?name={name}", - json={ - "Cmd": ["cp", "/etc/motd", "/motd.size_test"], - "Image": "alpine:latest", - "NetworkDisabled": True, - }, - ) - self.assertEqual(r.status_code, 201, r.text) - create = json.loads(r.text) - - r = requests.post(PODMAN_URL + f"/v1.40/containers/{create['Id']}/start") - self.assertEqual(r.status_code, 204, r.text) - - r = requests.post(PODMAN_URL + f"/v1.40/containers/{create['Id']}/wait") - self.assertEqual(r.status_code, 200, r.text) - wait = json.loads(r.text) - self.assertEqual(wait["StatusCode"], 0, wait["Error"]["Message"]) - - prune = requests.post(PODMAN_URL + "/v1.40/containers/prune") - self.assertEqual(prune.status_code, 200, prune.status_code) - prune_payload = json.loads(prune.text) - self.assertGreater(prune_payload["SpaceReclaimed"], 0) - self.assertIn(create["Id"], prune_payload["ContainersDeleted"]) - - # Delete any orphaned containers - r = requests.get(PODMAN_URL + "/v1.40/containers/json?all=true") - self.assertEqual(r.status_code, 200, r.text) - for ctnr in json.loads(r.text): - requests.delete(PODMAN_URL + f"/v1.40/containers/{ctnr['Id']}?force=true") - - prune = requests.post(PODMAN_URL + "/v1.40/images/prune") - self.assertEqual(prune.status_code, 200, prune.text) - prune_payload = json.loads(prune.text) - self.assertGreater(prune_payload["SpaceReclaimed"], 0) - - # FIXME need method to determine which image is going to be "pruned" to fix test - # TODO should handler be recursive when deleting images? - # self.assertIn(img["Id"], prune_payload["ImagesDeleted"][1]["Deleted"]) - - # FIXME (@vrothberg): I commented this line out during the `libimage` migration. - # It doesn't make sense to report anything to be deleted if the reclaimed space - # is zero. I think the test needs some rewrite. - # self.assertIsNotNone(prune_payload["ImagesDeleted"][1]["Deleted"]) - - def test_status_compat(self): - r = requests.post( - PODMAN_URL + "/v1.40/containers/create?name=topcontainer", - json={"Cmd": ["top"], "Image": "alpine:latest"}, - ) - self.assertEqual(r.status_code, 201, r.text) - payload = json.loads(r.text) - container_id = payload["Id"] - self.assertIsNotNone(container_id) - - r = requests.get( - PODMAN_URL + "/v1.40/containers/json", - params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, - ) - self.assertEqual(r.status_code, 200, r.text) - payload = json.loads(r.text) - self.assertEqual(payload[0]["Status"], "Created") - - r = requests.post(PODMAN_URL + f"/v1.40/containers/{container_id}/start") - self.assertEqual(r.status_code, 204, r.text) - - r = requests.get( - PODMAN_URL + "/v1.40/containers/json", - params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, - ) - self.assertEqual(r.status_code, 200, r.text) - payload = json.loads(r.text) - self.assertTrue(str(payload[0]["Status"]).startswith("Up")) - - r = requests.post(PODMAN_URL + f"/v1.40/containers/{container_id}/pause") - self.assertEqual(r.status_code, 204, r.text) - - r = requests.get( - PODMAN_URL + "/v1.40/containers/json", - params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, - ) - self.assertEqual(r.status_code, 200, r.text) - payload = json.loads(r.text) - self.assertTrue(str(payload[0]["Status"]).startswith("Up")) - self.assertTrue(str(payload[0]["Status"]).endswith("(Paused)")) - - r = requests.post(PODMAN_URL + f"/v1.40/containers/{container_id}/unpause") - self.assertEqual(r.status_code, 204, r.text) - r = requests.post(PODMAN_URL + f"/v1.40/containers/{container_id}/stop") - self.assertEqual(r.status_code, 204, r.text) - - r = requests.get( - PODMAN_URL + "/v1.40/containers/json", - params={"all": "true", "filters": f'{{"id":["{container_id}"]}}'}, - ) - self.assertEqual(r.status_code, 200, r.text) - payload = json.loads(r.text) - self.assertTrue(str(payload[0]["Status"]).startswith("Exited")) - - r = requests.delete(PODMAN_URL + f"/v1.40/containers/{container_id}") - self.assertEqual(r.status_code, 204, r.text) - - def test_pod_start_conflict(self): - """Verify issue #8865""" - - pod_name = list() - pod_name.append("Pod_" + "".join(random.choice(string.ascii_letters) for i in range(10))) - pod_name.append("Pod_" + "".join(random.choice(string.ascii_letters) for i in range(10))) - - r = requests.post( - _url("/pods/create"), - json={ - "name": pod_name[0], - "no_infra": False, - "portmappings": [{"host_ip": "127.0.0.1", "host_port": 8889, "container_port": 89}], - }, - ) - self.assertEqual(r.status_code, 201, r.text) - r = requests.post( - _url("/containers/create"), - json={ - "pod": pod_name[0], - "image": "docker.io/alpine:latest", - "command": ["top"], - }, - ) - self.assertEqual(r.status_code, 201, r.text) - - r = requests.post( - _url("/pods/create"), - json={ - "name": pod_name[1], - "no_infra": False, - "portmappings": [{"host_ip": "127.0.0.1", "host_port": 8889, "container_port": 89}], - }, - ) - self.assertEqual(r.status_code, 201, r.text) - r = requests.post( - _url("/containers/create"), - json={ - "pod": pod_name[1], - "image": "docker.io/alpine:latest", - "command": ["top"], - }, - ) - self.assertEqual(r.status_code, 201, r.text) - - r = requests.post(_url(f"/pods/{pod_name[0]}/start")) - self.assertEqual(r.status_code, 200, r.text) - - r = requests.post(_url(f"/pods/{pod_name[1]}/start")) - self.assertEqual(r.status_code, 409, r.text) - - start = json.loads(r.text) - self.assertGreater(len(start["Errs"]), 0, r.text) - - def test_manifest_409(self): - r = requests.post(_url("/manifests/create"), params={"name": "ThisIsAnInvalidImage"}) - self.assertEqual(r.status_code, 400, r.text) - - def test_df(self): - r = requests.get(_url("/system/df")) - self.assertEqual(r.status_code, 200, r.text) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go index 340ea31f3..2cf552274 100644 --- a/test/e2e/create_staticip_test.go +++ b/test/e2e/create_staticip_test.go @@ -60,8 +60,10 @@ var _ = Describe("Podman create with --ip flag", func() { }) It("Podman create with specified static IP has correct IP", func() { + // NOTE: we force the k8s-file log driver to make sure the + // tests are passing inside a container. ip := GetRandomIPAddress() - result := podmanTest.Podman([]string{"create", "--name", "test", "--ip", ip, ALPINE, "ip", "addr"}) + result := podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test", "--ip", ip, ALPINE, "ip", "addr"}) result.WaitWithDefaultTimeout() // Rootless static ip assignment without network should error if rootless.IsRootless() { @@ -83,10 +85,10 @@ var _ = Describe("Podman create with --ip flag", func() { It("Podman create two containers with the same IP", func() { SkipIfRootless("--ip not supported without network in rootless mode") ip := GetRandomIPAddress() - result := podmanTest.Podman([]string{"create", "--name", "test1", "--ip", ip, ALPINE, "sleep", "999"}) + result := podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test1", "--ip", ip, ALPINE, "sleep", "999"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) - result = podmanTest.Podman([]string{"create", "--name", "test2", "--ip", ip, ALPINE, "ip", "addr"}) + result = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test2", "--ip", ip, ALPINE, "ip", "addr"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) result = podmanTest.Podman([]string{"start", "test1"}) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 1f1786dbe..e4db6b845 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -160,9 +160,12 @@ var _ = Describe("Podman create", func() { if podmanTest.Host.Arch == "ppc64le" { Skip("skip failing test on ppc64le") } + // NOTE: we force the k8s-file log driver to make sure the + // tests are passing inside a container. + mountPath := filepath.Join(podmanTest.TempDir, "secrets") os.Mkdir(mountPath, 0755) - session := podmanTest.Podman([]string{"create", "--name", "test", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session := podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"start", "test"}) @@ -173,7 +176,7 @@ var _ = Describe("Podman create", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("/create/test rw")) - session = podmanTest.Podman([]string{"create", "--name", "test_ro", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,ro", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test_ro", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,ro", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"start", "test_ro"}) @@ -184,7 +187,7 @@ var _ = Describe("Podman create", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(session.OutputToString()).To(ContainSubstring("/create/test ro")) - session = podmanTest.Podman([]string{"create", "--name", "test_shared", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,shared", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test_shared", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test,shared", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"start", "test_shared"}) @@ -200,7 +203,7 @@ var _ = Describe("Podman create", func() { mountPath = filepath.Join(podmanTest.TempDir, "scratchpad") os.Mkdir(mountPath, 0755) - session = podmanTest.Podman([]string{"create", "--name", "test_tmpfs", "--mount", "type=tmpfs,target=/create/test", ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) + session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test_tmpfs", "--mount", "type=tmpfs,target=/create/test", ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) session = podmanTest.Podman([]string{"start", "test_tmpfs"}) diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index 0b112b312..60136bcc2 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -124,4 +124,15 @@ var _ = Describe("Podman Info", func() { } }) + It("verify ServiceIsRemote", func() { + session := podmanTest.Podman([]string{"info", "--format", "{{.Host.ServiceIsRemote}}"}) + session.WaitWithDefaultTimeout() + Expect(session).To(Exit(0)) + + if podmanTest.RemoteTest { + Expect(session.OutputToString()).To(ContainSubstring("true")) + } else { + Expect(session.OutputToString()).To(ContainSubstring("false")) + } + }) }) diff --git a/test/e2e/network_connect_disconnect_test.go b/test/e2e/network_connect_disconnect_test.go index 6974c7614..c82aacbe4 100644 --- a/test/e2e/network_connect_disconnect_test.go +++ b/test/e2e/network_connect_disconnect_test.go @@ -66,7 +66,7 @@ var _ = Describe("Podman network connect and disconnect", func() { con := podmanTest.Podman([]string{"network", "disconnect", netName, "test"}) con.WaitWithDefaultTimeout() Expect(con.ExitCode()).ToNot(BeZero()) - Expect(con.ErrorToString()).To(ContainSubstring(`network mode "slirp4netns" is not supported`)) + Expect(con.ErrorToString()).To(ContainSubstring(`"slirp4netns" is not supported: invalid network mode`)) }) It("podman network disconnect", func() { @@ -132,7 +132,7 @@ var _ = Describe("Podman network connect and disconnect", func() { con := podmanTest.Podman([]string{"network", "connect", netName, "test"}) con.WaitWithDefaultTimeout() Expect(con.ExitCode()).ToNot(BeZero()) - Expect(con.ErrorToString()).To(ContainSubstring(`network mode "slirp4netns" is not supported`)) + Expect(con.ErrorToString()).To(ContainSubstring(`"slirp4netns" is not supported: invalid network mode`)) }) It("podman connect on a container that already is connected to the network should error", func() { diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index 38f893a43..419748adb 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -88,6 +88,53 @@ var _ = Describe("Podman prune", func() { Expect(podmanTest.NumberOfContainers()).To(Equal(0)) }) + It("podman image prune - remove only dangling images", func() { + session := podmanTest.Podman([]string{"images", "-a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + hasNone, _ := session.GrepString("<none>") + Expect(hasNone).To(BeFalse()) + numImages := len(session.OutputToStringArray()) + + // Since there's no dangling image, none should be removed. + session = podmanTest.Podman([]string{"image", "prune", "-f"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(0)) + + // Let's be extra sure that the same number of images is + // reported. + session = podmanTest.Podman([]string{"images", "-a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(numImages)) + + // Now build a new image with dangling intermediate images. + podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true") + + session = podmanTest.Podman([]string{"images", "-a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + hasNone, _ = session.GrepString("<none>") + Expect(hasNone).To(BeTrue()) // ! we have dangling ones + numImages = len(session.OutputToStringArray()) + + // Since there's at least one dangling image, prune should + // remove them. + session = podmanTest.Podman([]string{"image", "prune", "-f"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + numPrunedImages := len(session.OutputToStringArray()) + Expect(numPrunedImages >= 1).To(BeTrue()) + + // Now make sure that exactly the number of pruned images has + // been removed. + session = podmanTest.Podman([]string{"images", "-a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(numImages - numPrunedImages)) + }) + It("podman image prune skip cache images", func() { podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true") diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 4c66e2823..37e837b1d 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -649,11 +649,13 @@ var _ = Describe("Podman run networking", func() { defer podmanTest.removeCNINetwork(netName) name := "nc-server" - run := podmanTest.Podman([]string{"run", "-d", "--name", name, "--net", netName, ALPINE, "nc", "-l", "-p", "8080"}) + run := podmanTest.Podman([]string{"run", "--log-driver", "k8s-file", "-d", "--name", name, "--net", netName, ALPINE, "nc", "-l", "-p", "8080"}) run.WaitWithDefaultTimeout() Expect(run.ExitCode()).To(Equal(0)) - run = podmanTest.Podman([]string{"run", "--rm", "--net", netName, "--uidmap", "0:1:4096", ALPINE, "sh", "-c", fmt.Sprintf("echo podman | nc -w 1 %s.dns.podman 8080", name)}) + // NOTE: we force the k8s-file log driver to make sure the + // tests are passing inside a container. + run = podmanTest.Podman([]string{"run", "--log-driver", "k8s-file", "--rm", "--net", netName, "--uidmap", "0:1:4096", ALPINE, "sh", "-c", fmt.Sprintf("echo podman | nc -w 1 %s.dns.podman 8080", name)}) run.WaitWithDefaultTimeout() Expect(run.ExitCode()).To(Equal(0)) diff --git a/test/e2e/run_selinux_test.go b/test/e2e/run_selinux_test.go index 6abe152a9..2886f06c1 100644 --- a/test/e2e/run_selinux_test.go +++ b/test/e2e/run_selinux_test.go @@ -343,4 +343,12 @@ var _ = Describe("Podman run", func() { session.WaitWithDefaultTimeout() Expect(session.OutputToString()).To(ContainSubstring("container_init_t")) }) + + It("podman relabels named volume with :Z", func() { + session := podmanTest.Podman([]string{"run", "-v", "testvol:/test1/test:Z", fedoraMinimal, "ls", "-alZ", "/test1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + match, _ := session.GrepString(":s0:") + Expect(match).Should(BeTrue()) + }) }) diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 59220cf01..58538b689 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -712,7 +712,7 @@ USER bin`, BB) It("podman run log-opt", func() { log := filepath.Join(podmanTest.TempDir, "/container.log") - session := podmanTest.Podman([]string{"run", "--rm", "--log-opt", fmt.Sprintf("path=%s", log), ALPINE, "ls"}) + session := podmanTest.Podman([]string{"run", "--rm", "--log-driver", "k8s-file", "--log-opt", fmt.Sprintf("path=%s", log), ALPINE, "ls"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) _, err := os.Stat(log) @@ -1669,6 +1669,49 @@ WORKDIR /madethis`, BB) Expect(session.OutputToString()).To(Equal(secretsString)) }) + It("podman run --secret mount with uid, gid, mode options", func() { + secretsString := "somesecretdata" + secretFilePath := filepath.Join(podmanTest.TempDir, "secret") + err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // check default permissions + session = podmanTest.Podman([]string{"run", "--secret", "mysecret", "--name", "secr", ALPINE, "ls", "-l", "/run/secrets/mysecret"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + output := session.OutputToString() + Expect(output).To(ContainSubstring("-r--r--r--")) + Expect(output).To(ContainSubstring("root")) + + session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=mount,uid=1000,gid=1001,mode=777", "--name", "secr2", ALPINE, "ls", "-ln", "/run/secrets/mysecret"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + output = session.OutputToString() + Expect(output).To(ContainSubstring("-rwxrwxrwx")) + Expect(output).To(ContainSubstring("1000")) + Expect(output).To(ContainSubstring("1001")) + }) + + It("podman run --secret with --user", func() { + secretsString := "somesecretdata" + secretFilePath := filepath.Join(podmanTest.TempDir, "secret") + err := ioutil.WriteFile(secretFilePath, []byte(secretsString), 0755) + Expect(err).To(BeNil()) + + session := podmanTest.Podman([]string{"secret", "create", "mysecret", secretFilePath}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "--secret", "mysecret", "--name", "nonroot", "--user", "200:200", ALPINE, "cat", "/run/secrets/mysecret"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Equal(secretsString)) + }) + It("podman run invalid secret option", func() { secretsString := "somesecretdata" secretFilePath := filepath.Join(podmanTest.TempDir, "secret") @@ -1694,6 +1737,11 @@ WORKDIR /madethis`, BB) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Not(Equal(0))) + // mount option with env type + session = podmanTest.Podman([]string{"run", "--secret", "source=mysecret,type=env,uid=1000", "--name", "secr", ALPINE, "printenv", "mysecret"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + // No source given session = podmanTest.Podman([]string{"run", "--secret", "type=env", "--name", "secr", ALPINE, "printenv", "mysecret"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/toolbox_test.go b/test/e2e/toolbox_test.go index 986f856bf..16300bebc 100644 --- a/test/e2e/toolbox_test.go +++ b/test/e2e/toolbox_test.go @@ -215,7 +215,7 @@ var _ = Describe("Toolbox-specific testing", func() { useradd := fmt.Sprintf("useradd --home-dir %s --shell %s --uid %s %s", homeDir, shell, uid, username) passwd := fmt.Sprintf("passwd --delete %s", username) - session = podmanTest.Podman([]string{"create", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", + session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", fmt.Sprintf("%s; %s; echo READY; sleep 1000", useradd, passwd)}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -250,7 +250,7 @@ var _ = Describe("Toolbox-specific testing", func() { groupadd := fmt.Sprintf("groupadd --gid %s %s", gid, groupName) - session = podmanTest.Podman([]string{"create", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", + session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", fmt.Sprintf("%s; echo READY; sleep 1000", groupadd)}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -294,7 +294,7 @@ var _ = Describe("Toolbox-specific testing", func() { usermod := fmt.Sprintf("usermod --append --groups wheel --home %s --shell %s --uid %s --gid %s %s", homeDir, shell, uid, gid, username) - session = podmanTest.Podman([]string{"create", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", + session = podmanTest.Podman([]string{"create", "--log-driver", "k8s-file", "--name", "test", "--userns=keep-id", "--user", "root:root", fedoraToolbox, "sh", "-c", fmt.Sprintf("%s; %s; %s; echo READY; sleep 1000", useradd, groupadd, usermod)}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -339,6 +339,7 @@ var _ = Describe("Toolbox-specific testing", func() { // These should be most of the switches that Toolbox uses to create a "toolbox" container // https://github.com/containers/toolbox/blob/master/src/cmd/create.go session = podmanTest.Podman([]string{"create", + "--log-driver", "k8s-file", "--dns", "none", "--hostname", "toolbox", "--ipc", "host", diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 9a136ff13..e12c32ef5 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -690,4 +690,18 @@ json-file | f run_podman rm $cid } +@test "podman run no /etc/mtab " { + tmpdir=$PODMAN_TMPDIR/build-test + mkdir -p $tmpdir + + cat >$tmpdir/Dockerfile <<EOF +FROM $IMAGE +RUN rm /etc/mtab +EOF + expected="'/etc/mtab' -> '/proc/mounts'" + run_podman build -t nomtab $tmpdir + run_podman run --rm nomtab stat -c %N /etc/mtab + is "$output" "$expected" "/etc/mtab should be created" +} + # vim: filetype=sh diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats index bac153b8e..3dd88e5eb 100644 --- a/test/system/035-logs.bats +++ b/test/system/035-logs.bats @@ -27,13 +27,22 @@ load helpers run_podman rm $cid } -@test "podman logs - multi" { +function _log_test_multi() { + local driver=$1 + skip_if_remote "logs does not support multiple containers when run remotely" + # Under k8s file, 'podman logs' returns just the facts, Ma'am. + # Under journald, there may be other cruft (e.g. container removals) + local etc= + if [[ $driver =~ journal ]]; then + etc='.*' + fi + # Simple helper to make the container starts, below, easier to read local -a cid doit() { - run_podman run --rm -d --name "$1" $IMAGE sh -c "$2"; + run_podman run --log-driver=$driver --rm -d --name "$1" $IMAGE sh -c "$2"; cid+=($(echo "${output:0:12}")) } @@ -47,24 +56,21 @@ load helpers run_podman logs -f c1 c2 is "$output" \ - "${cid[0]} a -${cid[1]} b -${cid[1]} c + "${cid[0]} a$etc +${cid[1]} b$etc +${cid[1]} c$etc ${cid[0]} d" "Sequential output from logs" } -@test "podman logs over journald" { +@test "podman logs - multi k8s-file" { + _log_test_multi k8s-file +} + +@test "podman logs - multi journald" { # We can't use journald on RHEL as rootless: rhbz#1895105 skip_if_journald_unavailable - msg=$(random_string 20) - - run_podman run --name myctr --log-driver journald $IMAGE echo $msg - - run_podman logs myctr - is "$output" "$msg" "check that log output equals the container output" - - run_podman rm myctr + _log_test_multi journald } # vim: filetype=sh diff --git a/test/system/070-build.bats b/test/system/070-build.bats index a2c8ae588..d2d56c051 100644 --- a/test/system/070-build.bats +++ b/test/system/070-build.bats @@ -393,9 +393,9 @@ Labels.$label_name | $label_value "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: \[localhost/build_test:latest]" \ + 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: \[$IMAGE]" \ + is "${lines[-1]}" ".* ID: [0-9a-f]\{12\} Size: .* Top Layer of: \[localhost/build_test:latest]" \ "image tree: last layer line" # FIXME: 'image tree --whatrequires' does not work via remote diff --git a/test/system/130-kill.bats b/test/system/130-kill.bats index 3770eac27..1b02b4976 100644 --- a/test/system/130-kill.bats +++ b/test/system/130-kill.bats @@ -8,7 +8,8 @@ load helpers @test "podman kill - test signal handling in containers" { # Start a container that will handle all signals by emitting 'got: N' local -a signals=(1 2 3 4 5 6 8 10 12 13 14 15 16 20 21 22 23 24 25 26 64) - run_podman run -d $IMAGE sh -c \ + # Force the k8s-file driver until #10323 is fixed. + run_podman run --log-driver=k8s-file -d $IMAGE sh -c \ "for i in ${signals[*]}; do trap \"echo got: \$i\" \$i; done; echo READY; while ! test -e /stop; do sleep 0.05; done; diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 94980346e..1cec50827 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -88,8 +88,9 @@ load helpers # Wait for container to restart retries=20 while :;do - run_podman '?' container inspect --format "{{.State.Pid}}" myweb - if [[ $status -eq 0 ]]; then + run_podman container inspect --format "{{.State.Pid}}" myweb + # pid is 0 as long as the container is not running + if [[ $output -ne 0 ]]; then if [[ $output == $pid ]]; then die "This should never happen! Restarted container has same PID ($output) as killed one!" fi @@ -161,6 +162,27 @@ load helpers done } +@test "podman run with slirp4ns assigns correct gateway address to host.containers.internal" { + CIDR="$(random_rfc1918_subnet)" + run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \ + $IMAGE grep 'host.containers.internal' /etc/hosts + is "$output" "${CIDR}.2 host.containers.internal" "host.containers.internal should be the cidr+2 address" +} + +@test "podman run with slirp4ns adds correct dns address to resolv.conf" { + CIDR="$(random_rfc1918_subnet)" + run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \ + $IMAGE grep "${CIDR}" /etc/resolv.conf + is "$output" "nameserver ${CIDR}.3" "resolv.conf should have slirp4netns cidr+3 as a nameserver" +} + +@test "podman run with slirp4ns assigns correct ip address container" { + CIDR="$(random_rfc1918_subnet)" + run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \ + $IMAGE sh -c "ip address | grep ${CIDR}" + is "$output" ".*inet ${CIDR}.100/24 \+" "container should have slirp4netns cidr+100 assigned to interface" +} + # "network create" now works rootless, with the help of a special container @test "podman network create" { myport=54322 @@ -214,7 +236,6 @@ load helpers @test "podman network reload" { skip_if_remote "podman network reload does not have remote support" - skip_if_rootless "podman network reload does not work rootless" random_1=$(random_string 30) HOST_PORT=12345 @@ -224,29 +245,42 @@ load helpers INDEX1=$PODMAN_TMPDIR/hello.txt echo $random_1 > $INDEX1 + # use default network for root + local netname=podman + # for rootless we have to create a custom network since there is no default network + if is_rootless; then + netname=testnet-$(random_string 10) + run_podman network create $netname + is "$output" ".*/cni/net.d/$netname.conflist" "output of 'network create'" + fi + # Bind-mount this file with a different name to a container running httpd run_podman run -d --name myweb -p "$HOST_PORT:80" \ - -v $INDEX1:/var/www/index.txt \ - -w /var/www \ - $IMAGE /bin/busybox-extras httpd -f -p 80 + --network $netname \ + -v $INDEX1:/var/www/index.txt \ + -w /var/www \ + $IMAGE /bin/busybox-extras httpd -f -p 80 cid=$output - run_podman inspect $cid --format "{{.NetworkSettings.IPAddress}}" + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").IPAddress}}" ip="$output" - run_podman inspect $cid --format "{{.NetworkSettings.MacAddress}}" + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}" mac="$output" # Verify http contents: curl from localhost run curl -s $SERVER/index.txt is "$output" "$random_1" "curl 127.0.0.1:/index.txt" - # flush the CNI iptables here - run iptables -t nat -F CNI-HOSTPORT-DNAT + # rootless cannot modify iptables + if ! is_rootless; then + # flush the CNI iptables here + run iptables -t nat -F CNI-HOSTPORT-DNAT - # check that we cannot curl (timeout after 5 sec) - run timeout 5 curl -s $SERVER/index.txt - if [ "$status" -ne 124 ]; then - die "curl did not timeout, status code: $status" + # check that we cannot curl (timeout after 5 sec) + run timeout 5 curl -s $SERVER/index.txt + if [ "$status" -ne 124 ]; then + die "curl did not timeout, status code: $status" + fi fi # reload the network to recreate the iptables rules @@ -254,9 +288,9 @@ load helpers is "$output" "$cid" "Output does not match container ID" # check that we still have the same mac and ip - run_podman inspect $cid --format "{{.NetworkSettings.IPAddress}}" + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").IPAddress}}" is "$output" "$ip" "IP address changed after podman network reload" - run_podman inspect $cid --format "{{.NetworkSettings.MacAddress}}" + run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}" is "$output" "$mac" "MAC address changed after podman network reload" # check that we can still curl @@ -274,6 +308,10 @@ load helpers # cleanup the container run_podman rm -f $cid + + if is_rootless; then + run_podman network rm -f $netname + fi } @test "podman rootless cni adds /usr/sbin to PATH" { diff --git a/test/system/700-play.bats b/test/system/700-play.bats index 8fa96741c..bcd8cf939 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -51,18 +51,40 @@ spec: seLinuxOptions: level: "s0:c1,c2" readOnlyRootFilesystem: false + volumeMounts: + - mountPath: /testdir:z + name: home-podman-testdir workingDir: / + volumes: + - hostPath: + path: TESTDIR + type: Directory + name: home-podman-testdir status: {} " +RELABEL="system_u:object_r:container_file_t:s0" + @test "podman play with stdin" { - echo "$testYaml" > $PODMAN_TMPDIR/test.yaml + TESTDIR=$PODMAN_TMPDIR/testdir + mkdir -p $TESTDIR + echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml run_podman play kube - < $PODMAN_TMPDIR/test.yaml + if [ -e /usr/sbin/selinuxenabled -a /usr/sbin/selinuxenabled ]; then + run ls -Zd $TESTDIR + is "$output" ${RELABEL} "selinux relabel should have happened" + fi run_podman pod rm -f test_pod } @test "podman play" { - echo "$testYaml" > $PODMAN_TMPDIR/test.yaml + TESTDIR=$PODMAN_TMPDIR/testdir + mkdir -p $TESTDIR + echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml run_podman play kube $PODMAN_TMPDIR/test.yaml + if [ -e /usr/sbin/selinuxenabled -a /usr/sbin/selinuxenabled ]; then + run ls -Zd $TESTDIR + is "$output" ${RELABEL} "selinux relabel should have happened" + fi run_podman pod rm -f test_pod } diff --git a/test/upgrade/test-upgrade.bats b/test/upgrade/test-upgrade.bats index dd827b398..ca478e263 100644 --- a/test/upgrade/test-upgrade.bats +++ b/test/upgrade/test-upgrade.bats @@ -109,6 +109,8 @@ podman \$opts run -d --name myrunningcontainer --label mylabel=$LABEL_RUNNING \ -w /var/www \ $IMAGE /bin/busybox-extras httpd -f -p 80 +podman \$opts pod create --name mypod + echo READY while :;do if [ -e /stop ]; then @@ -136,12 +138,18 @@ EOF # pollute it for use by old-podman. We must keep that pristine # so old-podman is the first to write to it. # + # mount /etc/containers/storage.conf to use the same storage settings as on the host + # mount /dev/shm because the container locks are stored there + # $PODMAN run -d --name podman_parent --pid=host \ --privileged \ --net=host \ --cgroupns=host \ + --pid=host \ + -v /etc/containers/storage.conf:/etc/containers/storage.conf \ -v /dev/fuse:/dev/fuse \ -v /run/crun:/run/crun \ + -v /dev/shm:/dev/shm \ -v $pmroot:$pmroot \ $OLD_PODMAN $pmroot/setup @@ -175,10 +183,11 @@ EOF run_podman ps -a \ --format '{{.Names}}--{{.Status}}--{{.Ports}}--{{.Labels.mylabel}}' \ --sort=names - is "${lines[0]}" "mycreatedcontainer--Created----$LABEL_CREATED" "created" - is "${lines[1]}" "mydonecontainer--Exited (0).*----<no value>" "done" - is "${lines[2]}" "myfailedcontainer--Exited (17) .*----$LABEL_FAILED" "fail" - is "${lines[3]}" "myrunningcontainer--Up .*----$LABEL_RUNNING" "running" + is "${lines[0]}" ".*-infra--Created----<no value>" "infra container" + is "${lines[1]}" "mycreatedcontainer--Created----$LABEL_CREATED" "created" + is "${lines[2]}" "mydonecontainer--Exited (0).*----<no value>" "done" + is "${lines[3]}" "myfailedcontainer--Exited (17) .*----$LABEL_FAILED" "fail" + is "${lines[4]}" "myrunningcontainer--Up .*----$LABEL_RUNNING" "running" # For debugging: dump containers and IDs if [[ -n "$PODMAN_UPGRADE_TEST_DEBUG" ]]; then @@ -206,9 +215,6 @@ failed | exited | 17 @test "logs" { run_podman logs mydonecontainer is "$output" "++$RANDOM_STRING_1++" "podman logs on stopped container" - -# run_podman logs myrunningcontainer -# is "$output" "READY" "podman logs on running container" } @test "exec" { @@ -226,45 +232,36 @@ failed | exited | 17 } @test "pods" { - skip "TBI" + run_podman pod inspect mypod + is "$output" ".*mypod.*" + + run_podman --cgroup-manager=cgroupfs pod start mypod + is "$output" "[0-9a-f]\\{64\\}" "podman pod start" + + run_podman pod ps + is "$output" ".*mypod.*" "podman pod ps shows name" + is "$output" ".*Running.*" "podman pod ps shows running state" + + run_podman pod stop mypod + is "$output" "[0-9a-f]\\{64\\}" "podman pod stop" + + run_podman --cgroup-manager=cgroupfs pod rm mypod + # FIXME: CI runs show this (non fatal) error: + # Error updating pod <ID> conmon cgroup PID limit: open /sys/fs/cgroup/libpod_parent/<ID>/conmon/pids.max: no such file or directory + # Investigate how to fix this (likely a race condition) + # Let's ignore the logrus messages for now + is "$output" ".*[0-9a-f]\\{64\\}" "podman pod rm" } # FIXME: commit? kill? network? pause? restart? top? volumes? What else? @test "start" { - skip "FIXME: this leaves a mount behind: root/overlay/sha/merged" run_podman --cgroup-manager=cgroupfs start -a mydonecontainer is "$output" "++$RANDOM_STRING_1++" "start on already-run container" } @test "rm a stopped container" { - # FIXME FIXME FIXME! - # - # I have no idea what's going on here. For most of my testing in this - # section, the code here was simply 'podman rm myfailedcontainer', and - # it would succeed, but then way down, in 'cleanup' below, the 'rm -f' - # step would fail: - # - # # podman rm -f podman_parent - # error freeing lock for container <sha>: no such file or directory - # ...where <sha> is the ID of the podman_parent container. - # - # I started playing with this section, by adding 'rm mydonecontainer', - # and now it always fails, the same way, but with the container we're - # removing right here: - # - # error freeing lock for container <sha>: no such file or directory - # ...where <sha> is the ID of mydonecontainer. - # - # I don't know. I give up for now, and am skip'ing the whole thing. - # If you want to play with it, try commenting out the 'myfailed' lines, - # or just the 'mydone' ones, or, I don't know. - skip "FIXME: error freeing lock for container <sha>: no such file or dir" - - # For debugging, so we can see what 'error freeing lock' refers to - run_podman ps -a - run_podman rm myfailedcontainer is "$output" "[0-9a-f]\\{64\\}" "podman rm myfailedcontainer" @@ -274,12 +271,6 @@ failed | exited | 17 @test "stop and rm" { - # About a ten-second pause, then: - # Error: timed out waiting for file /tmp/pu.nf747w/tmp/exits/<sha>: internal libpod error - # It doesn't seem to be a socket-length issue: the paths are ~80-88 chars. - # Leaving podman_parent running, and exec'ing into it, it doesn't look - # like the file is being written to the wrong place. - skip "FIXME: this doesn't work: timed out waiting for file tmpdir/exits/sha" run_podman stop myrunningcontainer run_podman rm myrunningcontainer } @@ -304,7 +295,6 @@ failed | exited | 17 run_podman logs podman_parent run_podman rm -f podman_parent - # FIXME: why does this remain mounted? umount $PODMAN_UPGRADE_WORKDIR/root/overlay || true rm -rf $PODMAN_UPGRADE_WORKDIR diff --git a/vendor/github.com/containers/common/libimage/events.go b/vendor/github.com/containers/common/libimage/events.go index bca736c7b..c7733564d 100644 --- a/vendor/github.com/containers/common/libimage/events.go +++ b/vendor/github.com/containers/common/libimage/events.go @@ -1,14 +1,18 @@ package libimage -import "time" +import ( + "time" -// EventType indicates the type of an event. Currrently, there is only one + "github.com/sirupsen/logrus" +) + +// EventType indicates the type of an event. Currently, there is only one // supported type for container image but we may add more (e.g., for manifest // lists) in the future. type EventType int const ( - // EventTypeUnknow is an unitialized EventType. + // EventTypeUnknown is an uninitialized EventType. EventTypeUnknown EventType = iota // EventTypeImagePull represents an image pull. EventTypeImagePull @@ -26,7 +30,7 @@ const ( EventTypeImageUntag // EventTypeImageMount represents an image being mounted. EventTypeImageMount - // EventTypeImageUnmounted represents an image being unmounted. + // EventTypeImageUnmount represents an image being unmounted. EventTypeImageUnmount ) @@ -41,3 +45,18 @@ type Event struct { // Type of the event. Type EventType } + +// writeEvent writes the specified event to the Runtime's event channel. The +// event is discarded if no event channel has been registered (yet). +func (r *Runtime) writeEvent(event *Event) { + select { + case r.eventChannel <- event: + // Done + case <-time.After(2 * time.Second): + // The Runtime's event channel has a buffer of size 100 which + // should be enough even under high load. However, we + // shouldn't block too long in case the buffer runs full (could + // be an honest user error or bug). + logrus.Warnf("Discarding libimage event which was not read within 2 seconds: %v", event) + } +} diff --git a/vendor/github.com/containers/common/libimage/image.go b/vendor/github.com/containers/common/libimage/image.go index 4728565bb..11abfdee7 100644 --- a/vendor/github.com/containers/common/libimage/image.go +++ b/vendor/github.com/containers/common/libimage/image.go @@ -277,6 +277,10 @@ func (i *Image) remove(ctx context.Context, rmMap map[string]*RemoveImageReport, return errors.Errorf("cannot remove read-only image %q", i.ID()) } + if i.runtime.eventChannel != nil { + i.runtime.writeEvent(&Event{ID: i.ID(), Name: referencedBy, Time: time.Now(), Type: EventTypeImageRemove}) + } + // Check if already visisted this image. report, exists := rmMap[i.ID()] if exists { @@ -423,6 +427,9 @@ func (i *Image) Tag(name string) error { } logrus.Debugf("Tagging image %s with %q", i.ID(), ref.String()) + if i.runtime.eventChannel != nil { + i.runtime.writeEvent(&Event{ID: i.ID(), Name: name, Time: time.Now(), Type: EventTypeImageTag}) + } newNames := append(i.Names(), ref.String()) if err := i.runtime.store.SetNames(i.ID(), newNames); err != nil { @@ -454,6 +461,9 @@ func (i *Image) Untag(name string) error { name = ref.String() logrus.Debugf("Untagging %q from image %s", ref.String(), i.ID()) + if i.runtime.eventChannel != nil { + i.runtime.writeEvent(&Event{ID: i.ID(), Name: name, Time: time.Now(), Type: EventTypeImageUntag}) + } removedName := false newNames := []string{} @@ -593,6 +603,10 @@ func (i *Image) RepoDigests() ([]string, error) { // are directly passed down to the containers storage. Returns the fully // evaluated path to the mount point. func (i *Image) Mount(ctx context.Context, mountOptions []string, mountLabel string) (string, error) { + if i.runtime.eventChannel != nil { + i.runtime.writeEvent(&Event{ID: i.ID(), Name: "", Time: time.Now(), Type: EventTypeImageMount}) + } + mountPoint, err := i.runtime.store.MountImage(i.ID(), mountOptions, mountLabel) if err != nil { return "", err @@ -634,6 +648,9 @@ func (i *Image) Mountpoint() (string, error) { // Unmount the image. Use force to ignore the reference counter and forcefully // unmount. func (i *Image) Unmount(force bool) error { + if i.runtime.eventChannel != nil { + i.runtime.writeEvent(&Event{ID: i.ID(), Name: "", Time: time.Now(), Type: EventTypeImageUnmount}) + } logrus.Debugf("Unmounted image %s", i.ID()) _, err := i.runtime.store.UnmountImage(i.ID(), force) return err diff --git a/vendor/github.com/containers/common/libimage/image_tree.go b/vendor/github.com/containers/common/libimage/image_tree.go index 6583a7007..b8b9cb216 100644 --- a/vendor/github.com/containers/common/libimage/image_tree.go +++ b/vendor/github.com/containers/common/libimage/image_tree.go @@ -35,36 +35,45 @@ func (i *Image) Tree(traverseChildren bool) (string, error) { fmt.Fprintf(sb, "No Image Layers") } - tree := gotree.New(sb.String()) - layerTree, err := i.runtime.layerTree() if err != nil { return "", err } - imageNode := layerTree.node(i.TopLayer()) // Traverse the entire tree down to all children. if traverseChildren { + tree := gotree.New(sb.String()) if err := imageTreeTraverseChildren(imageNode, tree); err != nil { return "", err } - } else { - // Walk all layers of the image and assemlbe their data. - for parentNode := imageNode; parentNode != nil; parentNode = parentNode.parent { - if parentNode.layer == nil { - break // we're done - } - var tags string - repoTags, err := parentNode.repoTags() - if err != nil { - return "", err - } - if len(repoTags) > 0 { - tags = fmt.Sprintf(" Top Layer of: %s", repoTags) - } - tree.Add(fmt.Sprintf("ID: %s Size: %7v%s", parentNode.layer.ID[:12], units.HumanSizeWithPrecision(float64(parentNode.layer.UncompressedSize), 4), tags)) + return tree.Print(), nil + } + + // Walk all layers of the image and assemlbe their data. Note that the + // tree is constructed in reverse order to remain backwards compatible + // with Podman. + contents := []string{} + for parentNode := imageNode; parentNode != nil; parentNode = parentNode.parent { + if parentNode.layer == nil { + break // we're done + } + var tags string + repoTags, err := parentNode.repoTags() + if err != nil { + return "", err + } + if len(repoTags) > 0 { + tags = fmt.Sprintf(" Top Layer of: %s", repoTags) } + content := fmt.Sprintf("ID: %s Size: %7v%s", parentNode.layer.ID[:12], units.HumanSizeWithPrecision(float64(parentNode.layer.UncompressedSize), 4), tags) + contents = append(contents, content) + } + contents = append(contents, sb.String()) + + tree := gotree.New(contents[len(contents)-1]) + for i := len(contents) - 2; i >= 0; i-- { + tree.Add(contents[i]) } return tree.Print(), nil @@ -80,14 +89,22 @@ func imageTreeTraverseChildren(node *layerNode, parent gotree.Tree) error { tags = fmt.Sprintf(" Top Layer of: %s", repoTags) } - newNode := parent.Add(fmt.Sprintf("ID: %s Size: %7v%s", node.layer.ID[:12], units.HumanSizeWithPrecision(float64(node.layer.UncompressedSize), 4), tags)) + content := fmt.Sprintf("ID: %s Size: %7v%s", node.layer.ID[:12], units.HumanSizeWithPrecision(float64(node.layer.UncompressedSize), 4), tags) - if len(node.children) <= 1 { - newNode = parent + var newTree gotree.Tree + if node.parent == nil || len(node.parent.children) <= 1 { + // No parent or no siblings, so we can go linear. + parent.Add(content) + newTree = parent + } else { + // Each siblings gets a new tree, so we can branch. + newTree = gotree.New(content) + parent.AddTree(newTree) } + for i := range node.children { child := node.children[i] - if err := imageTreeTraverseChildren(child, newNode); err != nil { + if err := imageTreeTraverseChildren(child, newTree); err != nil { return err } } diff --git a/vendor/github.com/containers/common/libimage/layer_tree.go b/vendor/github.com/containers/common/libimage/layer_tree.go index 7e0940339..4195b43c0 100644 --- a/vendor/github.com/containers/common/libimage/layer_tree.go +++ b/vendor/github.com/containers/common/libimage/layer_tree.go @@ -59,7 +59,7 @@ func (l *layerNode) repoTags() ([]string, error) { return nil, err } for _, tag := range repoTags { - if _, visted := visitedTags[tag]; visted { + if _, visited := visitedTags[tag]; visited { continue } visitedTags[tag] = true diff --git a/vendor/github.com/containers/common/libimage/load.go b/vendor/github.com/containers/common/libimage/load.go index c606aca5b..856813356 100644 --- a/vendor/github.com/containers/common/libimage/load.go +++ b/vendor/github.com/containers/common/libimage/load.go @@ -4,6 +4,7 @@ import ( "context" "errors" "os" + "time" dirTransport "github.com/containers/image/v5/directory" dockerArchiveTransport "github.com/containers/image/v5/docker/archive" @@ -23,6 +24,10 @@ type LoadOptions struct { func (r *Runtime) Load(ctx context.Context, path string, options *LoadOptions) ([]string, error) { logrus.Debugf("Loading image from %q", path) + if r.eventChannel != nil { + r.writeEvent(&Event{ID: "", Name: path, Time: time.Now(), Type: EventTypeImageLoad}) + } + var ( loadedImages []string loadError error diff --git a/vendor/github.com/containers/common/libimage/manifest_list.go b/vendor/github.com/containers/common/libimage/manifest_list.go index 72a2cf55f..f902db5cb 100644 --- a/vendor/github.com/containers/common/libimage/manifest_list.go +++ b/vendor/github.com/containers/common/libimage/manifest_list.go @@ -3,6 +3,7 @@ package libimage import ( "context" "fmt" + "time" "github.com/containers/common/libimage/manifests" imageCopy "github.com/containers/image/v5/copy" @@ -364,6 +365,10 @@ func (m *ManifestList) Push(ctx context.Context, destination string, options *Ma } } + if m.image.runtime.eventChannel != nil { + m.image.runtime.writeEvent(&Event{ID: m.ID(), Name: destination, Time: time.Now(), Type: EventTypeImagePush}) + } + // NOTE: we're using the logic in copier to create a proper // types.SystemContext. This prevents us from having an error prone // code duplicate here. diff --git a/vendor/github.com/containers/common/libimage/pull.go b/vendor/github.com/containers/common/libimage/pull.go index b92a5e15e..f92b4d36c 100644 --- a/vendor/github.com/containers/common/libimage/pull.go +++ b/vendor/github.com/containers/common/libimage/pull.go @@ -5,10 +5,10 @@ import ( "fmt" "io" "strings" + "time" "github.com/containers/common/pkg/config" - dirTransport "github.com/containers/image/v5/directory" - dockerTransport "github.com/containers/image/v5/docker" + registryTransport "github.com/containers/image/v5/docker" dockerArchiveTransport "github.com/containers/image/v5/docker/archive" "github.com/containers/image/v5/docker/reference" ociArchiveTransport "github.com/containers/image/v5/oci/archive" @@ -42,7 +42,7 @@ type PullOptions struct { // policies (e.g., buildah-bud versus podman-build). Making the pull-policy // choice explicit is an attempt to prevent silent regressions. // -// The errror is storage.ErrImageUnknown iff the pull policy is set to "never" +// The error is storage.ErrImageUnknown iff the pull policy is set to "never" // and no local image has been found. This allows for an easier integration // into some users of this package (e.g., Buildah). func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullPolicy, options *PullOptions) ([]*Image, error) { @@ -56,7 +56,7 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP if err != nil { // If the image clearly refers to a local one, we can look it up directly. // In fact, we need to since they are not parseable. - if strings.HasPrefix(name, "sha256:") || (len(name) == 64 && !strings.Contains(name, "/.:@")) { + if strings.HasPrefix(name, "sha256:") || (len(name) == 64 && !strings.ContainsAny(name, "/.:@")) { if pullPolicy == config.PullPolicyAlways { return nil, errors.Errorf("pull policy is always but image has been referred to by ID (%s)", name) } @@ -76,10 +76,14 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP ref = dockerRef } - if options.AllTags && ref.Transport().Name() != dockerTransport.Transport.Name() { + if options.AllTags && ref.Transport().Name() != registryTransport.Transport.Name() { return nil, errors.Errorf("pulling all tags is not supported for %s transport", ref.Transport().Name()) } + if r.eventChannel != nil { + r.writeEvent(&Event{ID: "", Name: name, Time: time.Now(), Type: EventTypeImagePull}) + } + var ( pulledImages []string pullError error @@ -88,29 +92,17 @@ func (r *Runtime) Pull(ctx context.Context, name string, pullPolicy config.PullP // Dispatch the copy operation. switch ref.Transport().Name() { - // DOCKER/REGISTRY - case dockerTransport.Transport.Name(): + // DOCKER REGISTRY + case registryTransport.Transport.Name(): pulledImages, pullError = r.copyFromRegistry(ctx, ref, strings.TrimPrefix(name, "docker://"), pullPolicy, options) // DOCKER ARCHIVE case dockerArchiveTransport.Transport.Name(): pulledImages, pullError = r.copyFromDockerArchive(ctx, ref, &options.CopyOptions) - // OCI - case ociTransport.Transport.Name(): - pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions) - - // OCI ARCHIVE - case ociArchiveTransport.Transport.Name(): - pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions) - - // DIR - case dirTransport.Transport.Name(): - pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions) - - // UNSUPPORTED + // ALL OTHER TRANSPORTS default: - return nil, errors.Errorf("unsupported transport %q for pulling", ref.Transport().Name()) + pulledImages, pullError = r.copyFromDefault(ctx, ref, &options.CopyOptions) } if pullError != nil { @@ -162,7 +154,12 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference, imageName = "sha256:" + storageName[1:] } else { storageName = manifest.Annotations["org.opencontainers.image.ref.name"] - imageName = storageName + named, err := NormalizeName(storageName) + if err != nil { + return nil, err + } + imageName = named.String() + storageName = imageName } default: @@ -275,7 +272,7 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference } named := reference.TrimNamed(ref.DockerReference()) - tags, err := dockerTransport.GetRepositoryTags(ctx, &r.systemContext, ref) + tags, err := registryTransport.GetRepositoryTags(ctx, &r.systemContext, ref) if err != nil { return nil, err } @@ -399,7 +396,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str for _, candidate := range resolved.PullCandidates { candidateString := candidate.Value.String() logrus.Debugf("Attempting to pull candidate %s for %s", candidateString, imageName) - srcRef, err := dockerTransport.NewReference(candidate.Value) + srcRef, err := registryTransport.NewReference(candidate.Value) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/common/libimage/push.go b/vendor/github.com/containers/common/libimage/push.go index 8ff5d5ffd..f1434b81d 100644 --- a/vendor/github.com/containers/common/libimage/push.go +++ b/vendor/github.com/containers/common/libimage/push.go @@ -2,6 +2,7 @@ package libimage import ( "context" + "time" dockerArchiveTransport "github.com/containers/image/v5/docker/archive" "github.com/containers/image/v5/docker/reference" @@ -61,8 +62,12 @@ func (r *Runtime) Push(ctx context.Context, source, destination string, options destRef = dockerRef } + if r.eventChannel != nil { + r.writeEvent(&Event{ID: image.ID(), Name: destination, Time: time.Now(), Type: EventTypeImagePush}) + } + // Buildah compat: Make sure to tag the destination image if it's a - // Docker archive. This way, we preseve the image name. + // Docker archive. This way, we preserve the image name. if destRef.Transport().Name() == dockerArchiveTransport.Transport.Name() { if named, err := reference.ParseNamed(resolvedSource); err == nil { tagged, isTagged := named.(reference.NamedTagged) diff --git a/vendor/github.com/containers/common/libimage/runtime.go b/vendor/github.com/containers/common/libimage/runtime.go index 4e6bd2cf2..c80e7ec7a 100644 --- a/vendor/github.com/containers/common/libimage/runtime.go +++ b/vendor/github.com/containers/common/libimage/runtime.go @@ -20,6 +20,9 @@ import ( // RuntimeOptions allow for creating a customized Runtime. type RuntimeOptions struct { + // The base system context of the runtime which will be used throughout + // the entire lifespan of the Runtime. Certain options in some + // functions may override specific fields. SystemContext *types.SystemContext } @@ -41,6 +44,8 @@ func setRegistriesConfPath(systemContext *types.SystemContext) { // Runtime is responsible for image management and storing them in a containers // storage. type Runtime struct { + // Use to send events out to users. + eventChannel chan *Event // Underlying storage store. store storage.Store // Global system context. No pointer to simplify copying and modifying @@ -55,6 +60,18 @@ func (r *Runtime) systemContextCopy() *types.SystemContext { return &sys } +// EventChannel creates a buffered channel for events that the Runtime will use +// to write events to. Callers are expected to read from the channel in a +// timely manner. +// Can be called once for a given Runtime. +func (r *Runtime) EventChannel() chan *Event { + if r.eventChannel != nil { + return r.eventChannel + } + r.eventChannel = make(chan *Event, 100) + return r.eventChannel +} + // RuntimeFromStore returns a Runtime for the specified store. func RuntimeFromStore(store storage.Store, options *RuntimeOptions) (*Runtime, error) { if options == nil { @@ -99,6 +116,9 @@ func RuntimeFromStoreOptions(runtimeOptions *RuntimeOptions, storeOptions *stora // is considered to be an error condition. func (r *Runtime) Shutdown(force bool) error { _, err := r.store.Shutdown(force) + if r.eventChannel != nil { + close(r.eventChannel) + } return err } diff --git a/vendor/github.com/containers/common/libimage/save.go b/vendor/github.com/containers/common/libimage/save.go index c03437682..c00c0107e 100644 --- a/vendor/github.com/containers/common/libimage/save.go +++ b/vendor/github.com/containers/common/libimage/save.go @@ -3,6 +3,7 @@ package libimage import ( "context" "strings" + "time" dirTransport "github.com/containers/image/v5/directory" dockerArchiveTransport "github.com/containers/image/v5/docker/archive" @@ -46,7 +47,7 @@ func (r *Runtime) Save(ctx context.Context, names []string, format, path string, // All formats support saving 1. default: if format != "docker-archive" { - return errors.Errorf("unspported format %q for saving multiple images (only docker-archive)", format) + return errors.Errorf("unsupported format %q for saving multiple images (only docker-archive)", format) } if len(options.AdditionalTags) > 0 { return errors.Errorf("cannot save multiple images with multiple tags") @@ -62,7 +63,7 @@ func (r *Runtime) Save(ctx context.Context, names []string, format, path string, return r.saveDockerArchive(ctx, names, path, options) } - return errors.Errorf("unspported format %q for saving images", format) + return errors.Errorf("unsupported format %q for saving images", format) } @@ -74,6 +75,10 @@ func (r *Runtime) saveSingleImage(ctx context.Context, name, format, path string return err } + if r.eventChannel != nil { + r.writeEvent(&Event{ID: image.ID(), Name: path, Time: time.Now(), Type: EventTypeImageSave}) + } + // Unless the image was referenced by ID, use the resolved name as a // tag. var tag string @@ -101,7 +106,7 @@ func (r *Runtime) saveSingleImage(ctx context.Context, name, format, path string options.ManifestMIMEType = manifest.DockerV2Schema2MediaType default: - return errors.Errorf("unspported format %q for saving images", format) + return errors.Errorf("unsupported format %q for saving images", format) } if err != nil { @@ -160,6 +165,9 @@ func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path st } } localImages[image.ID()] = local + if r.eventChannel != nil { + r.writeEvent(&Event{ID: image.ID(), Name: path, Time: time.Now(), Type: EventTypeImageSave}) + } } writer, err := dockerArchiveTransport.NewWriter(r.systemContextCopy(), path) diff --git a/vendor/github.com/containers/common/libimage/search.go b/vendor/github.com/containers/common/libimage/search.go index b36b6d2a3..4d1b842e7 100644 --- a/vendor/github.com/containers/common/libimage/search.go +++ b/vendor/github.com/containers/common/libimage/search.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - dockerTransport "github.com/containers/image/v5/docker" + registryTransport "github.com/containers/image/v5/docker" "github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" @@ -193,7 +193,7 @@ func (r *Runtime) searchImageInRegistry(ctx context.Context, term, registry stri return results, nil } - results, err := dockerTransport.SearchRegistry(ctx, sys, registry, term, limit) + results, err := registryTransport.SearchRegistry(ctx, sys, registry, term, limit) if err != nil { return []SearchResult{}, err } @@ -255,7 +255,7 @@ func (r *Runtime) searchImageInRegistry(ctx context.Context, term, registry stri func searchRepositoryTags(ctx context.Context, sys *types.SystemContext, registry, term string, options *SearchOptions) ([]SearchResult, error) { dockerPrefix := "docker://" imageRef, err := alltransports.ParseImageName(fmt.Sprintf("%s/%s", registry, term)) - if err == nil && imageRef.Transport().Name() != dockerTransport.Transport.Name() { + if err == nil && imageRef.Transport().Name() != registryTransport.Transport.Name() { return nil, errors.Errorf("reference %q must be a docker reference", term) } else if err != nil { imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, fmt.Sprintf("%s/%s", registry, term))) @@ -263,7 +263,7 @@ func searchRepositoryTags(ctx context.Context, sys *types.SystemContext, registr return nil, errors.Errorf("reference %q must be a docker reference", term) } } - tags, err := dockerTransport.GetRepositoryTags(ctx, sys, imageRef) + tags, err := registryTransport.GetRepositoryTags(ctx, sys, imageRef) if err != nil { return nil, errors.Errorf("error getting repository tags: %v", err) } @@ -288,18 +288,18 @@ func searchRepositoryTags(ctx context.Context, sys *types.SystemContext, registr return paramsArr, nil } -func (f *SearchFilter) matchesStarFilter(result dockerTransport.SearchResult) bool { +func (f *SearchFilter) matchesStarFilter(result registryTransport.SearchResult) bool { return result.StarCount >= f.Stars } -func (f *SearchFilter) matchesAutomatedFilter(result dockerTransport.SearchResult) bool { +func (f *SearchFilter) matchesAutomatedFilter(result registryTransport.SearchResult) bool { if f.IsAutomated != types.OptionalBoolUndefined { return result.IsAutomated == (f.IsAutomated == types.OptionalBoolTrue) } return true } -func (f *SearchFilter) matchesOfficialFilter(result dockerTransport.SearchResult) bool { +func (f *SearchFilter) matchesOfficialFilter(result registryTransport.SearchResult) bool { if f.IsOfficial != types.OptionalBoolUndefined { return result.IsOfficial == (f.IsOfficial == types.OptionalBoolTrue) } diff --git a/vendor/github.com/containers/common/pkg/config/config.go b/vendor/github.com/containers/common/pkg/config/config.go index 371dd3667..ee5957527 100644 --- a/vendor/github.com/containers/common/pkg/config/config.go +++ b/vendor/github.com/containers/common/pkg/config/config.go @@ -232,7 +232,7 @@ type EngineConfig struct { // will fall back to containers/image defaults. ImageParallelCopies uint `toml:"image_parallel_copies,omitempty"` - // ImageDefaultFormat sepecified the manifest Type (oci, v2s2, or v2s1) + // ImageDefaultFormat specified the manifest Type (oci, v2s2, or v2s1) // to use when pulling, pushing, building container images. By default // image pulled and pushed match the format of the source image. // Building/committing defaults to OCI. @@ -425,6 +425,12 @@ type NetworkConfig struct { // to attach pods to. DefaultNetwork string `toml:"default_network,omitempty"` + // DefaultSubnet is the subnet to be used for the default CNI network. + // If a network with the name given in DefaultNetwork is not present + // then a new network using this subnet will be created. + // Must be a valid IPv4 CIDR block. + DefaultSubnet string `toml:"default_subnet,omitempty"` + // NetworkConfigDir is where CNI network configuration files are stored. NetworkConfigDir string `toml:"network_config_dir,omitempty"` } diff --git a/vendor/github.com/containers/common/pkg/config/containers.conf b/vendor/github.com/containers/common/pkg/config/containers.conf index 00edd5438..f696843f5 100644 --- a/vendor/github.com/containers/common/pkg/config/containers.conf +++ b/vendor/github.com/containers/common/pkg/config/containers.conf @@ -157,7 +157,7 @@ default_sysctls = [ # Logging driver for the container. Available options: k8s-file and journald. # -# log_driver = "k8s-file" +# log_driver = "journald" # Maximum size allowed for the container log file. Negative numbers indicate # that no size limit is imposed. If positive, it must be >= 8192 to match or @@ -243,6 +243,12 @@ default_sysctls = [ # The network name of the default CNI network to attach pods to. # default_network = "podman" +# The default subnet for the default CNI network given in default_network. +# If a network with that name does not exist, a new network using that name and +# this subnet will be created. +# Must be a valid IPv4 CIDR prefix. +#default_subnet = "10.88.0.0/16" + # Path to the directory where CNI configuration files are located. # # network_config_dir = "/etc/cni/net.d/" @@ -254,7 +260,7 @@ default_sysctls = [ # Manifest Type (oci, v2s2, or v2s1) to use when pulling, pushing, building # container images. By default image pulled and pushed match the format of the -# source image. Building/commiting defaults to OCI. +# source image. Building/committing defaults to OCI. # image_default_format = "" # Cgroup management implementation used for the runtime. diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go index 34a360bf5..417776160 100644 --- a/vendor/github.com/containers/common/pkg/config/default.go +++ b/vendor/github.com/containers/common/pkg/config/default.go @@ -102,7 +102,7 @@ const ( // SystemdCgroupsManager represents systemd native cgroup manager SystemdCgroupsManager = "systemd" // DefaultLogDriver is the default type of log files - DefaultLogDriver = "k8s-file" + DefaultLogDriver = "journald" // DefaultLogSizeMax is the default value for the maximum log size // allowed for a container. Negative values mean that no limit is imposed. DefaultLogSizeMax = -1 @@ -114,6 +114,9 @@ const ( // DefaultSignaturePolicyPath is the default value for the // policy.json file. DefaultSignaturePolicyPath = "/etc/containers/policy.json" + // DefaultSubnet is the subnet that will be used for the default CNI + // network. + DefaultSubnet = "10.88.0.0/16" // DefaultRootlessSignaturePolicyPath is the location within // XDG_CONFIG_HOME of the rootless policy.json file. DefaultRootlessSignaturePolicyPath = "containers/policy.json" @@ -204,6 +207,7 @@ func DefaultConfig() (*Config, error) { }, Network: NetworkConfig{ DefaultNetwork: "podman", + DefaultSubnet: DefaultSubnet, NetworkConfigDir: cniConfig, CNIPluginDirs: cniBinDir, }, diff --git a/vendor/github.com/containers/common/pkg/filters/filters.go b/vendor/github.com/containers/common/pkg/filters/filters.go index 53f420db2..e26e056ad 100644 --- a/vendor/github.com/containers/common/pkg/filters/filters.go +++ b/vendor/github.com/containers/common/pkg/filters/filters.go @@ -96,7 +96,7 @@ func PrepareFilters(r *http.Request) (map[string][]string, error) { return filterMap, nil } -// MatchLabelFilters matches labels and returs true if they are valid +// MatchLabelFilters matches labels and returns true if they are valid func MatchLabelFilters(filterValues []string, labels map[string]string) bool { outer: for _, filterValue := range filterValues { diff --git a/vendor/github.com/containers/common/pkg/secrets/filedriver/filedriver.go b/vendor/github.com/containers/common/pkg/secrets/filedriver/filedriver.go index 37edc16be..80fcf5458 100644 --- a/vendor/github.com/containers/common/pkg/secrets/filedriver/filedriver.go +++ b/vendor/github.com/containers/common/pkg/secrets/filedriver/filedriver.go @@ -33,7 +33,7 @@ type Driver struct { func NewDriver(rootPath string) (*Driver, error) { fileDriver := new(Driver) fileDriver.secretsDataFilePath = filepath.Join(rootPath, secretsDataFile) - // the lockfile functions requre that the rootPath dir is executable + // the lockfile functions require that the rootPath dir is executable if err := os.MkdirAll(rootPath, 0700); err != nil { return nil, err } diff --git a/vendor/github.com/containers/common/pkg/secrets/secrets.go b/vendor/github.com/containers/common/pkg/secrets/secrets.go index 5e0fb3e9d..d27bb7472 100644 --- a/vendor/github.com/containers/common/pkg/secrets/secrets.go +++ b/vendor/github.com/containers/common/pkg/secrets/secrets.go @@ -99,7 +99,7 @@ func NewManager(rootPath string) (*SecretsManager, error) { if !filepath.IsAbs(rootPath) { return nil, errors.Wrapf(errInvalidPath, "path must be absolute: %s", rootPath) } - // the lockfile functions requre that the rootPath dir is executable + // the lockfile functions require that the rootPath dir is executable if err := os.MkdirAll(rootPath, 0700); err != nil { return nil, err } diff --git a/vendor/github.com/containers/common/pkg/secrets/secretsdb.go b/vendor/github.com/containers/common/pkg/secrets/secretsdb.go index 22db97c12..1395d103c 100644 --- a/vendor/github.com/containers/common/pkg/secrets/secretsdb.go +++ b/vendor/github.com/containers/common/pkg/secrets/secretsdb.go @@ -76,7 +76,7 @@ func (s *SecretsManager) getNameAndID(nameOrID string) (name, id string, err err } // ID prefix may have been given, iterate through all IDs. - // ID and partial ID has a max lenth of 25, so we return if its greater than that. + // ID and partial ID has a max length of 25, so we return if its greater than that. if len(nameOrID) > secretIDLength { return "", "", errors.Wrapf(errNoSuchSecret, "no secret with name or id %q", nameOrID) } diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index af0a1269e..df095f220 100644 --- a/vendor/github.com/containers/common/version/version.go +++ b/vendor/github.com/containers/common/version/version.go @@ -1,4 +1,4 @@ package version // Version is the version of the build. -const Version = "0.37.2-dev" +const Version = "0.38.1-dev" diff --git a/vendor/github.com/containers/storage/.cirrus.yml b/vendor/github.com/containers/storage/.cirrus.yml index 836bf659a..96714c2fc 100644 --- a/vendor/github.com/containers/storage/.cirrus.yml +++ b/vendor/github.com/containers/storage/.cirrus.yml @@ -17,15 +17,15 @@ env: #### #### Cache-image names to test with (double-quotes around names are critical) ### - FEDORA_NAME: "fedora-33" - PRIOR_FEDORA_NAME: "fedora-32" - UBUNTU_NAME: "ubuntu-2010" - PRIOR_UBUNTU_NAME: "ubuntu-2004" + FEDORA_NAME: "fedora-34" + PRIOR_FEDORA_NAME: "fedora-33" + UBUNTU_NAME: "ubuntu-2104" + PRIOR_UBUNTU_NAME: "ubuntu-2010" # GCE project where images live IMAGE_PROJECT: "libpod-218412" # VM Image built in containers/automation_images - _BUILT_IMAGE_SUFFIX: "c5744859501821952" + _BUILT_IMAGE_SUFFIX: "c6032583541653504" FEDORA_CACHE_IMAGE_NAME: "fedora-${_BUILT_IMAGE_SUFFIX}" PRIOR_FEDORA_CACHE_IMAGE_NAME: "prior-fedora-${_BUILT_IMAGE_SUFFIX}" UBUNTU_CACHE_IMAGE_NAME: "ubuntu-${_BUILT_IMAGE_SUFFIX}" @@ -112,8 +112,6 @@ ubuntu_testing_task: &ubuntu_testing - env: TEST_DRIVER: "vfs" - env: - TEST_DRIVER: "aufs" - - env: TEST_DRIVER: "overlay" diff --git a/vendor/github.com/containers/storage/VERSION b/vendor/github.com/containers/storage/VERSION index d1eaa3ba0..b0f33908d 100644 --- a/vendor/github.com/containers/storage/VERSION +++ b/vendor/github.com/containers/storage/VERSION @@ -1 +1 @@ -1.30.2 +1.30.3 diff --git a/vendor/github.com/containers/storage/go.mod b/vendor/github.com/containers/storage/go.mod index 08a54d60d..bda564681 100644 --- a/vendor/github.com/containers/storage/go.mod +++ b/vendor/github.com/containers/storage/go.mod @@ -16,9 +16,9 @@ require ( github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible github.com/moby/sys/mountinfo v0.4.1 github.com/opencontainers/go-digest v1.0.0 - github.com/opencontainers/runc v1.0.0-rc93 - github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d - github.com/opencontainers/selinux v1.8.0 + github.com/opencontainers/runc v1.0.0-rc94 + github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 + github.com/opencontainers/selinux v1.8.1 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 @@ -27,6 +27,6 @@ require ( github.com/ulikunitz/xz v0.5.10 github.com/vbatts/tar-split v0.11.1 golang.org/x/net v0.0.0-20201224014010-6772e930b67b - golang.org/x/sys v0.0.0-20210324051608-47abb6519492 + golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 gotest.tools v2.2.0+incompatible ) diff --git a/vendor/github.com/containers/storage/go.sum b/vendor/github.com/containers/storage/go.sum index dc2cbc338..692ddbb84 100644 --- a/vendor/github.com/containers/storage/go.sum +++ b/vendor/github.com/containers/storage/go.sum @@ -82,12 +82,14 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= @@ -107,6 +109,7 @@ github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -164,6 +167,7 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.1/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -205,6 +209,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= @@ -233,6 +238,7 @@ github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -350,8 +356,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -413,18 +420,22 @@ github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5X github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93 h1:x2UMpOOVf3kQ8arv/EsDGwim8PTNqzL1/EYDr/+scOM= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.0-rc94 h1:atqAFoBGp+Wkh9HKpYN3g/8NCbMzYG6SJrr+YgwamgM= +github.com/opencontainers/runc v1.0.0-rc94/go.mod h1:z+bZxa/+Tz/FmYVWkhUajJdzFeOqjc5vrqskhVyHGUM= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d h1:pNa8metDkwZjb9g4T8s+krQ+HRgZAkqnXml+wNir/+s= github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0 h1:+77ba4ar4jsCbL1GLbFL8fFM57w6suPfSS9PDLDY7KM= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.1 h1:yvEZh7CsfnJNwKzG9ZeXwbvR05RAZsu5RS/3vA6qFTA= +github.com/opencontainers/selinux v1.8.1/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -687,8 +698,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887 h1:dXfMednGJh/SUUFjTLsWJz3P+TQt9qnR11GgeI3vWKs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/doc.go b/vendor/github.com/opencontainers/selinux/go-selinux/doc.go index 9c9cbd120..0ac7d819e 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/doc.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/doc.go @@ -1,10 +1,6 @@ /* Package selinux provides a high-level interface for interacting with selinux. -This package uses a selinux build tag to enable the selinux functionality. This -allows non-linux and linux users who do not have selinux support to still use -tools that rely on this library. - Usage: import "github.com/opencontainers/selinux/go-selinux" diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go index 439455511..b3d142d8c 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/label/label_linux.go @@ -25,6 +25,8 @@ var ErrIncompatibleLabel = errors.New("Bad SELinux option z and Z can not be use // the container. A list of options can be passed into this function to alter // the labels. The labels returned will include a random MCS String, that is // guaranteed to be unique. +// If the disabled flag is passed in, the process label will not be set, but the mount label will be set +// to the container_file label with the maximum category. This label is not usable by any confined label. func InitLabels(options []string) (plabel string, mlabel string, retErr error) { if !selinux.GetEnabled() { return "", "", nil @@ -47,7 +49,8 @@ func InitLabels(options []string) (plabel string, mlabel string, retErr error) { } for _, opt := range options { if opt == "disable" { - return "", mountLabel, nil + selinux.ReleaseLabel(mountLabel) + return "", selinux.PrivContainerMountLabel(), nil } if i := strings.Index(opt, ":"); i == -1 { return "", "", errors.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type, filetype' followed by ':' and a value", opt) diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go index d9119908b..b336ebad3 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go @@ -11,9 +11,10 @@ const ( Permissive = 0 // Disabled constant to indicate SELinux is disabled Disabled = -1 - + // maxCategory is the maximum number of categories used within containers + maxCategory = 1024 // DefaultCategoryRange is the upper bound on the category range - DefaultCategoryRange = uint32(1024) + DefaultCategoryRange = uint32(maxCategory) ) var ( @@ -276,3 +277,8 @@ func DisableSecOpt() []string { func GetDefaultContextWithLevel(user, level, scon string) (string, error) { return getDefaultContextWithLevel(user, level, scon) } + +// PrivContainerMountLabel returns mount label for privileged containers +func PrivContainerMountLabel() string { + return privContainerMountLabel +} diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go index 5bfcc0490..54597398b 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go @@ -892,13 +892,13 @@ func openContextFile() (*os.File, error) { return os.Open(lxcPath) } -var labels = loadLabels() +var labels, privContainerMountLabel = loadLabels() -func loadLabels() map[string]string { +func loadLabels() (map[string]string, string) { labels := make(map[string]string) in, err := openContextFile() if err != nil { - return labels + return labels, "" } defer in.Close() @@ -920,7 +920,10 @@ func loadLabels() map[string]string { } } - return labels + con, _ := NewContext(labels["file"]) + con["level"] = fmt.Sprintf("s0:c%d,c%d", maxCategory-2, maxCategory-1) + reserveLabel(con.get()) + return labels, con.get() } // kvmContainerLabels returns the default processLabel and mountLabel to be used diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go index 70b7b7c85..b7218a0b6 100644 --- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go +++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_stub.go @@ -2,6 +2,8 @@ package selinux +const privContainerMountLabel = "" + func setDisabled() { } diff --git a/vendor/modules.txt b/vendor/modules.txt index a494d2fbe..2aa2e39fb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -90,7 +90,7 @@ github.com/containers/buildah/pkg/overlay github.com/containers/buildah/pkg/parse github.com/containers/buildah/pkg/rusage github.com/containers/buildah/util -# github.com/containers/common v0.37.2-0.20210503193405-42134aa138ce +# github.com/containers/common v0.38.1-0.20210510140555-24645399a050 github.com/containers/common/libimage github.com/containers/common/libimage/manifests github.com/containers/common/pkg/apparmor @@ -191,7 +191,7 @@ github.com/containers/psgo/internal/dev github.com/containers/psgo/internal/host github.com/containers/psgo/internal/proc github.com/containers/psgo/internal/process -# github.com/containers/storage v1.30.2 +# github.com/containers/storage v1.30.3 github.com/containers/storage github.com/containers/storage/drivers github.com/containers/storage/drivers/aufs @@ -513,7 +513,7 @@ github.com/opencontainers/runtime-tools/generate github.com/opencontainers/runtime-tools/generate/seccomp github.com/opencontainers/runtime-tools/specerror github.com/opencontainers/runtime-tools/validate -# github.com/opencontainers/selinux v1.8.0 +# github.com/opencontainers/selinux v1.8.1 github.com/opencontainers/selinux/go-selinux github.com/opencontainers/selinux/go-selinux/label github.com/opencontainers/selinux/pkg/pwalk |