diff options
95 files changed, 1509 insertions, 884 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 9daf33ba2..f951eda42 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -204,10 +204,11 @@ build_aarch64_task: only_if: "$CIRRUS_CRON != 'multiarch'" ec2_instance: &standard_build_ec2_aarch64 image: ${VM_IMAGE_NAME} - type: t4g.xlarge + type: ${EC2_INST_TYPE} region: us-east-1 architecture: arm64 # CAUTION: This has to be "arm64", not "aarch64". env: &stdenvars_aarch64 + EC2_INST_TYPE: "t4g.xlarge" DISTRO_NV: ${FEDORA_AARCH64_NAME} VM_IMAGE_NAME: ${FEDORA_AARCH64_AMI_ID} CTR_FQIN: ${FEDORA_CONTAINER_FQIN} @@ -307,6 +308,7 @@ bindings_task: only_if: >- $CIRRUS_PR != '' && $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:COPR.*' && $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' depends_on: - build @@ -414,9 +416,7 @@ alt_build_task: alias: alt_build # Don't create task for [CI:DOCS] or multiarch builds # Docs: ./contrib/cirrus/CIModes.md - only_if: ¬_docs_multiarch >- - $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && - $CIRRUS_CRON != 'multiarch' + only_if: $CIRRUS_CRON != 'multiarch' depends_on: - build env: @@ -449,7 +449,7 @@ osx_alt_build_task: name: "OSX Cross" alias: osx_alt_build # Docs: ./contrib/cirrus/CIModes.md - only_if: *not_docs_multiarch + only_if: $CIRRUS_CRON != 'multiarch' depends_on: - build env: @@ -484,6 +484,7 @@ docker-py_test_task: only_if: ¬_tag_branch_build_docs >- $CIRRUS_PR != '' && $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:COPR.*' && $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' depends_on: @@ -635,13 +636,11 @@ container_integration_test_task: matrix: &fedora_vm_axis - env: DISTRO_NV: ${FEDORA_NAME} - _BUILD_CACHE_HANDLE: ${FEDORA_NAME}-build-${CIRRUS_BUILD_ID} VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME} CTR_FQIN: ${FEDORA_CONTAINER_FQIN} CI_DESIRED_RUNTIME: crun #- env: #DISTRO_NV: ${PRIOR_FEDORA_NAME} - #_BUILD_CACHE_HANDLE: ${PRIOR_FEDORA_NAME}-build-${CIRRUS_BUILD_ID} #VM_IMAGE_NAME: ${PRIOR_FEDORA_CACHE_IMAGE_NAME} #CTR_FQIN: ${PRIOR_FEDORA_CONTAINER_FQIN} gce_instance: *standardvm @@ -695,9 +694,10 @@ podman_machine_task: - rootless_integration_test ec2_instance: image: "${VM_IMAGE_NAME}" - type: m5zn.metal # Bare-metal instance is required + type: "${EC2_INST_TYPE}" region: us-east-1 env: + EC2_INST_TYPE: "m5zn.metal" # Bare-metal instance is required TEST_FLAVOR: "machine" PRIV_NAME: "rootless" # intended use-case DISTRO_NV: "${FEDORA_NAME}" @@ -720,6 +720,7 @@ local_system_test_task: &local_system_test_task only_if: ¬_tag_build_docs_multiarch >- $CIRRUS_TAG == '' && $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:COPR.*' && $CIRRUS_CHANGE_TITLE !=~ '.*CI:BUILD.*' && $CIRRUS_CRON != 'multiarch' depends_on: @@ -785,8 +786,6 @@ rootless_remote_system_test_task: # Not used here, is used in other tasks VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME} CTR_FQIN: ${FEDORA_CONTAINER_FQIN} - # ID for re-use of build output - _BUILD_CACHE_HANDLE: ${FEDORA_NAME}-build-${CIRRUS_BUILD_ID} CI_DESIRED_RUNTIME: crun <<: *local_system_test_task alias: rootless_remote_system_test @@ -833,8 +832,6 @@ buildah_bud_test_task: # Not used here, is used in other tasks VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME} CTR_FQIN: ${FEDORA_CONTAINER_FQIN} - # ID for re-use of build output - _BUILD_CACHE_HANDLE: ${FEDORA_NAME}-build-${CIRRUS_BUILD_ID} matrix: - env: PODBIN_NAME: podman @@ -896,8 +893,6 @@ upgrade_test_task: TEST_FLAVOR: upgrade_test DISTRO_NV: ${FEDORA_NAME} VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME} - # ID for re-use of build output - _BUILD_CACHE_HANDLE: ${FEDORA_NAME}-build-${CIRRUS_BUILD_ID} clone_script: *get_gosrc setup_script: *setup main_script: *main @@ -1034,7 +1029,10 @@ artifacts_task: name: "Artifacts" alias: artifacts # Docs: ./contrib/cirrus/CIModes.md - only_if: *not_docs_multiarch + only_if: >- + $CIRRUS_CHANGE_TITLE !=~ '.*CI:DOCS.*' && + $CIRRUS_CHANGE_TITLE !=~ '.*CI:COPR.*' && + $CIRRUS_CRON != 'multiarch' depends_on: - success # This task is a secondary/convenience for downstream consumers, don't diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6ad306f0f..5946fa6a1 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -55,7 +55,7 @@ Briefly describe the problem you are having in a few paragraphs. (paste your output here) ``` -**Output of `podman info --debug`:** +**Output of `podman info`:** ``` (paste your output here) @@ -77,7 +77,12 @@ BUILDTAGS_CROSS ?= containers_image_openpgp exclude_graphdriver_btrfs exclude_gr CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker) OCI_RUNTIME ?= "" -MANPAGES_MD ?= $(wildcard docs/source/markdown/*.md) +# The 'sort' below is crucial: without it, 'make docs' behaves differently +# on the first run than on subsequent ones, because the generated .md +MANPAGES_SOURCE_DIR = docs/source/markdown +MANPAGES_MD_IN ?= $(wildcard $(MANPAGES_SOURCE_DIR)/*.md.in) +MANPAGES_MD_GENERATED ?= $(MANPAGES_MD_IN:%.md.in=%.md) +MANPAGES_MD ?= $(sort $(wildcard $(MANPAGES_SOURCE_DIR)/*.md) $(MANPAGES_MD_GENERATED)) MANPAGES ?= $(MANPAGES_MD:%.md=%) MANPAGES_DEST ?= $(subst markdown,man, $(subst source,build,$(MANPAGES))) @@ -416,17 +421,24 @@ completions: podman podman-remote pkg/api/swagger.yaml: make -C pkg/api -$(MANPAGES): %: %.md .install.md2man docdir +$(MANPAGES_MD_GENERATED): %.md: %.md.in $(MANPAGES_SOURCE_DIR)/options/*.md + hack/markdown-preprocess $< -### sed is used to filter http/s links as well as relative links -### replaces "\" at the end of a line with two spaces -### this ensures that manpages are renderd correctly +$(MANPAGES): %: %.md .install.md2man docdir - @$(SED) -e 's/\((podman[^)]*\.md\(#.*\)\?)\)//g' \ - -e 's/\[\(podman[^]]*\)\]/\1/g' \ - -e 's/\[\([^]]*\)](http[^)]\+)/\1/g' \ - -e 's;<\(/\)\?\(a\|a\s\+[^>]*\|sup\)>;;g' \ - -e 's/\\$$/ /g' $< | \ +# This does a bunch of filtering needed for man pages: +# 1. Strip markdown link targets like '[podman(1)](podman.1.md)' +# to just '[podman(1)]', because man pages have no link mechanism; +# 2. Then remove the brackets: '[podman(1)]' -> 'podman(1)'; +# 3. Then do the same for all other markdown links, +# like '[cgroups(7)](https://.....)' -> just 'cgroups(7)'; +# 4. Remove HTML-ish stuff like '<sup>..</sup>' and '<a>..</a>' +# 5. Replace "\" (backslash) at EOL with two spaces (no idea why) + @$(SED) -e 's/\((podman[^)]*\.md\(#.*\)\?)\)//g' \ + -e 's/\[\(podman[^]]*\)\]/\1/g' \ + -e 's/\[\([^]]*\)](http[^)]\+)/\1/g' \ + -e 's;<\(/\)\?\(a\|a\s\+[^>]*\|sup\)>;;g' \ + -e 's/\\$$/ /g' $< |\ $(GOMD2MAN) -in /dev/stdin -out $(subst source/markdown,build/man,$@) .PHONY: docdir diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 7d0f4d9ae..455127fd7 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -192,16 +192,14 @@ func replaceContainer(name string) error { } func CreateInit(c *cobra.Command, vals entities.ContainerCreateOptions, isInfra bool) (entities.ContainerCreateOptions, error) { - vals.UserNS = c.Flag("userns").Value.String() - // if user did not modify --userns flag and did turn on - // uid/gid mappings, set userns flag to "private" - if !c.Flag("userns").Changed && vals.UserNS == "host" { - if len(vals.UIDMap) > 0 || - len(vals.GIDMap) > 0 || - vals.SubUIDName != "" || - vals.SubGIDName != "" { - vals.UserNS = "private" + if len(vals.UIDMap) > 0 || len(vals.GIDMap) > 0 || vals.SubUIDName != "" || vals.SubGIDName != "" { + if c.Flag("userns").Changed { + return vals, errors.New("--userns and --uidmap/--gidmap/--subuidname/--subgidname are mutually exclusive") } + // force userns flag to "private" + vals.UserNS = "private" + } else { + vals.UserNS = c.Flag("userns").Value.String() } if c.Flag("kernel-memory") != nil && c.Flag("kernel-memory").Changed { logrus.Warnf("The --kernel-memory flag is no longer supported. This flag is a noop.") diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go index 5a5379389..c08b3abb6 100644 --- a/cmd/podman/containers/kill.go +++ b/cmd/podman/containers/kill.go @@ -49,7 +49,8 @@ var ( ) var ( - killOptions = entities.KillOptions{} + killOptions = entities.KillOptions{} + killCidFiles = []string{} ) func killFlags(cmd *cobra.Command) { @@ -61,7 +62,7 @@ func killFlags(cmd *cobra.Command) { flags.StringVarP(&killOptions.Signal, signalFlagName, "s", "KILL", "Signal to send to the container") _ = cmd.RegisterFlagCompletionFunc(signalFlagName, common.AutocompleteStopSignal) cidfileFlagName := "cidfile" - flags.StringArrayVar(&cidFiles, cidfileFlagName, []string{}, "Read the container ID from the file") + flags.StringArrayVar(&killCidFiles, cidfileFlagName, nil, "Read the container ID from the file") _ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault) } @@ -94,7 +95,7 @@ func kill(_ *cobra.Command, args []string) error { if sig < 1 || sig > 64 { return errors.New("valid signals are 1 through 64") } - for _, cidFile := range cidFiles { + for _, cidFile := range killCidFiles { content, err := ioutil.ReadFile(cidFile) if err != nil { return fmt.Errorf("error reading CIDFile: %w", err) diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 056e32651..1e3976389 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -52,8 +52,10 @@ var ( ) var ( - rmOptions = entities.RmOptions{} - cidFiles = []string{} + rmOptions = entities.RmOptions{ + Filters: make(map[string][]string), + } + rmCidFiles = []string{} ) func rmFlags(cmd *cobra.Command) { @@ -69,9 +71,13 @@ func rmFlags(cmd *cobra.Command) { flags.BoolVarP(&rmOptions.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container") cidfileFlagName := "cidfile" - flags.StringArrayVar(&cidFiles, cidfileFlagName, nil, "Read the container ID from the file") + flags.StringArrayVar(&rmCidFiles, cidfileFlagName, nil, "Read the container ID from the file") _ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault) + filterFlagName := "filter" + flags.StringSliceVar(&filters, filterFlagName, []string{}, "Filter output based on conditions given") + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePsFilters) + if !registry.IsRemote() { // This option is deprecated, but needs to still exists for backwards compatibility flags.Bool("storage", false, "Remove container from storage library") @@ -101,7 +107,7 @@ func rm(cmd *cobra.Command, args []string) error { } rmOptions.Timeout = &stopTimeout } - for _, cidFile := range cidFiles { + for _, cidFile := range rmCidFiles { content, err := ioutil.ReadFile(cidFile) if err != nil { return fmt.Errorf("error reading CIDFile: %w", err) @@ -110,6 +116,14 @@ func rm(cmd *cobra.Command, args []string) error { args = append(args, id) } + for _, f := range filters { + split := strings.SplitN(f, "=", 2) + if len(split) < 2 { + return fmt.Errorf("invalid filter %q", f) + } + rmOptions.Filters[split[0]] = append(rmOptions.Filters[split[0]], split[1]) + } + if rmOptions.All { logrus.Debug("--all is set: enforcing --depend=true") rmOptions.Depend = true @@ -147,7 +161,7 @@ func removeContainers(namesOrIDs []string, rmOptions entities.RmOptions, setExit } errs = append(errs, r.Err) } else { - fmt.Println(r.Id) + fmt.Println(r.RawInput) } } return errs.PrintErrors() diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 4416d1c55..061f0953d 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -59,8 +59,10 @@ func startFlags(cmd *cobra.Command) { flags.BoolVarP(&startOptions.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") flags.BoolVar(&startOptions.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)") - flags.StringSliceVarP(&filters, "filter", "f", []string{}, "Filter output based on conditions given") - _ = cmd.RegisterFlagCompletionFunc("filter", common.AutocompletePsFilters) + + filterFlagName := "filter" + flags.StringSliceVarP(&filters, filterFlagName, "f", []string{}, "Filter output based on conditions given") + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePsFilters) flags.BoolVar(&startOptions.All, "all", false, "Start all containers regardless of their state or configuration") @@ -84,7 +86,7 @@ func init() { } func validateStart(cmd *cobra.Command, args []string) error { - if len(args) == 0 && !startOptions.Latest && !startOptions.All { + if len(args) == 0 && !startOptions.Latest && !startOptions.All && len(filters) < 1 { return errors.New("start requires at least one argument") } if startOptions.All && startOptions.Latest { @@ -123,14 +125,12 @@ func start(cmd *cobra.Command, args []string) error { } containers := args - if len(filters) > 0 { - for _, f := range filters { - split := strings.SplitN(f, "=", 2) - if len(split) == 1 { - return fmt.Errorf("invalid filter %q", f) - } - startOptions.Filters[split[0]] = append(startOptions.Filters[split[0]], split[1]) + for _, f := range filters { + split := strings.SplitN(f, "=", 2) + if len(split) < 2 { + return fmt.Errorf("invalid filter %q", f) } + startOptions.Filters[split[0]] = append(startOptions.Filters[split[0]], split[1]) } responses, err := registry.ContainerEngine().ContainerStart(registry.GetContext(), containers, startOptions) diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index 9ed8e3083..7e31aa7d5 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -52,7 +52,8 @@ var ( stopOptions = entities.StopOptions{ Filters: make(map[string][]string), } - stopTimeout uint + stopCidFiles = []string{} + stopTimeout uint ) func stopFlags(cmd *cobra.Command) { @@ -62,7 +63,7 @@ func stopFlags(cmd *cobra.Command) { flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing") cidfileFlagName := "cidfile" - flags.StringArrayVar(&cidFiles, cidfileFlagName, nil, "Read the container ID from the file") + flags.StringArrayVar(&stopCidFiles, cidfileFlagName, nil, "Read the container ID from the file") _ = cmd.RegisterFlagCompletionFunc(cidfileFlagName, completion.AutocompleteDefault) timeFlagName := "time" @@ -103,7 +104,7 @@ func stop(cmd *cobra.Command, args []string) error { if cmd.Flag("time").Changed { stopOptions.Timeout = &stopTimeout } - for _, cidFile := range cidFiles { + for _, cidFile := range stopCidFiles { content, err := ioutil.ReadFile(cidFile) if err != nil { return fmt.Errorf("error reading CIDFile: %w", err) diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go index d3fd17440..4e4b001ad 100644 --- a/cmd/podman/images/rm.go +++ b/cmd/podman/images/rm.go @@ -61,6 +61,7 @@ func imageRemoveFlagSet(flags *pflag.FlagSet) { flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images") flags.BoolVarP(&imageOpts.Ignore, "ignore", "i", false, "Ignore errors if a specified image does not exist") flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image") + flags.BoolVar(&imageOpts.NoPrune, "no-prune", false, "Do not remove dangling images") } func rm(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/kube/down.go b/cmd/podman/kube/down.go new file mode 100644 index 000000000..b8c025928 --- /dev/null +++ b/cmd/podman/kube/down.go @@ -0,0 +1,39 @@ +package pods + +import ( + "github.com/containers/podman/v4/cmd/podman/common" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/spf13/cobra" +) + +var ( + downDescription = `Reads in a structured file of Kubernetes YAML. + + Removes pods that have been based on the Kubernetes kind described in the YAML.` + + downCmd = &cobra.Command{ + Use: "down KUBEFILE|-", + Short: "Remove pods based on Kubernetes YAML.", + Long: downDescription, + RunE: down, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteDefaultOneArg, + Example: `podman kube down nginx.yml + cat nginx.yml | podman kube down -`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: downCmd, + Parent: kubeCmd, + }) +} + +func down(cmd *cobra.Command, args []string) error { + reader, err := readerFromArg(args[0]) + if err != nil { + return err + } + return teardown(reader) +} diff --git a/cmd/podman/kube/play.go b/cmd/podman/kube/play.go index 685cb521c..4811bcf4b 100644 --- a/cmd/podman/kube/play.go +++ b/cmd/podman/kube/play.go @@ -1,8 +1,10 @@ package pods import ( + "bytes" "errors" "fmt" + "io" "net" "os" "strings" @@ -37,9 +39,9 @@ var ( // https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/ defaultSeccompRoot = "/var/lib/kubelet/seccomp" playOptions = playKubeOptionsWrapper{} - playDescription = `Command reads in a structured file of Kubernetes YAML. + playDescription = `Reads in a structured file of Kubernetes YAML. - It creates pods or volumes based on the Kubernetes kind described in the YAML. Supported kinds are Pods, Deployments and PersistentVolumeClaims.` + Creates pods or volumes based on the Kubernetes kind described in the YAML. Supported kinds are Pods, Deployments and PersistentVolumeClaims.` playCmd = &cobra.Command{ Use: "play [options] KUBEFILE|-", @@ -139,6 +141,7 @@ func playFlags(cmd *cobra.Command) { downFlagName := "down" flags.BoolVar(&playOptions.Down, downFlagName, false, "Stop pods defined in the YAML file") + _ = flags.MarkHidden("down") replaceFlagName := "replace" flags.BoolVar(&playOptions.Replace, replaceFlagName, false, "Delete and recreate pods defined in the YAML file") @@ -223,10 +226,6 @@ func Play(cmd *cobra.Command, args []string) error { } playOptions.Annotations[splitN[0]] = annotation } - yamlfile := args[0] - if yamlfile == "-" { - yamlfile = "/dev/stdin" - } for _, mac := range playOptions.macs { m, err := net.ParseMAC(mac) @@ -235,36 +234,62 @@ func Play(cmd *cobra.Command, args []string) error { } playOptions.StaticMACs = append(playOptions.StaticMACs, m) } + + reader, err := readerFromArg(args[0]) + if err != nil { + return err + } + if playOptions.Down { - return teardown(yamlfile) + return teardown(reader) } + if playOptions.Replace { - if err := teardown(yamlfile); err != nil && !errorhandling.Contains(err, define.ErrNoSuchPod) { + if err := teardown(reader); err != nil && !errorhandling.Contains(err, define.ErrNoSuchPod) { + return err + } + if _, err := reader.Seek(0, 0); err != nil { return err } } - return kubeplay(yamlfile) + return kubeplay(reader) } func playKube(cmd *cobra.Command, args []string) error { return Play(cmd, args) } -func teardown(yamlfile string) error { +func readerFromArg(fileName string) (*bytes.Reader, error) { + if fileName == "-" { // Read from stdin + data, err := io.ReadAll(os.Stdin) + if err != nil { + return nil, err + } + return bytes.NewReader(data), nil + } + f, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer f.Close() + + data, err := io.ReadAll(f) + if err != nil { + return nil, err + } + return bytes.NewReader(data), nil +} + +func teardown(body io.Reader) error { var ( podStopErrors utils.OutputErrors podRmErrors utils.OutputErrors ) options := new(entities.PlayKubeDownOptions) - f, err := os.Open(yamlfile) + reports, err := registry.ContainerEngine().PlayKubeDown(registry.GetContext(), body, *options) if err != nil { return err } - defer f.Close() - reports, err := registry.ContainerEngine().PlayKubeDown(registry.GetContext(), f, *options) - if err != nil { - return fmt.Errorf("%v: %w", yamlfile, err) - } // Output stopped pods fmt.Println("Pods stopped:") @@ -290,19 +315,15 @@ func teardown(yamlfile string) error { podRmErrors = append(podRmErrors, removed.Err) } } + return podRmErrors.PrintErrors() } -func kubeplay(yamlfile string) error { - f, err := os.Open(yamlfile) +func kubeplay(body io.Reader) error { + report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), body, playOptions.PlayKubeOptions) if err != nil { return err } - defer f.Close() - report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), f, playOptions.PlayKubeOptions) - if err != nil { - return fmt.Errorf("%s: %w", yamlfile, err) - } // Print volumes report for i, volume := range report.Volumes { if i == 0 { diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index f8fd946cd..296fa4def 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -63,6 +63,7 @@ func infoFlags(cmd *cobra.Command) { flags := cmd.Flags() flags.BoolVarP(&debug, "debug", "D", false, "Display additional debug information") + _ = flags.MarkHidden("debug") // It's a NOP since Podman version 2.0 formatFlagName := "format" flags.StringVarP(&inFormat, formatFlagName, "f", "", "Change the output format to JSON or a Go template") diff --git a/contrib/cirrus/CIModes.md b/contrib/cirrus/CIModes.md index c782ca64b..0b5a189a6 100644 --- a/contrib/cirrus/CIModes.md +++ b/contrib/cirrus/CIModes.md @@ -85,6 +85,16 @@ of this document, it's not possible to override the behavior of `$CIRRUS_PR`. + meta + success +### Intended `[CI:COPR]` PR Tasks: ++ ext_svc_check ++ automation ++ *build* ++ validate ++ swagger ++ consistency ++ meta ++ success + ### Intend `[CI:BUILD]` PR Tasks: + ext_svc_check + automation diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index c3b7811bc..f84f78ee9 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -277,7 +277,7 @@ case "$TEST_FLAVOR" in ;& # continue with next item compose) make install.tools - rpm -ivh $PACKAGE_DOWNLOAD_DIR/podman-docker* + dnf install -y $PACKAGE_DOWNLOAD_DIR/podman-docker* ;& # continue with next item int) ;& sys) ;& @@ -307,7 +307,7 @@ case "$TEST_FLAVOR" in install_test_configs ;; machine) - rpm -ivh $PACKAGE_DOWNLOAD_DIR/podman-gvproxy* + dnf install -y $PACKAGE_DOWNLOAD_DIR/podman-gvproxy* remove_packaged_podman_files make install.tools make install PREFIX=/usr ETCDIR=/etc diff --git a/contrib/pkginstaller/Makefile b/contrib/pkginstaller/Makefile index 19c9b51aa..c84a08482 100644 --- a/contrib/pkginstaller/Makefile +++ b/contrib/pkginstaller/Makefile @@ -9,14 +9,15 @@ QEMU_RELEASE_URL ?= https://github.com/containers/podman-machine-qemu/releases/d PACKAGE_DIR ?= out/packaging TMP_DOWNLOAD ?= tmp-download PACKAGE_ROOT ?= root +PKG_NAME := podman-installer-macos-$(ARCH).pkg default: pkginstaller -get_gvproxy: +$(TMP_DOWNLOAD)/gvproxy: mkdir -p $(TMP_DOWNLOAD) cd $(TMP_DOWNLOAD) && curl -sLo gvproxy $(GVPROXY_RELEASE_URL) -get_qemu: +$(TMP_DOWNLOAD)/podman-machine-qemu-$(ARCH)-$(QEMU_VERSION).tar.xz: mkdir -p $(TMP_DOWNLOAD) cd $(TMP_DOWNLOAD) && curl -sLO $(QEMU_RELEASE_URL) @@ -32,8 +33,9 @@ packagedir: package_root Distribution welcome.html echo -n $(PODMAN_VERSION) > $(PACKAGE_DIR)/VERSION echo -n $(ARCH) > $(PACKAGE_DIR)/ARCH cp ../../LICENSE $(PACKAGE_DIR)/Resources/LICENSE.txt + cp hvf.entitlements $(PACKAGE_DIR)/ -package_root: get_gvproxy get_qemu +package_root: clean-pkgroot $(TMP_DOWNLOAD)/podman-machine-qemu-$(ARCH)-$(QEMU_VERSION).tar.xz $(TMP_DOWNLOAD)/gvproxy mkdir -p $(PACKAGE_ROOT)/podman/bin $(PACKAGE_ROOT)/podman/qemu tar -C $(PACKAGE_ROOT)/podman/qemu -xf $(TMP_DOWNLOAD)/podman-machine-qemu-$(ARCH)-$(QEMU_VERSION).tar.xz cp $(TMP_DOWNLOAD)/gvproxy $(PACKAGE_ROOT)/podman/bin/ @@ -45,6 +47,15 @@ package_root: get_gvproxy get_qemu pkginstaller: packagedir cd $(PACKAGE_DIR) && ./package.sh .. -.PHONY: clean +_notarize: pkginstaller + xcrun notarytool submit --apple-id $(NOTARIZE_USERNAME) --password $(NOTARIZE_PASSWORD) --team-id=$(NOTARIZE_TEAM) -f json --wait out/$(PKG_NAME) + +notarize: _notarize + xcrun stapler staple out/$(PKG_NAME) + +.PHONY: clean clean-pkgroot clean: rm -rf $(TMP_DOWNLOAD) $(PACKAGE_ROOT) $(PACKAGE_DIR) Distribution welcome.html + +clean-pkgroot: + rm -rf $(PACKAGE_ROOT) $(PACKAGE_DIR) Distribution welcome.html diff --git a/contrib/pkginstaller/README.md b/contrib/pkginstaller/README.md index 37c59ce04..7aaf64808 100644 --- a/contrib/pkginstaller/README.md +++ b/contrib/pkginstaller/README.md @@ -5,10 +5,13 @@ $ make ARCH=<amd64 | aarch64> NO_CODESIGN=1 pkginstaller # or to create signed pkg $ make ARCH=<amd64 | aarch64> CODESIGN_IDENTITY=<ID> PRODUCTSIGN_IDENTITY=<ID> pkginstaller + +# or to prepare a signed and notarized pkg for release +$ make ARCH=<amd64 | aarch64> CODESIGN_IDENTITY=<ID> PRODUCTSIGN_IDENTITY=<ID> NOTARIZE_USERNAME=<appleID> NOTARIZE_PASSWORD=<appleID-password> NOTARIZE_TEAM=<team-id> notarize ``` The generated pkg will be written to `out/podman-macos-installer-*.pkg`. -Currently the pkg installs `podman`, `qemu`, `gvproxy` and `podman-mac-helper` to `/Applications/podman` +Currently the pkg installs `podman`, `qemu`, `gvproxy` and `podman-mac-helper` to `/opt/podman` The `qemu` build it uses is from [containers/podman-machine-qemu](https://github.com/containers/podman-machine-qemu) diff --git a/contrib/pkginstaller/hvf.entitlements b/contrib/pkginstaller/hvf.entitlements new file mode 100644 index 000000000..154f3308e --- /dev/null +++ b/contrib/pkginstaller/hvf.entitlements @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.security.hypervisor</key> + <true/> +</dict> +</plist> diff --git a/contrib/pkginstaller/package.sh b/contrib/pkginstaller/package.sh index b7b33954d..f6f7cef16 100755 --- a/contrib/pkginstaller/package.sh +++ b/contrib/pkginstaller/package.sh @@ -10,11 +10,19 @@ NO_CODESIGN=${NO_CODESIGN:-0} HELPER_BINARIES_DIR="/opt/podman/qemu/bin" binDir="${BASEDIR}/root/podman/bin" +qemuBinDir="${BASEDIR}/root/podman/qemu/bin" + +version=$(cat "${BASEDIR}/VERSION") +arch=$(cat "${BASEDIR}/ARCH") function build_podman() { pushd "$1" - make podman-remote HELPER_BINARIES_DIR="${HELPER_BINARIES_DIR}" - make podman-mac-helper + local goArch="${arch}" + if [ "${goArch}" = aarch64 ]; then + goArch=arm64 + fi + make GOARCH="${goArch}" podman-remote HELPER_BINARIES_DIR="${HELPER_BINARIES_DIR}" + make GOARCH="${goArch}" podman-mac-helper cp bin/darwin/podman "contrib/pkginstaller/out/packaging/${binDir}/podman" cp bin/darwin/podman-mac-helper "contrib/pkginstaller/out/packaging/${binDir}/podman-mac-helper" popd @@ -29,16 +37,40 @@ function sign() { if [ -f "${entitlements}" ]; then opts="--entitlements ${entitlements}" fi - codesign --deep --sign "${CODESIGN_IDENTITY}" --options runtime --force --timestamp "${opts}" "$1" + codesign --deep --sign "${CODESIGN_IDENTITY}" --options runtime --timestamp --force ${opts} "$1" } -version=$(cat "${BASEDIR}/VERSION") -arch=$(cat "${BASEDIR}/ARCH") +function signQemu() { + if [ "${NO_CODESIGN}" -eq "1" ]; then + return + fi + + local qemuArch="${arch}" + if [ "${qemuArch}" = amd64 ]; then + qemuArch=x86_64 + fi + + # sign the files inside /opt/podman/qemu/lib + libs=$(find "${BASEDIR}"/root/podman/qemu/lib -depth -name "*.dylib" -or -type f -perm +111) + echo "${libs}" | xargs -t -I % codesign --deep --sign "${CODESIGN_IDENTITY}" --options runtime --timestamp --force % || true + + # sign the files inside /opt/podman/qemu/bin except qemu-system-* + bins=$(find "${BASEDIR}"/root/podman/qemu/bin -depth -type f -perm +111 ! -name "qemu-system-${qemuArch}") + echo "${bins}" | xargs -t -I % codesign --deep --sign "${CODESIGN_IDENTITY}" --options runtime --timestamp --force % || true + + # sign the qemu-system-* binary + # need to remove any extended attributes, otherwise codesign complains: + # qemu-system-aarch64: resource fork, Finder information, or similar detritus not allowed + xattr -cr "${qemuBinDir}/qemu-system-${qemuArch}" + codesign --deep --sign "${CODESIGN_IDENTITY}" --options runtime --timestamp --force \ + --entitlements "${BASEDIR}/hvf.entitlements" "${qemuBinDir}/qemu-system-${qemuArch}" +} build_podman "../../../../" sign "${binDir}/podman" sign "${binDir}/gvproxy" sign "${binDir}/podman-mac-helper" +signQemu pkgbuild --identifier com.redhat.podman --version "${version}" \ --scripts "${BASEDIR}/scripts" \ diff --git a/docs/Makefile b/docs/Makefile index fb67e266c..c9192f5e6 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -13,7 +13,8 @@ help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) clean: - rm -fr build/ + $(RM) -fr build + cd source/markdown && $(RM) -f $$(<.gitignore) .PHONY: help Makefile diff --git a/docs/source/conf.py b/docs/source/conf.py index 505bcbec7..b58bb3f46 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,6 +15,19 @@ # sys.path.insert(0, os.path.abspath('.')) import re +import os +import subprocess + +# We have to run the preprocessor to create the actual markdown files from .in files. +# Do it here so the it can work on readthedocs as well. +path = os.path.join(os.path.abspath(os.path.dirname( + __file__)), "../../hack/markdown-preprocess") +p = subprocess.Popen(path, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) +out, err = p.communicate() +if p.returncode != 0: + raise Exception("failed to run markdown-preprocess", out, err) + # -- Project information ----------------------------------------------------- diff --git a/docs/source/markdown/.gitignore b/docs/source/markdown/.gitignore new file mode 100644 index 000000000..c441d74c5 --- /dev/null +++ b/docs/source/markdown/.gitignore @@ -0,0 +1,2 @@ +podman-create.1.md +podman-run.1.md diff --git a/docs/source/markdown/options/cgroup-conf.md b/docs/source/markdown/options/cgroup-conf.md new file mode 100644 index 000000000..91d724ab6 --- /dev/null +++ b/docs/source/markdown/options/cgroup-conf.md @@ -0,0 +1,3 @@ +#### **--cgroup-conf**=*KEY=VALUE* + +When running on cgroup v2, specify the cgroup file to write to and its value. For example **--cgroup-conf=memory.high=1073741824** sets the memory.high limit to 1GB. diff --git a/docs/source/markdown/options/chrootdirs.md b/docs/source/markdown/options/chrootdirs.md new file mode 100644 index 000000000..624a10624 --- /dev/null +++ b/docs/source/markdown/options/chrootdirs.md @@ -0,0 +1,5 @@ +#### **--chrootdirs**=*path* + +Path to a directory inside the container that should be treated as a `chroot` directory. +Any Podman managed file (e.g., /etc/resolv.conf, /etc/hosts, etc/hostname) that is mounted into the root directory will be mounted into that location as well. +Multiple directories should be separated with a comma. diff --git a/docs/source/markdown/options/env-host.md b/docs/source/markdown/options/env-host.md new file mode 100644 index 000000000..665fca016 --- /dev/null +++ b/docs/source/markdown/options/env-host.md @@ -0,0 +1,3 @@ +#### **--env-host** + +Use host environment inside of the container. See **Environment** note below for precedence. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) diff --git a/docs/source/markdown/options/group-add.md b/docs/source/markdown/options/group-add.md new file mode 100644 index 000000000..ac9ade3a7 --- /dev/null +++ b/docs/source/markdown/options/group-add.md @@ -0,0 +1,11 @@ +#### **--group-add**=*group* | *keep-groups* + +Assign additional groups to the primary user running within the container process. + +- `keep-groups` is a special flag that tells Podman to keep the supplementary group access. + +Allows container to use the user's supplementary group access. If file systems or +devices are only accessible by the rootless user's group, this flag tells the OCI +runtime to pass the group access into the container. Currently only available +with the `crun` OCI runtime. Note: `keep-groups` is exclusive, you cannot add any other groups +with this flag. (Not available for remote commands, including Mac and Windows (excluding WSL2) machines) diff --git a/docs/source/markdown/options/hostuser.md b/docs/source/markdown/options/hostuser.md new file mode 100644 index 000000000..d1d12c88f --- /dev/null +++ b/docs/source/markdown/options/hostuser.md @@ -0,0 +1,4 @@ +#### **--hostuser**=*name* + +Add a user account to /etc/passwd from the host to the container. The Username +or UID must exist on the host system. diff --git a/docs/source/markdown/options/image-volume.md b/docs/source/markdown/options/image-volume.md new file mode 100644 index 000000000..2a549ef3c --- /dev/null +++ b/docs/source/markdown/options/image-volume.md @@ -0,0 +1,8 @@ +#### **--image-volume**=**bind** | *tmpfs* | *ignore* + +Tells Podman how to handle the builtin image volumes. Default is **bind**. + +- **bind**: An anonymous named volume will be created and mounted into the container. +- **tmpfs**: The volume is mounted onto the container as a tmpfs, which allows the users to create +content that disappears when the container is stopped. +- **ignore**: All volumes are just ignored and no action is taken. diff --git a/docs/source/markdown/options/init-path.md b/docs/source/markdown/options/init-path.md new file mode 100644 index 000000000..c2be27874 --- /dev/null +++ b/docs/source/markdown/options/init-path.md @@ -0,0 +1,3 @@ +#### **--init-path**=*path* + +Path to the container-init binary. diff --git a/docs/source/markdown/options/init.md b/docs/source/markdown/options/init.md new file mode 100644 index 000000000..caf300efe --- /dev/null +++ b/docs/source/markdown/options/init.md @@ -0,0 +1,5 @@ +#### **--init** + +Run an init inside the container that forwards signals and reaps processes. +The container-init binary is mounted at `/run/podman-init`. +Mounting over `/run` will hence break container execution. diff --git a/docs/source/markdown/options/mount.md b/docs/source/markdown/options/mount.md new file mode 100644 index 000000000..e81af539d --- /dev/null +++ b/docs/source/markdown/options/mount.md @@ -0,0 +1,77 @@ +#### **--mount**=*type=TYPE,TYPE-SPECIFIC-OPTION[,...]* + +Attach a filesystem mount to the container + +Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and **devpts**. <sup>[[1]](#Footnote1)</sup> + + e.g. + + type=bind,source=/path/on/host,destination=/path/in/container + + type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared + + type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true + + type=volume,source=vol1,destination=/path/in/container,ro=true + + type=tmpfs,tmpfs-size=512M,destination=/path/in/container + + type=image,source=fedora,destination=/fedora-image,rw=true + + type=devpts,destination=/dev/pts + + Common Options: + + · src, source: mount source spec for bind and volume. Mandatory for bind. + + · dst, destination, target: mount destination spec. + + Options specific to volume: + + · ro, readonly: true or false (default). + + . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + + · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. + + Options specific to image: + + · rw, readwrite: true or false (default). + + Options specific to bind: + + · ro, readonly: true or false (default). + + · bind-propagation: shared, slave, private, unbindable, rshared, rslave, runbindable, or rprivate(default). See also mount(2). + + . bind-nonrecursive: do not set up a recursive bind mount. By default it is recursive. + + . relabel: shared, private. + + · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. + + . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + + Options specific to tmpfs: + + · ro, readonly: true or false (default). + + · tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux. + + · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux. + + · tmpcopyup: Enable copyup from the image directory at the same location to the tmpfs. Used by default. + + · notmpcopyup: Disable copying files from the image to the tmpfs. + + . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. + + Options specific to devpts: + + · uid: UID of the file owner (default 0). + + · gid: GID of the file owner (default 0). + + · mode: permission mask for the file (default 600). + + · max: maximum number of PTYs (default 1048576). diff --git a/docs/source/markdown/options/no-healthcheck.md b/docs/source/markdown/options/no-healthcheck.md new file mode 100644 index 000000000..a722ac5b5 --- /dev/null +++ b/docs/source/markdown/options/no-healthcheck.md @@ -0,0 +1,3 @@ +#### **--no-healthcheck** + +Disable any defined healthchecks for container. diff --git a/docs/source/markdown/options/oom-kill-disable.md b/docs/source/markdown/options/oom-kill-disable.md new file mode 100644 index 000000000..24ed9a889 --- /dev/null +++ b/docs/source/markdown/options/oom-kill-disable.md @@ -0,0 +1,5 @@ +#### **--oom-kill-disable** + +Whether to disable OOM Killer for the container or not. + +This flag is not supported on cgroups V2 systems. diff --git a/docs/source/markdown/options/passwd-entry.md b/docs/source/markdown/options/passwd-entry.md new file mode 100644 index 000000000..33c179d12 --- /dev/null +++ b/docs/source/markdown/options/passwd-entry.md @@ -0,0 +1,5 @@ +#### **--passwd-entry**=*ENTRY* + +Customize the entry that is written to the `/etc/passwd` file within the container when `--passwd` is used. + +The variables $USERNAME, $UID, $GID, $NAME, $HOME are automatically replaced with their value at runtime. diff --git a/docs/source/markdown/options/personality.md b/docs/source/markdown/options/personality.md new file mode 100644 index 000000000..663f14782 --- /dev/null +++ b/docs/source/markdown/options/personality.md @@ -0,0 +1,3 @@ +#### **--personality**=*persona* + +Personality sets the execution domain via Linux personality(2). diff --git a/docs/source/markdown/options/pidfile.md b/docs/source/markdown/options/pidfile.md new file mode 100644 index 000000000..a494b522e --- /dev/null +++ b/docs/source/markdown/options/pidfile.md @@ -0,0 +1,9 @@ +#### **--pidfile**=*path* + +When the pidfile location is specified, the container process' PID will be written to the pidfile. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) +If the pidfile option is not specified, the container process' PID will be written to /run/containers/storage/${storage-driver}-containers/$CID/userdata/pidfile. + +After the container is started, the location for the pidfile can be discovered with the following `podman inspect` command: + + $ podman inspect --format '{{ .PidFile }}' $CID + /run/containers/storage/${storage-driver}-containers/$CID/userdata/pidfile diff --git a/docs/source/markdown/options/sdnotify.md b/docs/source/markdown/options/sdnotify.md new file mode 100644 index 000000000..d090cbf7c --- /dev/null +++ b/docs/source/markdown/options/sdnotify.md @@ -0,0 +1,10 @@ +#### **--sdnotify**=**container** | *conmon* | *ignore* + +Determines how to use the NOTIFY_SOCKET, as passed with systemd and Type=notify. + +Default is **container**, which means allow the OCI runtime to proxy the socket into the +container to receive ready notification. Podman will set the MAINPID to conmon's pid. +The **conmon** option sets MAINPID to conmon's pid, and sends READY when the container +has started. The socket is never passed to the runtime or the container. +The **ignore** option removes NOTIFY_SOCKET from the environment for itself and child processes, +for the case where some other process above Podman uses NOTIFY_SOCKET and Podman should not use it. diff --git a/docs/source/markdown/options/seccomp-policy.md b/docs/source/markdown/options/seccomp-policy.md new file mode 100644 index 000000000..3b2eb7553 --- /dev/null +++ b/docs/source/markdown/options/seccomp-policy.md @@ -0,0 +1,5 @@ +#### **--seccomp-policy**=*policy* + +Specify the policy to select the seccomp profile. If set to *image*, Podman will look for a "io.containers.seccomp.profile" label in the container-image config and use its value as a seccomp profile. Otherwise, Podman will follow the *default* policy by applying the default profile unless specified otherwise via *--security-opt seccomp* as described below. + +Note that this feature is experimental and may change in the future. diff --git a/docs/source/markdown/options/timeout.md b/docs/source/markdown/options/timeout.md new file mode 100644 index 000000000..731feb973 --- /dev/null +++ b/docs/source/markdown/options/timeout.md @@ -0,0 +1,5 @@ +#### **--timeout**=*seconds* + +Maximum time a container is allowed to run before conmon sends it the kill +signal. By default containers will run until they exit or are stopped by +`podman stop`. diff --git a/docs/source/markdown/options/tz.md b/docs/source/markdown/options/tz.md new file mode 100644 index 000000000..0442e8a76 --- /dev/null +++ b/docs/source/markdown/options/tz.md @@ -0,0 +1,4 @@ +#### **--tz**=*timezone* + +Set timezone in container. This flag takes area-based timezones, GMT time, as well as `local`, which sets the timezone in the container to match the host machine. See `/usr/share/zoneinfo/` for valid timezones. +Remote connections use local containers.conf for defaults diff --git a/docs/source/markdown/options/umask.md b/docs/source/markdown/options/umask.md new file mode 100644 index 000000000..55668b6da --- /dev/null +++ b/docs/source/markdown/options/umask.md @@ -0,0 +1,4 @@ +#### **--umask**=*umask* + +Set the umask inside the container. Defaults to `0022`. +Remote connections use local containers.conf for defaults diff --git a/docs/source/markdown/options/unsetenv-all.md b/docs/source/markdown/options/unsetenv-all.md new file mode 100644 index 000000000..3aad2e805 --- /dev/null +++ b/docs/source/markdown/options/unsetenv-all.md @@ -0,0 +1,5 @@ +#### **--unsetenv-all** + +Unset all default environment variables for the container. Default environment +variables include variables provided natively by Podman, environment variables +configured by the image, and environment variables from containers.conf. diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md.in index b3e707e45..008c3c18f 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md.in @@ -115,9 +115,7 @@ Add Linux capabilities Drop Linux capabilities -#### **--cgroup-conf**=*KEY=VALUE* - -When running on cgroup v2, specify the cgroup file to write to and its value. For example **--cgroup-conf=memory.high=1073741824** sets the memory.high limit to 1GB. +@@option cgroup-conf #### **--cgroup-parent**=*path* @@ -143,11 +141,7 @@ The *disabled* option will force the container to not create CGroups, and thus c The *no-conmon* option disables a new CGroup only for the conmon process. The *split* option splits the current cgroup in two sub-cgroups: one for conmon and one for the container payload. It is not possible to set *--cgroup-parent* with *split*. -#### **--chrootdirs**=*path* - -Path to a directory inside the container that should be treated as a `chroot` directory. -Any Podman managed file (e.g., /etc/resolv.conf, /etc/hosts, etc/hostname) that is mounted into the root directory will be mounted into that location as well. -Multiple directories should be separated with a comma. +@@option chrootdirs #### **--cidfile**=*id* @@ -358,9 +352,7 @@ See [**Environment**](#environment) note below for precedence and examples. Read in a line delimited file of environment variables. See **Environment** note below for precedence. -#### **--env-host** - -Use host environment inside of the container. See **Environment** note below for precedence. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) +@@option env-host #### **--expose**=*port* @@ -376,17 +368,7 @@ __--uidmap__ maps host UIDs to container UIDs. For details see __--uidmap__. Note: the **--gidmap** flag cannot be called in conjunction with the **--pod** flag as a gidmap cannot be set on the container level when in a pod. -#### **--group-add**=*group* | *keep-groups* - -Assign additional groups to the primary user running within the container process. - -- `keep-groups` is a special flag that tells Podman to keep the supplementary group access. - -Allows container to use the user's supplementary group access. If file systems or -devices are only accessible by the rootless user's group, this flag tells the OCI -runtime to pass the group access into the container. Currently only available -with the `crun` OCI runtime. Note: `keep-groups` is exclusive, you cannot add any other groups -with this flag. (Not available for remote commands, including Mac and Windows (excluding WSL2) machines) +@@option group-add #### **--health-cmd**=*"command"* | *'["command", "arg1", ...]'* @@ -425,10 +407,7 @@ Container host name Sets the container host name that is available inside the container. Can only be used with a private UTS namespace `--uts=private` (default). If `--pod` is specified and the pod shares the UTS namespace (default) the pod's hostname will be used. -#### **--hostuser**=*name* - -Add a user account to /etc/passwd from the host to the container. The Username -or UID must exist on the host system. +@@option hostuser #### **--http-proxy** @@ -450,20 +429,9 @@ container: Defaults to `true` -#### **--image-volume**=**bind** | *tmpfs* | *ignore* - -Tells Podman how to handle the builtin image volumes. Default is **bind**. - -- **bind**: An anonymous named volume will be created and mounted into the container. -- **tmpfs**: The volume is mounted onto the container as a tmpfs, which allows the users to create -content that disappears when the container is stopped. -- **ignore**: All volumes are just ignored and no action is taken. +@@option image-volume -#### **--init** - -Run an init inside the container that forwards signals and reaps processes. -The container-init binary is mounted at `/run/podman-init`. -Mounting over `/run` will hence break container execution. +@@option init #### **--init-ctr**=*type* @@ -480,9 +448,7 @@ Init containers are only run on pod `start`. Restarting a pod will not execute containers should they be present. Furthermore, init containers can only be created in a pod when that pod is not running. -#### **--init-path**=*path* - -Path to the container-init binary. +@@option init-path #### **--interactive**, **-i** @@ -611,83 +577,7 @@ Tune a container's memory swappiness behavior. Accepts an integer between 0 and This flag is not supported on cgroups V2 systems. -#### **--mount**=*type=TYPE,TYPE-SPECIFIC-OPTION[,...]* - -Attach a filesystem mount to the container - -Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and **devpts**. <sup>[[1]](#Footnote1)</sup> - - e.g. - - type=bind,source=/path/on/host,destination=/path/in/container - - type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared - - type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true - - type=volume,source=vol1,destination=/path/in/container,ro=true - - type=tmpfs,tmpfs-size=512M,destination=/path/in/container - - type=image,source=fedora,destination=/fedora-image,rw=true - - type=devpts,destination=/dev/pts - - Common Options: - - · src, source: mount source spec for bind and volume. Mandatory for bind. - - · dst, destination, target: mount destination spec. - - Options specific to volume: - - · ro, readonly: true or false (default). - - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. - - · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. - - Options specific to image: - - · rw, readwrite: true or false (default). - - Options specific to bind: - - · ro, readonly: true or false (default). - - · bind-propagation: shared, slave, private, unbindable, rshared, rslave, runbindable, or rprivate(default). See also mount(2). - - . bind-nonrecursive: do not set up a recursive bind mount. By default it is recursive. - - . relabel: shared, private. - - · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. - - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. - - Options specific to tmpfs: - - · ro, readonly: true or false (default). - - · tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux. - - · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux. - - · tmpcopyup: Enable copyup from the image directory at the same location to the tmpfs. Used by default. - - · notmpcopyup: Disable copying files from the image to the tmpfs. - - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. - - Options specific to devpts: - - · uid: UID of the file owner (default 0). - - · gid: GID of the file owner (default 0). - - · mode: permission mask for the file (default 600). - - · max: maximum number of PTYs (default 1048576). +@@option mount #### **--name**=*name* @@ -745,9 +635,7 @@ these aliases can be used for name resolution on the given network. This option NOTE: When using CNI a container will only have access to aliases on the first network that it joins. This limitation does not exist with netavark/aardvark-dns. -#### **--no-healthcheck** - -Disable any defined healthchecks for container. +@@option no-healthcheck #### **--no-hosts** @@ -756,11 +644,7 @@ By default, Podman will manage _/etc/hosts_, adding the container's own IP addre **--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. This option conflicts with **--add-host**. -#### **--oom-kill-disable** - -Whether to disable OOM Killer for the container or not. - -This flag is not supported on cgroups V2 systems. +@@option oom-kill-disable #### **--oom-score-adj**=*num* @@ -769,15 +653,9 @@ Tune the host's OOM preferences for containers (accepts -1000 to 1000) #### **--os**=*OS* Override the OS, defaults to hosts, of the image to be pulled. For example, `windows`. -#### **--passwd-entry**=*ENTRY* - -Customize the entry that is written to the `/etc/passwd` file within the container when `--passwd` is used. +@@option passwd-entry -The variables $USERNAME, $UID, $GID, $NAME, $HOME are automatically replaced with their value at runtime. - -#### **--personality**=*persona* - -Personality sets the execution domain via Linux personality(2). +@@option personality #### **--pid**=*pid* @@ -788,15 +666,7 @@ Default is to create a private PID namespace for the container - `ns`: join the specified PID namespace - `private`: create a new namespace for the container (default) -#### **--pidfile**=*path* - -When the pidfile location is specified, the container process' PID will be written to the pidfile. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) -If the pidfile option is not specified, the container process' PID will be written to /run/containers/storage/${storage-driver}-containers/$CID/userdata/pidfile. - -After the container is started, the location for the pidfile can be discovered with the following `podman inspect` command: - - $ podman inspect --format '{{ .PidFile }}' $CID - /run/containers/storage/${storage-driver}-containers/$CID/userdata/pidfile +@@option pidfile #### **--pids-limit**=*limit* @@ -944,22 +814,9 @@ directory will be the lower, and the container storage directory will be the upper. Modifications to the mount point are destroyed when the container finishes executing, similar to a tmpfs mount point being unmounted. -#### **--sdnotify**=**container** | *conmon* | *ignore* - -Determines how to use the NOTIFY_SOCKET, as passed with systemd and Type=notify. - -Default is **container**, which means allow the OCI runtime to proxy the socket into the -container to receive ready notification. Podman will set the MAINPID to conmon's pid. -The **conmon** option sets MAINPID to conmon's pid, and sends READY when the container -has started. The socket is never passed to the runtime or the container. -The **ignore** option removes NOTIFY_SOCKET from the environment for itself and child processes, -for the case where some other process above Podman uses NOTIFY_SOCKET and Podman should not use it. - -#### **--seccomp-policy**=*policy* - -Specify the policy to select the seccomp profile. If set to *image*, Podman will look for a "io.containers.seccomp.profile" label in the container-image config and use its value as a seccomp profile. Otherwise, Podman will follow the *default* policy by applying the default profile unless specified otherwise via *--security-opt seccomp* as described below. +@@option sdnotify -Note that this feature is experimental and may change in the future. +@@option seccomp-policy #### **--secret**=*secret[,opt=opt ...]* @@ -1085,11 +942,7 @@ The `container_manage_cgroup` boolean must be enabled for this to be allowed on `setsebool -P container_manage_cgroup true` -#### **--timeout**=*seconds* - -Maximum time a container is allowed to run before conmon sends it the kill -signal. By default containers will run until they exit or are stopped by -`podman stop`. +@@option timeout #### **--tls-verify** @@ -1119,10 +972,7 @@ interactive shell. The default is false. Note: The **-t** option is incompatible with a redirection of the Podman client standard input. -#### **--tz**=*timezone* - -Set timezone in container. This flag takes area-based timezones, GMT time, as well as `local`, which sets the timezone in the container to match the host machine. See `/usr/share/zoneinfo/` for valid timezones. -Remote connections use local containers.conf for defaults +@@option tz #### **--uidmap**=*container_uid:from_uid:amount* @@ -1208,10 +1058,7 @@ Ulimit options You can pass `host` to copy the current configuration from the host. -#### **--umask**=*umask* - -Set the umask inside the container. Defaults to `0022`. -Remote connections use local containers.conf for defaults +@@option umask #### **--unsetenv**=*env* @@ -1219,11 +1066,7 @@ Unset default environment variables for the container. Default environment variables include variables provided natively by Podman, environment variables configured by the image, and environment variables from containers.conf. -#### **--unsetenv-all** - -Unset all default environment variables for the container. Default environment -variables include variables provided natively by Podman, environment variables -configured by the image, and environment variables from containers.conf. +@@option unsetenv-all #### **--user**, **-u**=*user* diff --git a/docs/source/markdown/podman-info.1.md b/docs/source/markdown/podman-info.1.md index 347d8d27b..fc7e1f0e2 100644 --- a/docs/source/markdown/podman-info.1.md +++ b/docs/source/markdown/podman-info.1.md @@ -15,10 +15,6 @@ Displays information pertinent to the host, current storage stats, configured co ## OPTIONS -#### **--debug**, **-D** - -Show additional information - #### **--format**, **-f**=*format* Change output format to "json" or a Go template. diff --git a/docs/source/markdown/podman-kube-down.1.md b/docs/source/markdown/podman-kube-down.1.md new file mode 100644 index 000000000..35725043b --- /dev/null +++ b/docs/source/markdown/podman-kube-down.1.md @@ -0,0 +1,43 @@ +% podman-kube-down(1) + +## NAME +podman-kube-down - Remove containers and pods based on Kubernetes YAML + +## SYNOPSIS +**podman kube down** *file.yml|-* + +## DESCRIPTION +**podman kube down** reads a specified Kubernetes YAML file, tearing down pods that were created by the `podman kube play` command via the same Kubernetes YAML file. Any volumes that were created by the previous `podman kube play` command remain intact. If the YAML file is specified as `-`, `podman kube down` reads the YAML from stdin. + +## EXAMPLES + +Example YAML file `demo.yml`: +``` +apiVersion: v1 +kind: Pod +metadata: +... +spec: + containers: + - command: + - top + - name: container + value: podman + image: foobar +... +``` + +Remove the pod and containers as described in the `demo.yml` file +``` +$ podman kube down demo.yml +52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 +``` + +Remove the pod and containers as described in the`demo.yml` file YAML sent to stdin +``` +$ cat demo.yml | podman kube play - +52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 +``` + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-kube(1)](podman-kube.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)**, **[podman-generate-kube(1)](podman-generate-kube.1.md)**, **[containers-certs.d(5)](https://github.com/containers/image/blob/main/docs/containers-certs.d.5.md)** diff --git a/docs/source/markdown/podman-kube-play.1.md b/docs/source/markdown/podman-kube-play.1.md index 25248ce99..b3c385fe9 100644 --- a/docs/source/markdown/podman-kube-play.1.md +++ b/docs/source/markdown/podman-kube-play.1.md @@ -1,7 +1,7 @@ % podman-kube-play(1) ## NAME -podman-kube-play - Create containers, pods or volumes based on Kubernetes YAML +podman-kube-play - Create containers, pods and volumes based on Kubernetes YAML ## SYNOPSIS **podman kube play** [*options*] *file.yml|-* @@ -30,6 +30,9 @@ Note: If the `:latest` tag is used, Podman will attempt to pull the image from a Note: The command `podman play kube` is an alias of `podman kube play`, and will perform the same function. +Note: The command `podman kube down` can be used to stop and remove pods or containers based on the same Kubernetes YAML used +by `podman kube play` to create them. + `Kubernetes PersistentVolumeClaims` A Kubernetes PersistentVolumeClaim represents a Podman named volume. Only the PersistentVolumeClaim name is required by Podman to create a volume. Kubernetes annotations can be used to make use of the available options for Podman volumes. @@ -145,11 +148,6 @@ The [username[:password]] to use to authenticate with the registry if required. If one or both values are not supplied, a command line prompt will appear and the value can be entered. The password is entered without echo. -#### **--down** - -Tears down the pods that were created by a previous run of `kube play`. The pods are stopped and then -removed. Any volumes created are left intact. - #### **--help**, **-h** Print usage statement @@ -325,7 +323,7 @@ $ podman kube play demo.yml --network net1:ip=10.89.1.5 --network net2:ip=10.89. Please take into account that networks must be created first using podman-network-create(1). ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-kube(1)](podman-kube.1.md)**, **[podman-network-create(1)](podman-network-create.1.md)**, **[podman-generate-kube(1)](podman-generate-kube.1.md)**, **[containers-certs.d(5)](https://github.com/containers/image/blob/main/docs/containers-certs.d.5.md)** +**[podman(1)](podman.1.md)**, **[podman-kube(1)](podman-kube.1.md)**, **[podman-kube-down(1)](podman-kube-down.1.md)**, **[podman-network-create(1)](podman-network-create.1.md)**, **[podman-generate-kube(1)](podman-generate-kube.1.md)**, **[containers-certs.d(5)](https://github.com/containers/image/blob/main/docs/containers-certs.d.5.md)** ## HISTORY December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/source/markdown/podman-kube.1.md b/docs/source/markdown/podman-kube.1.md index f815ffae2..7a6978a2b 100644 --- a/docs/source/markdown/podman-kube.1.md +++ b/docs/source/markdown/podman-kube.1.md @@ -14,7 +14,8 @@ file input. Containers will be automatically started. | Command | Man Page | Description | | ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- | -| play | [podman-kube-play(1)](podman-kube-play.1.md) | Create containers, pods or volumes based on Kubernetes YAML. | +| down | [podman-kube-down(1)](podman-kube-down.1.md) | Remove containers and pods based on Kubernetes YAML. | +| play | [podman-kube-play(1)](podman-kube-play.1.md) | Create containers, pods and volumes based on Kubernetes YAML. | ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-container(1)](podman-container.1.md)**, **[podman-generate(1)](podman-generate.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)** +**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-container(1)](podman-container.1.md)**, **[podman-generate(1)](podman-generate.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)**, **[podman-kube-down(1)](podman-kube-down.1.md)** diff --git a/docs/source/markdown/podman-rm.1.md b/docs/source/markdown/podman-rm.1.md index 1fac3aa34..1dbd1d0c3 100644 --- a/docs/source/markdown/podman-rm.1.md +++ b/docs/source/markdown/podman-rm.1.md @@ -26,6 +26,30 @@ Read container ID from the specified file and remove the container. Can be spec Remove selected container and recursively remove all containers that depend on it. +#### **--filter**=*filter* + +Filter what containers remove. +Multiple filters can be given with multiple uses of the --filter flag. +Filters with the same key work inclusive with the only exception being +`label` which is exclusive. Filters with different keys always work exclusive. + +Valid filters are listed below: + +| **Filter** | **Description** | +| --------------- | -------------------------------------------------------------------------------- | +| id | [ID] Container's ID (accepts regex) | +| name | [Name] Container's name (accepts regex) | +| label | [Key] or [Key=Value] Label assigned to a container | +| exited | [Int] Container's exit code | +| status | [Status] Container's status: 'created', 'exited', 'paused', 'running', 'unknown' | +| ancestor | [ImageName] Image or descendant used to create container | +| before | [ID] or [Name] Containers created before this container | +| since | [ID] or [Name] Containers created since this container | +| volume | [VolumeName] or [MountpointDestination] Volume mounted in container | +| health | [Status] healthy or unhealthy | +| pod | [Pod] name or full or partial ID of pod | +| network | [Network] name or full ID of network | + #### **--force**, **-f** Force the removal of running and paused containers. Forcing a container removal also diff --git a/docs/source/markdown/podman-rmi.1.md b/docs/source/markdown/podman-rmi.1.md index 8d0e5e500..93658daaf 100644 --- a/docs/source/markdown/podman-rmi.1.md +++ b/docs/source/markdown/podman-rmi.1.md @@ -28,6 +28,9 @@ This option will cause podman to remove all containers that are using the image If a specified image does not exist in the local storage, ignore it and do not throw an error. +#### **--no-prune** + +This options will not remove dangling parents of specified image Remove an image by its short ID ``` diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md.in index 8c889f0a5..df4c43c41 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md.in @@ -132,9 +132,7 @@ Add Linux capabilities. Drop Linux capabilities. -#### **--cgroup-conf**=*KEY=VALUE* - -When running on cgroup v2, specify the cgroup file to write to and its value. For example **--cgroup-conf=memory.high=1073741824** sets the memory.high limit to 1GB. +@@option cgroup-conf #### **--cgroup-parent**=*path* @@ -162,11 +160,7 @@ The **disabled** option will force the container to not create CGroups, and thus The **no-conmon** option disables a new CGroup only for the **conmon** process. The **split** option splits the current CGroup in two sub-cgroups: one for conmon and one for the container payload. It is not possible to set **--cgroup-parent** with **split**. -#### **--chrootdirs**=*path* - -Path to a directory inside the container that should be treated as a `chroot` directory. -Any Podman managed file (e.g., /etc/resolv.conf, /etc/hosts, etc/hostname) that is mounted into the root directory will be mounted into that location as well. -Multiple directories should be separated with a comma. +@@option chrootdirs #### **--cidfile**=*file* @@ -393,9 +387,7 @@ See [**Environment**](#environment) note below for precedence and examples. Read in a line delimited file of environment variables. See **Environment** note below for precedence. -#### **--env-host** - -Use host environment inside of the container. See **Environment** note below for precedence. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) +@@option env-host #### **--expose**=*port* @@ -411,17 +403,7 @@ __--uidmap__ maps host UIDs to container UIDs. For details see __--uidmap__. Note: the **--gidmap** flag cannot be called in conjunction with the **--pod** flag as a gidmap cannot be set on the container level when in a pod. -#### **--group-add**=*group* | *keep-groups* - -Assign additional groups to the primary user running within the container process. - -- `keep-groups` is a special flag that tells Podman to keep the supplementary group access. - -Allows container to use the user's supplementary group access. If file systems or -devices are only accessible by the rootless user's group, this flag tells the OCI -runtime to pass the group access into the container. Currently only available -with the `crun` OCI runtime. Note: `keep-groups` is exclusive, you cannot add any other groups -with this flag. (Not available for remote commands, including Mac and Windows (excluding WSL2) machines) +@@option group-add #### **--health-cmd**=*"command"* | *'["command", "arg1", ...]'* @@ -460,10 +442,7 @@ Container host name Sets the container host name that is available inside the container. Can only be used with a private UTS namespace `--uts=private` (default). If `--pod` is specified and the pod shares the UTS namespace (default) the pod's hostname will be used. -#### **--hostuser**=*name* - -Add a user account to /etc/passwd from the host to the container. The Username -or UID must exist on the host system. +@@option hostuser #### **--http-proxy** @@ -480,24 +459,11 @@ proxy environment at container build time.) (This option is not available with t Defaults to **true**. -#### **--image-volume**=**bind** | *tmpfs* | *ignore* - -Tells Podman how to handle the builtin image volumes. Default is **bind**. - -- **bind**: An anonymous named volume will be created and mounted into the container. -- **tmpfs**: The volume is mounted onto the container as a tmpfs, which allows the users to create -content that disappears when the container is stopped. -- **ignore**: All volumes are just ignored and no action is taken. +@@option image-volume -#### **--init** +@@option init -Run an init inside the container that forwards signals and reaps processes. -The container-init binary is mounted at `/run/podman-init`. -Mounting over `/run` will hence break container execution. - -#### **--init-path**=*path* - -Path to the container-init binary. +@@option init-path #### **--interactive**, **-i** @@ -627,83 +593,7 @@ Tune a container's memory swappiness behavior. Accepts an integer between *0* an This flag is not supported on cgroups V2 systems. -#### **--mount**=*type=TYPE,TYPE-SPECIFIC-OPTION[,...]* - -Attach a filesystem mount to the container - -Current supported mount TYPEs are **bind**, **volume**, **image**, **tmpfs** and **devpts**. <sup>[[1]](#Footnote1)</sup> - - e.g. - - type=bind,source=/path/on/host,destination=/path/in/container - - type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared - - type=bind,src=/path/on/host,dst=/path/in/container,relabel=shared,U=true - - type=volume,source=vol1,destination=/path/in/container,ro=true - - type=tmpfs,tmpfs-size=512M,destination=/path/in/container - - type=image,source=fedora,destination=/fedora-image,rw=true - - type=devpts,destination=/dev/pts - - Common Options: - - · src, source: mount source spec for bind and volume. Mandatory for bind. - - · dst, destination, target: mount destination spec. - - Options specific to volume: - - · ro, readonly: true or false (default). - - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. - - · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. - - Options specific to image: - - · rw, readwrite: true or false (default). - - Options specific to bind: - - · ro, readonly: true or false (default). - - · bind-propagation: shared, slave, private, unbindable, rshared, rslave, runbindable, or rprivate(default). See also mount(2). - - . bind-nonrecursive: do not set up a recursive bind mount. By default it is recursive. - - . relabel: shared, private. - - · idmap: true or false (default). If specified, create an idmapped mount to the target user namespace in the container. - - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. - - Options specific to tmpfs: - - · ro, readonly: true or false (default). - - · tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux. - - · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux. - - · tmpcopyup: Enable copyup from the image directory at the same location to the tmpfs. Used by default. - - · notmpcopyup: Disable copying files from the image to the tmpfs. - - . U, chown: true or false (default). Change recursively the owner and group of the source volume based on the UID and GID of the container. - - Options specific to devpts: - - · uid: UID of the file owner (default 0). - - · gid: GID of the file owner (default 0). - - · mode: permission mask for the file (default 600). - - · max: maximum number of PTYs (default 1048576). +@@option mount #### **--name**=*name* @@ -762,9 +652,7 @@ these aliases can be used for name resolution on the given network. This option NOTE: When using CNI a container will only have access to aliases on the first network that it joins. This limitation does not exist with netavark/aardvark-dns. -#### **--no-healthcheck** - -Disable any defined healthchecks for container. +@@option no-healthcheck #### **--no-hosts** @@ -773,11 +661,7 @@ By default, Podman will manage _/etc/hosts_, adding the container's own IP addre **--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. This option conflicts with **--add-host**. -#### **--oom-kill-disable** - -Whether to disable OOM Killer for the container or not. - -This flag is not supported on cgroups V2 systems. +@@option oom-kill-disable #### **--oom-score-adj**=*num* @@ -791,15 +675,9 @@ Override the OS, defaults to hosts, of the image to be pulled. For example, `win Allow Podman to add entries to /etc/passwd and /etc/group when used in conjunction with the --user option. This is used to override the Podman provided user setup in favor of entrypoint configurations such as libnss-extrausers. -#### **--passwd-entry**=*ENTRY* - -Customize the entry that is written to the `/etc/passwd` file within the container when `--passwd` is used. +@@option passwd-entry -The variables $USERNAME, $UID, $GID, $NAME, $HOME are automatically replaced with their value at runtime. - -#### **--personality**=*persona* - -Personality sets the execution domain via Linux personality(2). +@@option personality #### **--pid**=*mode* @@ -811,15 +689,7 @@ The default is to create a private PID namespace for the container. - **private**: create a new namespace for the container (default) - **ns:**_path_: join the specified PID namespace. -#### **--pidfile**=*path* - -When the pidfile location is specified, the container process' PID will be written to the pidfile. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) -If the pidfile option is not specified, the container process' PID will be written to /run/containers/storage/${storage-driver}-containers/$CID/userdata/pidfile. - -After the container is started, the location for the pidfile can be discovered with the following `podman inspect` command: - - $ podman inspect --format '{{ .PidFile }}' $CID - /run/containers/storage/${storage-driver}-containers/$CID/userdata/pidfile +@@option pidfile #### **--pids-limit**=*limit* @@ -982,22 +852,9 @@ finishes executing, similar to a tmpfs mount point being unmounted. Note: On **SELinux** systems, the rootfs needs the correct label, which is by default **unconfined_u:object_r:container_file_t**. -#### **--sdnotify**=**container** | *conmon* | *ignore* - -Determines how to use the NOTIFY_SOCKET, as passed with systemd and Type=notify. - -Default is **container**, which means allow the OCI runtime to proxy the socket into the -container to receive ready notification. Podman will set the MAINPID to conmon's pid. -The **conmon** option sets MAINPID to conmon's pid, and sends READY when the container -has started. The socket is never passed to the runtime or the container. -The **ignore** option removes NOTIFY_SOCKET from the environment for itself and child processes, -for the case where some other process above Podman uses NOTIFY_SOCKET and Podman should not use it. - -#### **--seccomp-policy**=*policy* - -Specify the policy to select the seccomp profile. If set to *image*, Podman will look for a "io.containers.seccomp.profile" label in the container-image config and use its value as a seccomp profile. Otherwise, Podman will follow the *default* policy by applying the default profile unless specified otherwise via *--security-opt seccomp* as described below. +@@option sdnotify -Note that this feature is experimental and may change in the future. +@@option seccomp-policy #### **--secret**=*secret[,opt=opt ...]* @@ -1139,11 +996,7 @@ The **container_manage_cgroup** boolean must be enabled for this to be allowed o setsebool -P container_manage_cgroup true ``` -#### **--timeout**=*seconds* - -Maximum time a container is allowed to run before conmon sends it the kill -signal. By default containers will run until they exit or are stopped by -`podman stop`. +@@option timeout #### **--tls-verify** @@ -1178,10 +1031,7 @@ interactive shell. The default is **false**. echo "asdf" | podman run --rm -i someimage /bin/cat ``` -#### **--tz**=*timezone* - -Set timezone in container. This flag takes area-based timezones, GMT time, as well as `local`, which sets the timezone in the container to match the host machine. See `/usr/share/zoneinfo/` for valid timezones. -Remote connections use local containers.conf for defaults +@@option tz #### **--uidmap**=*container_uid:from_uid:amount* @@ -1267,10 +1117,7 @@ Note: the **--uidmap** flag cannot be called in conjunction with the **--pod** f Ulimit options. You can use **host** to copy the current configuration from the host. -#### **--umask**=*umask* - -Set the umask inside the container. Defaults to `0022`. -Remote connections use local containers.conf for defaults +@@option umask #### **--unsetenv**=*env* @@ -1278,11 +1125,7 @@ Unset default environment variables for the container. Default environment variables include variables provided natively by Podman, environment variables configured by the image, and environment variables from containers.conf. -#### **--unsetenv-all** - -Unset all default environment variables for the container. Default environment -variables include variables provided natively by Podman, environment variables -configured by the image, and environment variables from containers.conf. +@@option unsetenv-all #### **--user**, **-u**=*user[:group]* @@ -12,7 +12,7 @@ require ( github.com/containernetworking/cni v1.1.2 github.com/containernetworking/plugins v1.1.1 github.com/containers/buildah v1.26.1-0.20220716095526-d31d27c357ab - github.com/containers/common v0.49.1-0.20220729221035-246800047d46 + github.com/containers/common v0.49.2-0.20220804143628-dc97077782d5 github.com/containers/conmon v2.0.20+incompatible github.com/containers/image/v5 v5.22.0 github.com/containers/ocicrypt v1.1.5 @@ -395,8 +395,8 @@ github.com/containernetworking/plugins v1.1.1/go.mod h1:Sr5TH/eBsGLXK/h71HeLfX19 github.com/containers/buildah v1.26.1-0.20220716095526-d31d27c357ab h1:NeI0DOkTf3Tn4OpdjMhMubAfTPs2oCO5jUY5wnpv4qk= github.com/containers/buildah v1.26.1-0.20220716095526-d31d27c357ab/go.mod h1:iVtQtU6a+pbETBqIzg0oAWW3gTR1ItrAihJpLFFppmA= github.com/containers/common v0.48.1-0.20220715075726-2ac10faca05a/go.mod h1:1dA7JPGoSi83kjf5H4NIrGANyLOULyvFqV1bwvYFEek= -github.com/containers/common v0.49.1-0.20220729221035-246800047d46 h1:BNNV+JlPYSmaa9rTapL9kh2JZrg7hmWwi/VrIY/KH1E= -github.com/containers/common v0.49.1-0.20220729221035-246800047d46/go.mod h1:ueM5hT0itKqCQvVJDs+EtjornAQtrHYxQJzP2gxeGIg= +github.com/containers/common v0.49.2-0.20220804143628-dc97077782d5 h1:bOdbfjiOvj5n51dyeo8LF3qAtvaiflS13q70Cx4NA40= +github.com/containers/common v0.49.2-0.20220804143628-dc97077782d5/go.mod h1:ueM5hT0itKqCQvVJDs+EtjornAQtrHYxQJzP2gxeGIg= 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.21.2-0.20220712113758-29aec5f7bbbf/go.mod h1:0+N0ZM9mgMmoZZc6uNcgnEsbX85Ne7b29cIW5lqWwVU= diff --git a/hack/get_ci_vm.sh b/hack/get_ci_vm.sh index 6632a0178..bdd947bba 100755 --- a/hack/get_ci_vm.sh +++ b/hack/get_ci_vm.sh @@ -15,7 +15,7 @@ SCRIPT_FILEPATH=$(realpath "${BASH_SOURCE[0]}") SCRIPT_DIRPATH=$(dirname "$SCRIPT_FILEPATH") REPO_DIRPATH=$(realpath "$SCRIPT_DIRPATH/../") -# Help detect if we were called by get_ci_vm container +# Help detect what get_ci_vm container called this script GET_CI_VM="${GET_CI_VM:-0}" in_get_ci_vm() { if ((GET_CI_VM==0)); then @@ -27,8 +27,10 @@ in_get_ci_vm() { # get_ci_vm APIv1 container entrypoint calls into this script # to obtain required repo. specific configuration options. if [[ "$1" == "--config" ]]; then - in_get_ci_vm "$1" - cat <<EOF + in_get_ci_vm "$1" # handles GET_CI_VM==0 case + case "$GET_CI_VM" in + 1) + cat <<EOF DESTDIR="/var/tmp/go/src/github.com/containers/podman" UPSTREAM_REPO="https://github.com/containers/podman.git" CI_ENVFILE="/etc/ci_environment" @@ -40,8 +42,18 @@ GCLOUD_CPUS="2" GCLOUD_MEMORY="4Gb" GCLOUD_DISK="200" EOF + ;; + 2) + # get_ci_vm APIv2 configuration details + echo "AWS_PROFILE=containers" + ;; + *) + echo "Error: Your get_ci_vm container image is too old." + ;; + esac elif [[ "$1" == "--setup" ]]; then in_get_ci_vm "$1" + unset GET_CI_VM # get_ci_vm container entrypoint calls us with this option on the # Cirrus-CI environment instance, to perform repo.-specific setup. cd $REPO_DIRPATH @@ -54,8 +66,9 @@ elif [[ "$1" == "--setup" ]]; then echo "+ Running environment setup" > /dev/stderr ./contrib/cirrus/setup_environment.sh else - # Create and access VM for specified Cirrus-CI task + # Pass this repo and CLI args into container for VM creation/management mkdir -p $HOME/.config/gcloud/ssh + mkdir -p $HOME/.aws podman run -it --rm \ --tz=local \ -e NAME="$USER" \ @@ -65,5 +78,6 @@ else -v $REPO_DIRPATH:/src:O \ -v $HOME/.config/gcloud:/root/.config/gcloud:z \ -v $HOME/.config/gcloud/ssh:/root/.ssh:z \ + -v $HOME/.aws:/root/.aws:z \ quay.io/libpod/get_ci_vm:latest "$@" fi diff --git a/hack/markdown-preprocess b/hack/markdown-preprocess new file mode 100755 index 000000000..4bc67a819 --- /dev/null +++ b/hack/markdown-preprocess @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# +# markdown-preprocess - filter *.md.in files, convert to .md +# + +import glob +import os +import sys + +def main(): + script_dir = os.path.abspath(os.path.dirname(__file__)) + man_dir = os.path.join(script_dir,"../docs/source/markdown") + + try: + os.chdir(man_dir) + except FileNotFoundError: + raise Exception("Please invoke me from the base repo dir") + + # If called with args, process only those files + infiles = [ os.path.basename(x) for x in sys.argv[1:] ] + if len(infiles) == 0: + # Called without args: process all *.md.in files + infiles = glob.glob('*.md.in') + for infile in infiles: + process(infile) + +def process(infile): + # Some options are the same between containers and pods; determine + # which description to use from the name of the source man page. + pod_or_container = 'container' + if '-pod-' in infile: + pod_or_container = 'pod' + + # Sometimes a man page includes the subcommand. + subcommand = removesuffix(removeprefix(infile,"podman-"),".1.md.in").replace("-", " ") + + # foo.md.in -> foo.md -- but always write to a tmpfile + outfile = os.path.splitext(infile)[0] + outfile_tmp = outfile + '.tmp.' + str(os.getpid()) + +# print("got here: ",infile, " -> ", outfile) + + with open(infile, 'r') as fh_in, open(outfile_tmp, 'w') as fh_out: + for line in fh_in: + # '@@option foo' -> include file options/foo.md + if line.startswith('@@option '): + _, optionname = line.strip().split(" ") + optionfile = os.path.join("options", optionname + '.md') + + # Comment intended to help someone viewing the .md file. + # Leading newline is important because if two lines are + # consecutive without a break, sphinx (but not go-md2man) + # treats them as one line and will unwantedly render the + # comment in its output. + fh_out.write("\n[//]: # (BEGIN included file " + optionfile + ")\n") + with open(optionfile, 'r') as fh_optfile: + for opt_line in fh_optfile: + opt_line = opt_line.replace('<POD-OR-CONTAINER>', pod_or_container) + opt_line = opt_line.replace('<SUBCOMMAND>', subcommand) + fh_out.write(opt_line) + fh_out.write("\n[//]: # (END included file " + optionfile + ")\n") + else: + fh_out.write(line) + + os.chmod(outfile_tmp, 0o444) + os.rename(outfile_tmp, outfile) + +# str.removeprefix() is python 3.9+, we need to support older versions on mac +def removeprefix(string: str, prefix: str) -> str: + if string.startswith(prefix): + return string[len(prefix):] + else: + return string[:] + +# str.removesuffix() is python 3.9+, we need to support older versions on mac +def removesuffix(string: str, suffix: str) -> str: + # suffix='' should not call self[:-0]. + if suffix and string.endswith(suffix): + return string[:-len(suffix)] + else: + return string[:] + + +if __name__ == "__main__": + main() diff --git a/hack/swagger-check b/hack/swagger-check index 1e5b95c3a..b4481f5bb 100755 --- a/hack/swagger-check +++ b/hack/swagger-check @@ -320,8 +320,8 @@ sub operation_name { if ($action eq 'df') { $action = 'dataUsage'; } - elsif ($action eq "delete" && $endpoint eq "/libpod/kube/play") { - $action = "PlayDown" + elsif ($action eq "delete" && $endpoint eq "/libpod/play/kube") { + $action = "KubeDown" } # Grrrrrr, this one is annoying: some operations get an extra 'All' elsif ($action =~ /^(delete|get|stats)$/ && $endpoint !~ /\{/) { diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index ce0fd869d..703ae5cbe 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -995,7 +995,7 @@ func (r *Runtime) RemoveDepend(ctx context.Context, rmCtr *Container, force bool return nil, err } for _, cID := range podContainerIDS { - rmReports = append(rmReports, &reports.RmReport{Id: cID}) + rmReports = append(rmReports, &reports.RmReport{Id: cID, RawInput: cID}) } return rmReports, nil } @@ -1023,7 +1023,7 @@ func (r *Runtime) RemoveDepend(ctx context.Context, rmCtr *Container, force bool rmReports = append(rmReports, reports...) } - report := reports.RmReport{Id: rmCtr.ID()} + report := reports.RmReport{Id: rmCtr.ID(), RawInput: rmCtr.ID()} report.Err = r.removeContainer(ctx, rmCtr, force, removeVolume, false, timeout) return append(rmReports, &report), nil } diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 67943ecf1..bccaad932 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -547,6 +547,7 @@ func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) { Ignore bool `schema:"ignore"` LookupManifest bool `schema:"lookupManifest"` Images []string `schema:"images"` + NoPrune bool `schema:"noprune"` }{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { @@ -554,7 +555,7 @@ func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) { return } - opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force, Ignore: query.Ignore, LookupManifest: query.LookupManifest} + opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force, Ignore: query.Ignore, LookupManifest: query.LookupManifest, NoPrune: query.NoPrune} imageEngine := abi.ImageEngine{Libpod: runtime} rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts) strErrs := errorhandling.ErrorsToStrings(rmErrors) diff --git a/pkg/api/handlers/swagger/responses.go b/pkg/api/handlers/swagger/responses.go index 5731f8edd..93a508b39 100644 --- a/pkg/api/handlers/swagger/responses.go +++ b/pkg/api/handlers/swagger/responses.go @@ -71,7 +71,7 @@ type imagesRemoveResponseLibpod struct { // PlayKube response // swagger:response -type kubePlayResponseLibpod struct { +type playKubeResponseLibpod struct { // in:body Body entities.PlayKubeReport } diff --git a/pkg/api/server/register_kube.go b/pkg/api/server/register_play.go index 6ae9e8123..76e150504 100644 --- a/pkg/api/server/register_kube.go +++ b/pkg/api/server/register_play.go @@ -7,8 +7,8 @@ import ( "github.com/gorilla/mux" ) -func (s *APIServer) registerKubeHandlers(r *mux.Router) error { - // swagger:operation POST /libpod/kube/play libpod KubePlayLibpod +func (s *APIServer) registerPlayHandlers(r *mux.Router) error { + // swagger:operation POST /libpod/play/kube libpod PlayKubeLibpod // --- // tags: // - containers @@ -57,12 +57,12 @@ func (s *APIServer) registerKubeHandlers(r *mux.Router) error { // - application/json // responses: // 200: - // $ref: "#/responses/kubePlayResponseLibpod" + // $ref: "#/responses/playKubeResponseLibpod" // 500: // $ref: "#/responses/internalError" - r.HandleFunc(VersionedPath("/libpod/kube/play"), s.APIHandler(libpod.KubePlay)).Methods(http.MethodPost) r.HandleFunc(VersionedPath("/libpod/play/kube"), s.APIHandler(libpod.PlayKube)).Methods(http.MethodPost) - // swagger:operation DELETE /libpod/kube/play libpod KubePlayDownLibpod + r.HandleFunc(VersionedPath("/libpod/kube/play"), s.APIHandler(libpod.KubePlay)).Methods(http.MethodPost) + // swagger:operation DELETE /libpod/play/kube libpod PlayKubeDownLibpod // --- // tags: // - containers @@ -73,10 +73,10 @@ func (s *APIServer) registerKubeHandlers(r *mux.Router) error { // - application/json // responses: // 200: - // $ref: "#/responses/kubePlayResponseLibpod" + // $ref: "#/responses/playKubeResponseLibpod" // 500: // $ref: "#/responses/internalError" - r.HandleFunc(VersionedPath("/libpod/kube/play"), s.APIHandler(libpod.KubePlayDown)).Methods(http.MethodDelete) r.HandleFunc(VersionedPath("/libpod/play/kube"), s.APIHandler(libpod.PlayKubeDown)).Methods(http.MethodDelete) + r.HandleFunc(VersionedPath("/libpod/kube/play"), s.APIHandler(libpod.KubePlayDown)).Methods(http.MethodDelete) return nil } diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index a6d8b5e4c..5482a8ec2 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -126,11 +126,11 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser server.registerHealthCheckHandlers, server.registerImagesHandlers, server.registerInfoHandlers, - server.registerKubeHandlers, server.registerManifestHandlers, server.registerMonitorHandlers, server.registerNetworkHandlers, server.registerPingHandlers, + server.registerPlayHandlers, server.registerPluginsHandlers, server.registerPodsHandlers, server.registerSecretHandlers, diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go index 8d9991622..297d6640e 100644 --- a/pkg/autoupdate/autoupdate.go +++ b/pkg/autoupdate/autoupdate.go @@ -43,15 +43,41 @@ const ( // Map for easy lookups of supported policies. var supportedPolicies = map[string]Policy{ - "": PolicyDefault, - "disabled": PolicyDefault, - "image": PolicyRegistryImage, - "registry": PolicyRegistryImage, - "local": PolicyLocalImage, + "": PolicyDefault, + string(PolicyDefault): PolicyDefault, + "image": PolicyRegistryImage, // Deprecated in favor of PolicyRegistryImage + string(PolicyRegistryImage): PolicyRegistryImage, + string(PolicyLocalImage): PolicyLocalImage, } -// policyMapper is used for tying a container to it's autoupdate policy -type policyMapper map[Policy][]*libpod.Container +// updater includes shared state for auto-updating one or more containers. +type updater struct { + conn *dbus.Conn // DBUS connection + options *entities.AutoUpdateOptions // User-specified options + unitToTasks map[string][]*task // Keeps track of tasks per unit + updatedRawImages map[string]bool // Keeps track of updated images + runtime *libpod.Runtime // The libpod runtime +} + +const ( + statusFailed = "failed" // The update has failed + statusUpdated = "true" // The update succeeded + statusNotUpdated = "false" // No update was needed + statusPending = "pending" // The update is pending (see options.DryRun) + statusRolledBack = "rolled back" // Rollback after a failed update +) + +// task includes data and state for updating a container +type task struct { + authfile string // Container-specific authfile + auto *updater // Reverse pointer to the updater + container *libpod.Container // Container to update + policy Policy // Update policy + image *libimage.Image // Original image before the update + rawImageName string // The container's raw image name + status string // Auto-update status + unit string // Name of the systemd unit +} // LookupPolicy looks up the corresponding Policy for the specified // string. If none is found, an errors is returned including the list of @@ -116,23 +142,22 @@ func ValidateImageReference(imageName string) error { // It returns a slice of successfully restarted systemd units and a slice of // errors encountered during auto update. func AutoUpdate(ctx context.Context, runtime *libpod.Runtime, options entities.AutoUpdateOptions) ([]*entities.AutoUpdateReport, []error) { - // Create a map from `image ID -> []*Container`. - containerMap, errs := imageContainersMap(runtime) - if len(containerMap) == 0 { - return nil, errs - } + // Note that (most) errors are non-fatal such that a single + // misconfigured container does not prevent others from being updated + // (which could be a security threat). - // Create a map from `image ID -> *libimage.Image` for image lookups. - listOptions := &libimage.ListImagesOptions{ - Filters: []string{"readonly=false"}, + auto := updater{ + options: &options, + runtime: runtime, + updatedRawImages: make(map[string]bool), } - imagesSlice, err := runtime.LibimageRuntime().ListImages(ctx, nil, listOptions) - if err != nil { - return nil, []error{err} - } - imageMap := make(map[string]*libimage.Image) - for i := range imagesSlice { - imageMap[imagesSlice[i].ID()] = imagesSlice[i] + + // Find auto-update tasks and assemble them by unit. + errors := auto.assembleTasks(ctx) + + // Nothing to do. + if len(auto.unitToTasks) == 0 { + return nil, errors } // Connect to DBUS. @@ -142,185 +167,176 @@ func AutoUpdate(ctx context.Context, runtime *libpod.Runtime, options entities.A return nil, []error{err} } defer conn.Close() + auto.conn = conn runtime.NewSystemEvent(events.AutoUpdate) // Update all images/container according to their auto-update policy. var allReports []*entities.AutoUpdateReport - updatedRawImages := make(map[string]bool) - for imageID, policyMapper := range containerMap { - image, exists := imageMap[imageID] - if !exists { - errs = append(errs, fmt.Errorf("container image ID %q not found in local storage", imageID)) - return nil, errs + for unit, tasks := range auto.unitToTasks { + // Sanity check: we'll support that in the future. + if len(tasks) != 1 { + errors = append(errors, fmt.Errorf("only 1 task per unit supported but unit %s has %d", unit, len(tasks))) + return nil, errors } - for _, ctr := range policyMapper[PolicyRegistryImage] { - report, err := autoUpdateRegistry(ctx, image, ctr, updatedRawImages, &options, conn, runtime) - if err != nil { - errs = append(errs, err) - } - if report != nil { - allReports = append(allReports, report) - } - } + for _, task := range tasks { + err := func() error { + // Transition from state to state. Will be + // split into multiple loops in the future to + // support more than one container/task per + // unit. + updateAvailable, err := task.updateAvailable(ctx) + if err != nil { + task.status = statusFailed + return fmt.Errorf("checking image updates for container %s: %w", task.container.ID(), err) + } + + if !updateAvailable { + task.status = statusNotUpdated + return nil + } + + if options.DryRun { + task.status = statusPending + return nil + } + + if err := task.update(ctx); err != nil { + task.status = statusFailed + return fmt.Errorf("updating image for container %s: %w", task.container.ID(), err) + } + + updateError := auto.restartSystemdUnit(ctx, unit) + if updateError == nil { + task.status = statusUpdated + return nil + } + + if !options.Rollback { + task.status = statusFailed + return fmt.Errorf("restarting unit %s for container %s: %w", task.unit, task.container.ID(), err) + } + + if err := task.rollbackImage(); err != nil { + task.status = statusFailed + return fmt.Errorf("rolling back image for container %s: %w", task.container.ID(), err) + } + + if err := auto.restartSystemdUnit(ctx, unit); err != nil { + task.status = statusFailed + return fmt.Errorf("restarting unit %s for container %s during rollback: %w", task.unit, task.container.ID(), err) + } + + task.status = statusRolledBack + return nil + }() - for _, ctr := range policyMapper[PolicyLocalImage] { - report, err := autoUpdateLocally(ctx, image, ctr, &options, conn, runtime) if err != nil { - errs = append(errs, err) - } - if report != nil { - allReports = append(allReports, report) + errors = append(errors, err) } + allReports = append(allReports, task.report()) } } - return allReports, errs + return allReports, errors } -// autoUpdateRegistry updates the image/container according to the "registry" policy. -func autoUpdateRegistry(ctx context.Context, image *libimage.Image, ctr *libpod.Container, updatedRawImages map[string]bool, options *entities.AutoUpdateOptions, conn *dbus.Conn, runtime *libpod.Runtime) (*entities.AutoUpdateReport, error) { - cid := ctr.ID() - rawImageName := ctr.RawImageName() - if rawImageName == "" { - return nil, fmt.Errorf("registry auto-updating container %q: raw-image name is empty", cid) +// report creates an auto-update report for the task. +func (t *task) report() *entities.AutoUpdateReport { + return &entities.AutoUpdateReport{ + ContainerID: t.container.ID(), + ContainerName: t.container.Name(), + ImageName: t.container.RawImageName(), + Policy: string(t.policy), + SystemdUnit: t.unit, + Updated: t.status, } +} - labels := ctr.Labels() - unit, exists := labels[systemdDefine.EnvVariable] - if !exists { - return nil, fmt.Errorf("auto-updating container %q: no %s label found", ctr.ID(), systemdDefine.EnvVariable) +// updateAvailable returns whether an update for the task is available. +func (t *task) updateAvailable(ctx context.Context) (bool, error) { + switch t.policy { + case PolicyRegistryImage: + return t.registryUpdateAvailable(ctx) + case PolicyLocalImage: + return t.localUpdateAvailable() + default: + return false, fmt.Errorf("unexpected auto-update policy %s for container %s", t.policy, t.container.ID()) } +} - report := &entities.AutoUpdateReport{ - ContainerID: cid, - ContainerName: ctr.Name(), - ImageName: rawImageName, - Policy: PolicyRegistryImage, - SystemdUnit: unit, - Updated: "failed", +// update the task according to its auto-update policy. +func (t *task) update(ctx context.Context) error { + switch t.policy { + case PolicyRegistryImage: + return t.registryUpdate(ctx) + case PolicyLocalImage: + // Nothing to do as the image is already available in the local storage. + return nil + default: + return fmt.Errorf("unexpected auto-update policy %s for container %s", t.policy, t.container.ID()) } +} - if _, updated := updatedRawImages[rawImageName]; updated { - logrus.Infof("Auto-updating container %q using registry image %q", cid, rawImageName) - if err := restartSystemdUnit(ctx, ctr, unit, conn); err != nil { - return report, err - } - report.Updated = "true" - return report, nil +// registryUpdateAvailable returns whether a new image on the registry is available. +func (t *task) registryUpdateAvailable(ctx context.Context) (bool, error) { + // The newer image has already been pulled for another task, so we know + // there's a newer one available. + if _, exists := t.auto.updatedRawImages[t.rawImageName]; exists { + return true, nil } - authfile := getAuthfilePath(ctr, options) - needsUpdate, err := newerRemoteImageAvailable(ctx, image, rawImageName, authfile) + remoteRef, err := docker.ParseReference("//" + t.rawImageName) if err != nil { - return report, fmt.Errorf("registry auto-updating container %q: image check for %q failed: %w", cid, rawImageName, err) - } - - if !needsUpdate { - report.Updated = "false" - return report, nil - } - - if options.DryRun { - report.Updated = "pending" - return report, nil - } - - if _, err := updateImage(ctx, runtime, rawImageName, authfile); err != nil { - return report, fmt.Errorf("registry auto-updating container %q: image update for %q failed: %w", cid, rawImageName, err) - } - updatedRawImages[rawImageName] = true - - logrus.Infof("Auto-updating container %q using registry image %q", cid, rawImageName) - updateErr := restartSystemdUnit(ctx, ctr, unit, conn) - if updateErr == nil { - report.Updated = "true" - return report, nil - } - - if !options.Rollback { - return report, updateErr - } - - // To fallback, simply retag the old image and restart the service. - if err := image.Tag(rawImageName); err != nil { - return report, fmt.Errorf("falling back to previous image: %w", err) - } - if err := restartSystemdUnit(ctx, ctr, unit, conn); err != nil { - return report, fmt.Errorf("restarting unit with old image during fallback: %w", err) + return false, err } - - report.Updated = "rolled back" - return report, nil + options := &libimage.HasDifferentDigestOptions{AuthFilePath: t.authfile} + return t.image.HasDifferentDigest(ctx, remoteRef, options) } -// autoUpdateRegistry updates the image/container according to the "local" policy. -func autoUpdateLocally(ctx context.Context, image *libimage.Image, ctr *libpod.Container, options *entities.AutoUpdateOptions, conn *dbus.Conn, runtime *libpod.Runtime) (*entities.AutoUpdateReport, error) { - cid := ctr.ID() - rawImageName := ctr.RawImageName() - if rawImageName == "" { - return nil, fmt.Errorf("locally auto-updating container %q: raw-image name is empty", cid) +// registryUpdate pulls down the image from the registry. +func (t *task) registryUpdate(ctx context.Context) error { + // The newer image has already been pulled for another task. + if _, exists := t.auto.updatedRawImages[t.rawImageName]; exists { + return nil } - labels := ctr.Labels() - unit, exists := labels[systemdDefine.EnvVariable] - if !exists { - return nil, fmt.Errorf("auto-updating container %q: no %s label found", ctr.ID(), systemdDefine.EnvVariable) + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = t.authfile + pullOptions.Writer = os.Stderr + if _, err := t.auto.runtime.LibimageRuntime().Pull(ctx, t.rawImageName, config.PullPolicyAlways, pullOptions); err != nil { + return err } - report := &entities.AutoUpdateReport{ - ContainerID: cid, - ContainerName: ctr.Name(), - ImageName: rawImageName, - Policy: PolicyLocalImage, - SystemdUnit: unit, - Updated: "failed", - } + t.auto.updatedRawImages[t.rawImageName] = true + return nil +} - needsUpdate, err := newerLocalImageAvailable(runtime, image, rawImageName) +// localUpdateAvailable returns whether a new image in the local storage is available. +func (t *task) localUpdateAvailable() (bool, error) { + localImg, _, err := t.auto.runtime.LibimageRuntime().LookupImage(t.rawImageName, nil) if err != nil { - return report, fmt.Errorf("locally auto-updating container %q: image check for %q failed: %w", cid, rawImageName, err) - } - - if !needsUpdate { - report.Updated = "false" - return report, nil - } - - if options.DryRun { - report.Updated = "pending" - return report, nil - } - - logrus.Infof("Auto-updating container %q using local image %q", cid, rawImageName) - updateErr := restartSystemdUnit(ctx, ctr, unit, conn) - if updateErr == nil { - report.Updated = "true" - return report, nil - } - - if !options.Rollback { - return report, updateErr + return false, err } + return localImg.Digest().String() != t.image.Digest().String(), nil +} +// rollbackImage rolls back the task's image to the previous version before the update. +func (t *task) rollbackImage() error { // To fallback, simply retag the old image and restart the service. - if err := image.Tag(rawImageName); err != nil { - return report, fmt.Errorf("falling back to previous image: %w", err) - } - if err := restartSystemdUnit(ctx, ctr, unit, conn); err != nil { - return report, fmt.Errorf("restarting unit with old image during fallback: %w", err) + if err := t.image.Tag(t.rawImageName); err != nil { + return err } - - report.Updated = "rolled back" - return report, nil + t.auto.updatedRawImages[t.rawImageName] = false + return nil } // restartSystemdUnit restarts the systemd unit the container is running in. -func restartSystemdUnit(ctx context.Context, ctr *libpod.Container, unit string, conn *dbus.Conn) error { +func (u *updater) restartSystemdUnit(ctx context.Context, unit string) error { restartChan := make(chan string) - if _, err := conn.RestartUnitContext(ctx, unit, "replace", restartChan); err != nil { - return fmt.Errorf("auto-updating container %q: restarting systemd unit %q failed: %w", ctr.ID(), unit, err) + if _, err := u.conn.RestartUnitContext(ctx, unit, "replace", restartChan); err != nil { + return err } // Wait for the restart to finish and actually check if it was @@ -329,25 +345,34 @@ func restartSystemdUnit(ctx context.Context, ctr *libpod.Container, unit string, switch result { case "done": - logrus.Infof("Successfully restarted systemd unit %q of container %q", unit, ctr.ID()) + logrus.Infof("Successfully restarted systemd unit %q", unit) return nil default: - return fmt.Errorf("auto-updating container %q: restarting systemd unit %q failed: expected %q but received %q", ctr.ID(), unit, "done", result) + return fmt.Errorf("expected %q but received %q", "done", result) } } -// imageContainersMap generates a map[image ID] -> [containers using the image] -// of all containers with a valid auto-update policy. -func imageContainersMap(runtime *libpod.Runtime) (map[string]policyMapper, []error) { - allContainers, err := runtime.GetAllContainers() +// assembleTasks assembles update tasks per unit and populates a mapping from +// `unit -> []*task` such that multiple containers _can_ run in a single unit. +func (u *updater) assembleTasks(ctx context.Context) []error { + // Assemble a map `image ID -> *libimage.Image` that we can consult + // later on for lookups. + imageMap, err := u.assembleImageMap(ctx) if err != nil { - return nil, []error{err} + return []error{err} } + allContainers, err := u.runtime.GetAllContainers() + if err != nil { + return []error{err} + } + + u.unitToTasks = make(map[string][]*task) + errors := []error{} - containerMap := make(map[string]policyMapper) - for _, ctr := range allContainers { + for _, c := range allContainers { + ctr := c state, err := ctr.State() if err != nil { errors = append(errors, err) @@ -358,77 +383,75 @@ func imageContainersMap(runtime *libpod.Runtime) (map[string]policyMapper, []err continue } - // Only update containers with the specific label/policy set. + // Check the container's auto-update policy which is configured + // as a label. labels := ctr.Labels() value, exists := labels[Label] if !exists { continue } - policy, err := LookupPolicy(value) if err != nil { errors = append(errors, err) continue } - - // Skip labels not related to autoupdate if policy == PolicyDefault { continue - } else { - id, _ := ctr.Image() - policyMap, exists := containerMap[id] - if !exists { - policyMap = make(map[Policy][]*libpod.Container) - } - policyMap[policy] = append(policyMap[policy], ctr) - containerMap[id] = policyMap - // Now we know that `ctr` is configured for auto updates. } - } - return containerMap, errors -} + // Make sure the container runs in a systemd unit which is + // stored as a label at container creation. + unit, exists := labels[systemdDefine.EnvVariable] + if !exists { + errors = append(errors, fmt.Errorf("auto-updating container %q: no %s label found", ctr.ID(), systemdDefine.EnvVariable)) + continue + } -// getAuthfilePath returns an authfile path, if set. The authfile label in the -// container, if set, as precedence over the one set in the options. -func getAuthfilePath(ctr *libpod.Container, options *entities.AutoUpdateOptions) string { - labels := ctr.Labels() - authFilePath, exists := labels[AuthfileLabel] - if exists { - return authFilePath - } - return options.Authfile -} + id, _ := ctr.Image() + image, exists := imageMap[id] + if !exists { + err := fmt.Errorf("internal error: no image found for ID %s", id) + errors = append(errors, err) + continue + } -// newerRemoteImageAvailable returns true if there corresponding image on the remote -// registry is newer. -func newerRemoteImageAvailable(ctx context.Context, img *libimage.Image, origName string, authfile string) (bool, error) { - remoteRef, err := docker.ParseReference("//" + origName) - if err != nil { - return false, err - } - options := &libimage.HasDifferentDigestOptions{AuthFilePath: authfile} - return img.HasDifferentDigest(ctx, remoteRef, options) -} + rawImageName := ctr.RawImageName() + if rawImageName == "" { + errors = append(errors, fmt.Errorf("locally auto-updating container %q: raw-image name is empty", ctr.ID())) + continue + } -// newerLocalImageAvailable returns true if the container and local image have different digests -func newerLocalImageAvailable(runtime *libpod.Runtime, img *libimage.Image, rawImageName string) (bool, error) { - localImg, _, err := runtime.LibimageRuntime().LookupImage(rawImageName, nil) - if err != nil { - return false, err + t := task{ + authfile: labels[AuthfileLabel], + auto: u, + container: ctr, + policy: policy, + image: image, + unit: unit, + rawImageName: rawImageName, + status: statusFailed, // must be updated later on + } + + // Add the task to the unit. + u.unitToTasks[unit] = append(u.unitToTasks[unit], &t) } - return localImg.Digest().String() != img.Digest().String(), nil -} -// updateImage pulls the specified image. -func updateImage(ctx context.Context, runtime *libpod.Runtime, name, authfile string) (*libimage.Image, error) { - pullOptions := &libimage.PullOptions{} - pullOptions.AuthFilePath = authfile - pullOptions.Writer = os.Stderr + return errors +} - pulledImages, err := runtime.LibimageRuntime().Pull(ctx, name, config.PullPolicyAlways, pullOptions) +// assembleImageMap creates a map from `image ID -> *libimage.Image` for image lookups. +func (u *updater) assembleImageMap(ctx context.Context) (map[string]*libimage.Image, error) { + listOptions := &libimage.ListImagesOptions{ + Filters: []string{"readonly=false"}, + } + imagesSlice, err := u.runtime.LibimageRuntime().ListImages(ctx, nil, listOptions) if err != nil { return nil, err } - return pulledImages[0], nil + imageMap := make(map[string]*libimage.Image) + for i := range imagesSlice { + imageMap[imagesSlice[i].ID()] = imagesSlice[i] + } + + return imageMap, nil } diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index 0664afc1b..9783a8e18 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -15,6 +15,8 @@ type RemoveOptions struct { Ignore *bool // Confirms if given name is a manifest list and removes it, otherwise returns error. LookupManifest *bool + // Does not remove dangling parent images + NoPrune *bool } //go:generate go run ../generator/generator.go DiffOptions diff --git a/pkg/bindings/images/types_remove_options.go b/pkg/bindings/images/types_remove_options.go index 559ebcfd5..8972ac93c 100644 --- a/pkg/bindings/images/types_remove_options.go +++ b/pkg/bindings/images/types_remove_options.go @@ -76,3 +76,18 @@ func (o *RemoveOptions) GetLookupManifest() bool { } return *o.LookupManifest } + +// WithNoPrune set field NoPrune to given value +func (o *RemoveOptions) WithNoPrune(value bool) *RemoveOptions { + o.NoPrune = &value + return o +} + +// GetNoPrune returns value of field NoPrune +func (o *RemoveOptions) GetNoPrune() bool { + if o.NoPrune == nil { + var z bool + return z + } + return *o.NoPrune +} diff --git a/pkg/bindings/kube/kube.go b/pkg/bindings/kube/kube.go index b9cc0efa7..db40c5134 100644 --- a/pkg/bindings/kube/kube.go +++ b/pkg/bindings/kube/kube.go @@ -51,7 +51,7 @@ func PlayWithBody(ctx context.Context, body io.Reader, options *PlayOptions) (*e return nil, err } - response, err := conn.DoRequest(ctx, body, http.MethodPost, "/kube/play", params, header) + response, err := conn.DoRequest(ctx, body, http.MethodPost, "/play/kube", params, header) if err != nil { return nil, err } @@ -85,7 +85,7 @@ func DownWithBody(ctx context.Context, body io.Reader) (*entities.KubePlayReport return nil, err } - response, err := conn.DoRequest(ctx, body, http.MethodDelete, "/kube/play", nil, nil) + response, err := conn.DoRequest(ctx, body, http.MethodDelete, "/play/kube", nil, nil) if err != nil { return nil, err } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index fdaf4e194..3ba507750 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -131,6 +131,7 @@ type RestartReport struct { } type RmOptions struct { + Filters map[string][]string All bool Depend bool Force bool diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index dad2dc6cc..21c1372b9 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -94,6 +94,8 @@ type ImageRemoveOptions struct { Ignore bool // Confirms if given name is a manifest list and removes it, otherwise returns error. LookupManifest bool + // NoPrune will not remove dangling images + NoPrune bool } // ImageRemoveReport is the response for removing one or more image(s) from storage diff --git a/pkg/domain/entities/reports/containers.go b/pkg/domain/entities/reports/containers.go index db9a66012..6759fc402 100644 --- a/pkg/domain/entities/reports/containers.go +++ b/pkg/domain/entities/reports/containers.go @@ -1,8 +1,9 @@ package reports type RmReport struct { - Id string `json:"Id"` //nolint:revive,stylecheck - Err error `json:"Err,omitempty"` + Id string `json:"Id"` //nolint:revive,stylecheck + Err error `json:"Err,omitempty"` + RawInput string } func RmReportsIds(r []*RmReport) []string { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index d7fd2e15d..900a51302 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -40,6 +40,7 @@ import ( // is specified. It also returns a list of the corresponding input name used to lookup each container. func getContainersAndInputByContext(all, latest bool, names []string, filters map[string][]string, runtime *libpod.Runtime) (ctrs []*libpod.Container, rawInput []string, err error) { var ctr *libpod.Container + var filteredCtrs []*libpod.Container ctrs = []*libpod.Container{} filterFuncs := make([]libpod.ContainerFilter, 0, len(filters)) @@ -58,7 +59,17 @@ func getContainersAndInputByContext(all, latest bool, names []string, filters ma } rawInput = []string{} for _, candidate := range ctrs { - rawInput = append(rawInput, candidate.ID()) + if len(names) > 0 { + for _, name := range names { + if candidate.ID() == name || candidate.Name() == name { + rawInput = append(rawInput, candidate.ID()) + filteredCtrs = append(filteredCtrs, candidate) + } + } + ctrs = filteredCtrs + } else { + rawInput = append(rawInput, candidate.ID()) + } } case all: ctrs, err = runtime.GetAllContainers() @@ -142,10 +153,10 @@ func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []stri if err != nil { return nil, err } - ctrMap := map[string]string{} + idToRawInput := map[string]string{} if len(rawInputs) == len(ctrs) { for i := range ctrs { - ctrMap[ctrs[i].ID()] = rawInputs[i] + idToRawInput[ctrs[i].ID()] = rawInputs[i] } } reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) @@ -158,7 +169,7 @@ func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []stri reports = append(reports, &entities.PauseUnpauseReport{ Id: c.ID(), Err: err, - RawInput: ctrMap[c.ID()], + RawInput: idToRawInput[c.ID()], }) } return reports, nil @@ -169,10 +180,10 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st if err != nil { return nil, err } - ctrMap := map[string]string{} + idToRawInput := map[string]string{} if len(rawInputs) == len(ctrs) { for i := range ctrs { - ctrMap[ctrs[i].ID()] = rawInputs[i] + idToRawInput[ctrs[i].ID()] = rawInputs[i] } } reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) @@ -185,7 +196,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st reports = append(reports, &entities.PauseUnpauseReport{ Id: c.ID(), Err: err, - RawInput: ctrMap[c.ID()], + RawInput: idToRawInput[c.ID()], }) } return reports, nil @@ -196,10 +207,10 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin if err != nil && !(options.Ignore && errors.Is(err, define.ErrNoSuchCtr)) { return nil, err } - ctrMap := map[string]string{} + idToRawInput := map[string]string{} if len(rawInputs) == len(ctrs) { for i := range ctrs { - ctrMap[ctrs[i].ID()] = rawInputs[i] + idToRawInput[ctrs[i].ID()] = rawInputs[i] } } errMap, err := parallelctr.ContainerOp(ctx, ctrs, func(c *libpod.Container) error { @@ -245,7 +256,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin if options.All { report.RawInput = ctr.ID() } else { - report.RawInput = ctrMap[ctr.ID()] + report.RawInput = idToRawInput[ctr.ID()] } report.Err = err reports = append(reports, report) @@ -275,10 +286,10 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin if err != nil { return nil, err } - ctrMap := map[string]string{} + idToRawInput := map[string]string{} if len(rawInputs) == len(ctrs) { for i := range ctrs { - ctrMap[ctrs[i].ID()] = rawInputs[i] + idToRawInput[ctrs[i].ID()] = rawInputs[i] } } reports := make([]*entities.KillReport, 0, len(ctrs)) @@ -291,7 +302,7 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin reports = append(reports, &entities.KillReport{ Id: con.ID(), Err: err, - RawInput: ctrMap[con.ID()], + RawInput: idToRawInput[con.ID()], }) } return reports, nil @@ -360,7 +371,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, // this will fail and code will fall through to removing the container from libpod.` tmpNames := []string{} for _, ctr := range names { - report := reports.RmReport{Id: ctr} + report := reports.RmReport{Id: ctr, RawInput: ctr} report.Err = ic.Libpod.RemoveStorageContainer(ctr, options.Force) //nolint:gocritic if report.Err == nil { @@ -381,7 +392,16 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, } names = tmpNames - ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod) + ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, names, options.Filters, ic.Libpod) + if err != nil && !(options.Ignore && errors.Is(err, define.ErrNoSuchCtr)) { + return nil, err + } + idToRawInput := map[string]string{} + if len(rawInputs) == len(ctrs) { + for i := range ctrs { + idToRawInput[ctrs[i].ID()] = rawInputs[i] + } + } if err != nil && !(options.Ignore && errors.Is(err, define.ErrNoSuchCtr)) { // Failed to get containers. If force is specified, get the containers ID // and evict them @@ -391,7 +411,10 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, for _, ctr := range names { logrus.Debugf("Evicting container %q", ctr) - report := reports.RmReport{Id: ctr} + report := reports.RmReport{ + Id: ctr, + RawInput: idToRawInput[ctr], + } _, err := ic.Libpod.EvictContainer(ctx, ctr, options.Volumes) if err != nil { if options.Ignore && errors.Is(err, define.ErrNoSuchCtr) { @@ -461,6 +484,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, report := new(reports.RmReport) report.Id = ctr.ID() report.Err = err + report.RawInput = idToRawInput[ctr.ID()] rmReports = append(rmReports, report) } return rmReports, nil @@ -900,38 +924,7 @@ func (ic *ContainerEngine) ContainerExecDetached(ctx context.Context, nameOrID s func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { reports := []*entities.ContainerStartReport{} var exitCode = define.ExecErrorCodeGeneric - containersNamesOrIds := namesOrIds - all := options.All - if len(options.Filters) > 0 { - all = false - filterFuncs := make([]libpod.ContainerFilter, 0, len(options.Filters)) - if len(options.Filters) > 0 { - for k, v := range options.Filters { - generatedFunc, err := dfilters.GenerateContainerFilterFuncs(k, v, ic.Libpod) - if err != nil { - return nil, err - } - filterFuncs = append(filterFuncs, generatedFunc) - } - } - candidates, err := ic.Libpod.GetContainers(filterFuncs...) - if err != nil { - return nil, err - } - containersNamesOrIds = []string{} - for _, candidate := range candidates { - if options.All { - containersNamesOrIds = append(containersNamesOrIds, candidate.ID()) - continue - } - for _, nameOrID := range namesOrIds { - if nameOrID == candidate.ID() || nameOrID == candidate.Name() { - containersNamesOrIds = append(containersNamesOrIds, nameOrID) - } - } - } - } - ctrs, rawInputs, err := getContainersAndInputByContext(all, options.Latest, containersNamesOrIds, options.Filters, ic.Libpod) + ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, namesOrIds, options.Filters, ic.Libpod) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 94178a8e2..1f34cbd01 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -565,6 +565,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie libimageOptions.Force = opts.Force libimageOptions.Ignore = opts.Ignore libimageOptions.LookupManifest = opts.LookupManifest + libimageOptions.NoPrune = opts.NoPrune if !opts.All { libimageOptions.Filters = append(libimageOptions.Filters, "intermediate=false") } @@ -581,7 +582,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie rmErrors = libimageErrors - return + return report, rmErrors } // Shutdown Libpod engine diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 13768f017..d49f029d5 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -61,9 +61,9 @@ func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []stri if err != nil { return nil, err } - ctrMap := map[string]string{} + idToRawInput := map[string]string{} for i := range ctrs { - ctrMap[ctrs[i].ID] = rawInputs[i] + idToRawInput[ctrs[i].ID] = rawInputs[i] } reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { @@ -75,7 +75,7 @@ func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []stri reports = append(reports, &entities.PauseUnpauseReport{ Id: c.ID, Err: err, - RawInput: ctrMap[c.ID], + RawInput: idToRawInput[c.ID], }) } return reports, nil @@ -86,9 +86,9 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st if err != nil { return nil, err } - ctrMap := map[string]string{} + idToRawInput := map[string]string{} for i := range ctrs { - ctrMap[ctrs[i].ID] = rawInputs[i] + idToRawInput[ctrs[i].ID] = rawInputs[i] } reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { @@ -100,7 +100,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st reports = append(reports, &entities.PauseUnpauseReport{ Id: c.ID, Err: err, - RawInput: ctrMap[c.ID], + RawInput: idToRawInput[c.ID], }) } return reports, nil @@ -111,9 +111,9 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin if err != nil { return nil, err } - ctrMap := map[string]string{} + idToRawInput := map[string]string{} for i := range ctrs { - ctrMap[ctrs[i].ID] = rawInputs[i] + idToRawInput[ctrs[i].ID] = rawInputs[i] } options := new(containers.StopOptions).WithIgnore(opts.Ignore) if to := opts.Timeout; to != nil { @@ -123,7 +123,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin for _, c := range ctrs { report := entities.StopReport{ Id: c.ID, - RawInput: ctrMap[c.ID], + RawInput: idToRawInput[c.ID], } if err = containers.Stop(ic.ClientCtx, c.ID, options); err != nil { // These first two are considered non-fatal under the right conditions @@ -154,9 +154,9 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin if err != nil { return nil, err } - ctrMap := map[string]string{} + idToRawInput := map[string]string{} for i := range ctrs { - ctrMap[ctrs[i].ID] = rawInputs[i] + idToRawInput[ctrs[i].ID] = rawInputs[i] } options := new(containers.KillOptions).WithSignal(opts.Signal) reports := make([]*entities.KillReport, 0, len(ctrs)) @@ -169,7 +169,7 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin reports = append(reports, &entities.KillReport{ Id: c.ID, Err: err, - RawInput: ctrMap[c.ID], + RawInput: idToRawInput[c.ID], }) } return reports, nil @@ -208,11 +208,18 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, toRemove := []string{} alreadyRemoved := make(map[string]bool) // Avoids trying to remove already removed containers - if opts.All { - ctrs, err := getContainersByContext(ic.ClientCtx, opts.All, opts.Ignore, nil) + idToRawInput := map[string]string{} + + if opts.All || len(opts.Filters) > 0 { + ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, opts.Ignore, nil, opts.Filters) if err != nil { return nil, err } + if len(rawInputs) == len(ctrs) { + for i := range ctrs { + idToRawInput[ctrs[i].ID] = rawInputs[i] + } + } for _, c := range ctrs { toRemove = append(toRemove, c.ID) } @@ -225,10 +232,15 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, // instead of the ID. Since this can only happen // with external containers, it poses no threat // to the `alreadyRemoved` checks below. - ctrs, err := getContainersByContext(ic.ClientCtx, false, true, []string{ctr}) + ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, false, true, []string{ctr}, opts.Filters) if err != nil { return nil, err } + if len(rawInputs) == len(ctrs) { + for i := range ctrs { + idToRawInput[ctrs[i].ID] = rawInputs[i] + } + } id := ctr if len(ctrs) == 1 { id = ctrs[0].ID @@ -238,13 +250,20 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, } rmReports := make([]*reports.RmReport, 0, len(toRemove)) - for _, nameOrID := range toRemove { - if alreadyRemoved[nameOrID] { + for _, rmCtr := range toRemove { + if alreadyRemoved[rmCtr] { continue } - newReports, err := containers.Remove(ic.ClientCtx, nameOrID, options) + if ctr, exist := idToRawInput[rmCtr]; exist { + rmCtr = ctr + } + newReports, err := containers.Remove(ic.ClientCtx, rmCtr, options) if err != nil { - rmReports = append(rmReports, &reports.RmReport{Id: nameOrID, Err: err}) + rmReports = append(rmReports, &reports.RmReport{ + Id: rmCtr, + Err: err, + RawInput: idToRawInput[rmCtr], + }) continue } for i := range newReports { @@ -648,36 +667,7 @@ func logIfRmError(id string, err error, reports []*reports.RmReport) { func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { reports := []*entities.ContainerStartReport{} var exitCode = define.ExecErrorCodeGeneric - containersNamesOrIds := namesOrIds - all := options.All - if len(options.Filters) > 0 { - all = false - containersNamesOrIds = []string{} - opts := new(containers.ListOptions).WithFilters(options.Filters).WithAll(true) - candidates, listErr := containers.List(ic.ClientCtx, opts) - if listErr != nil { - return nil, listErr - } - for _, candidate := range candidates { - if options.All { - containersNamesOrIds = append(containersNamesOrIds, candidate.ID) - continue - } - for _, nameOrID := range namesOrIds { - if nameOrID == candidate.ID { - containersNamesOrIds = append(containersNamesOrIds, nameOrID) - continue - } - for _, containerName := range candidate.Names { - if containerName == nameOrID { - containersNamesOrIds = append(containersNamesOrIds, nameOrID) - continue - } - } - } - } - } - ctrs, err := getContainersByContext(ic.ClientCtx, all, false, containersNamesOrIds) + ctrs, namesOrIds, err := getContainersAndInputByContext(ic.ClientCtx, options.All, false, namesOrIds, options.Filters) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index 9ff1641f0..90d558119 100644 --- a/pkg/domain/infra/tunnel/helpers.go +++ b/pkg/domain/infra/tunnel/helpers.go @@ -14,7 +14,7 @@ import ( // FIXME: the `ignore` parameter is very likely wrong here as it should rather // be used on *errors* from operations such as remove. -func getContainersByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string) ([]entities.ListContainer, error) { +func getContainersByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string) ([]entities.ListContainer, error) { //nolint:unparam ctrs, _, err := getContainersAndInputByContext(contextWithConnection, all, ignore, namesOrIDs, nil) return ctrs, err } @@ -31,8 +31,17 @@ func getContainersAndInputByContext(contextWithConnection context.Context, all, rawInputs := []string{} switch { case len(filters) > 0: + namesOrIDs = nil for i := range allContainers { - namesOrIDs = append(namesOrIDs, allContainers[i].ID) + if len(namesOrIDs) > 0 { + for _, name := range namesOrIDs { + if name == allContainers[i].ID { + namesOrIDs = append(namesOrIDs, allContainers[i].ID) + } + } + } else { + namesOrIDs = append(namesOrIDs, allContainers[i].ID) + } } case all: for i := range allContainers { diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 4f79325fd..4fecefaa3 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -28,7 +28,7 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.Boo } func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, []error) { - options := new(images.RemoveOptions).WithForce(opts.Force).WithIgnore(opts.Ignore).WithAll(opts.All).WithLookupManifest(opts.LookupManifest) + options := new(images.RemoveOptions).WithForce(opts.Force).WithIgnore(opts.Ignore).WithAll(opts.All).WithLookupManifest(opts.LookupManifest).WithNoPrune(opts.NoPrune) return images.Remove(ir.ClientCtx, imagesArg, options) } diff --git a/pkg/k8s.io/api/core/v1/types.go b/pkg/k8s.io/api/core/v1/types.go index 39a675dae..384965769 100644 --- a/pkg/k8s.io/api/core/v1/types.go +++ b/pkg/k8s.io/api/core/v1/types.go @@ -56,7 +56,8 @@ type VolumeSource struct { // ConfigMap represents a configMap that should populate this volume // +optional ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"` - Secret *SecretVolumeSource + // Secret represents a secret that should be mounted as a volume + Secret *SecretVolumeSource `json:"secret,omitempty"` } // PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 189723ac7..8f6ef7a43 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -56,7 +56,9 @@ rm -f /etc/systemd/system/getty.target.wants/getty@tty1.service rm -f /etc/systemd/system/multi-user.target.wants/systemd-resolved.service rm -f /etc/systemd/system/dbus-org.freedesktop.resolve1.service ln -fs /dev/null /etc/systemd/system/console-getty.service +ln -fs /dev/null /etc/systemd/system/systemd-oomd.socket mkdir -p /etc/systemd/system/systemd-sysusers.service.d/ +echo CREATE_MAIL_SPOOL=no >> /etc/default/useradd adduser -m [USER] -G wheel mkdir -p /home/[USER]/.config/systemd/[USER]/ chown [USER]:[USER] /home/[USER]/.config @@ -89,9 +91,18 @@ fi const enterns = "#!/bin/bash\n" + sysdpid + ` if [ ! -z "$SYSDPID" ] && [ "$SYSDPID" != "1" ]; then - nsenter -m -p -t $SYSDPID "$@" -fi -` + NSENTER=("nsenter" "-m" "-p" "-t" "$SYSDPID" "--wd=$PWD") + + if [ "$UID" != "0" ]; then + NSENTER=("sudo" "${NSENTER[@]}") + if [ "$#" != "0" ]; then + NSENTER+=("sudo" "-u" "$USER") + else + NSENTER+=("su" "-l" "$USER") + fi + fi + "${NSENTER[@]}" "$@" +fi` const waitTerm = sysdpid + ` if [ ! -z "$SYSDPID" ]; then @@ -99,6 +110,10 @@ if [ ! -z "$SYSDPID" ]; then fi ` +const wslConf = `[user] +default=[USER] +` + // WSL kernel does not have sg and crypto_user modules const overrideSysusers = `[Service] LoadCredential= @@ -349,14 +364,6 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { return false, err } - if err := v.writeConfig(); err != nil { - return false, err - } - - if err := setupConnections(v, opts, sshDir); err != nil { - return false, err - } - dist, err := provisionWSLDist(v) if err != nil { return false, err @@ -375,6 +382,17 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { return false, err } + // Cycle so that user change goes into effect + _ = terminateDist(dist) + + if err := v.writeConfig(); err != nil { + return false, err + } + + if err := setupConnections(v, opts, sshDir); err != nil { + return false, err + } + return true, nil } @@ -450,12 +468,12 @@ func provisionWSLDist(v *MachineVM) (string, error) { dist := toDist(v.Name) fmt.Println("Importing operating system into WSL (this may take a few minutes on a new WSL install)...") - if err = runCmdPassThrough("wsl", "--import", dist, distTarget, v.ImagePath); err != nil { + if err = runCmdPassThrough("wsl", "--import", dist, distTarget, v.ImagePath, "--version", "2"); err != nil { return "", fmt.Errorf("the WSL import of guest OS failed: %w", err) } // Fixes newuidmap - if err = runCmdPassThrough("wsl", "-d", dist, "rpm", "-q", "--restore", "shadow-utils", "2>/dev/null"); err != nil { + if err = wslInvoke(dist, "rpm", "-q", "--restore", "shadow-utils", "2>/dev/null"); err != nil { return "", fmt.Errorf("package permissions restore of shadow-utils on guest OS failed: %w", err) } @@ -463,7 +481,7 @@ func provisionWSLDist(v *MachineVM) (string, error) { // operation when mount was not present on the initial start. Force a cycle so that it won't // repeatedly complain. if winVersionAtLeast(10, 0, 22000) { - if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil { + if err := terminateDist(dist); err != nil { logrus.Warnf("could not cycle WSL dist: %s", err.Error()) } } @@ -478,16 +496,16 @@ func createKeys(v *MachineVM, dist string, sshDir string) error { return fmt.Errorf("could not create ssh directory: %w", err) } - if err := runCmdPassThrough("wsl", "--terminate", dist); err != nil { + if err := terminateDist(dist); err != nil { return fmt.Errorf("could not cycle WSL dist: %w", err) } - key, err := machine.CreateSSHKeysPrefix(sshDir, v.Name, true, true, "wsl", "-d", dist) + key, err := wslCreateKeys(sshDir, v.Name, dist) if err != nil { return fmt.Errorf("could not create ssh keys: %w", err) } - if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", "mkdir -p /root/.ssh;"+ + if err := wslPipe(key+"\n", dist, "sh", "-c", "mkdir -p /root/.ssh;"+ "cat >> /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys"); err != nil { return fmt.Errorf("could not create root authorized keys on guest OS: %w", err) } @@ -495,7 +513,7 @@ func createKeys(v *MachineVM, dist string, sshDir string) error { userAuthCmd := withUser("mkdir -p /home/[USER]/.ssh;"+ "cat >> /home/[USER]/.ssh/authorized_keys; chown -R [USER]:[USER] /home/[USER]/.ssh;"+ "chmod 600 /home/[USER]/.ssh/authorized_keys", user) - if err := pipeCmdPassThrough("wsl", key+"\n", "-d", dist, "sh", "-c", userAuthCmd); err != nil { + if err := wslPipe(key+"\n", dist, "sh", "-c", userAuthCmd); err != nil { return fmt.Errorf("could not create '%s' authorized keys on guest OS: %w", v.RemoteUsername, err) } @@ -504,25 +522,25 @@ func createKeys(v *MachineVM, dist string, sshDir string) error { func configureSystem(v *MachineVM, dist string) error { user := v.RemoteUsername - if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", fmt.Sprintf(appendPort, v.Port, v.Port)); err != nil { + if err := wslInvoke(dist, "sh", "-c", fmt.Sprintf(appendPort, v.Port, v.Port)); err != nil { return fmt.Errorf("could not configure SSH port for guest OS: %w", err) } - if err := pipeCmdPassThrough("wsl", withUser(configServices, user), "-d", dist, "sh"); err != nil { + if err := wslPipe(withUser(configServices, user), dist, "sh"); err != nil { return fmt.Errorf("could not configure systemd settings for guest OS: %w", err) } - if err := pipeCmdPassThrough("wsl", sudoers, "-d", dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil { + if err := wslPipe(sudoers, dist, "sh", "-c", "cat >> /etc/sudoers"); err != nil { return fmt.Errorf("could not add wheel to sudoers: %w", err) } - if err := pipeCmdPassThrough("wsl", overrideSysusers, "-d", dist, "sh", "-c", + if err := wslPipe(overrideSysusers, dist, "sh", "-c", "cat > /etc/systemd/system/systemd-sysusers.service.d/override.conf"); err != nil { return fmt.Errorf("could not generate systemd-sysusers override for guest OS: %w", err) } lingerCmd := withUser("cat > /home/[USER]/.config/systemd/[USER]/linger-example.service", user) - if err := pipeCmdPassThrough("wsl", lingerService, "-d", dist, "sh", "-c", lingerCmd); err != nil { + if err := wslPipe(lingerService, dist, "sh", "-c", lingerCmd); err != nil { return fmt.Errorf("could not generate linger service for guest OS: %w", err) } @@ -530,24 +548,28 @@ func configureSystem(v *MachineVM, dist string) error { return err } - if err := pipeCmdPassThrough("wsl", withUser(lingerSetup, user), "-d", dist, "sh"); err != nil { - return fmt.Errorf("could not configure systemd settomgs for guest OS: %w", err) + if err := wslPipe(withUser(lingerSetup, user), dist, "sh"); err != nil { + return fmt.Errorf("could not configure systemd settings for guest OS: %w", err) } - if err := pipeCmdPassThrough("wsl", containersConf, "-d", dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil { + if err := wslPipe(containersConf, dist, "sh", "-c", "cat > /etc/containers/containers.conf"); err != nil { return fmt.Errorf("could not create containers.conf for guest OS: %w", err) } - if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil { + if err := wslInvoke(dist, "sh", "-c", "echo wsl > /etc/containers/podman-machine"); err != nil { return fmt.Errorf("could not create podman-machine file for guest OS: %w", err) } + if err := wslPipe(withUser(wslConf, user), dist, "sh", "-c", "cat > /etc/wsl.conf"); err != nil { + return fmt.Errorf("could not configure wsl config for guest OS: %w", err) + } + return nil } func configureProxy(dist string, useProxy bool) error { if !useProxy { - _ = runCmdPassThrough("wsl", "-d", dist, "sh", "-c", clearProxySettings) + _ = wslInvoke(dist, "sh", "-c", clearProxySettings) return nil } var content string @@ -561,17 +583,17 @@ func configureProxy(dist string, useProxy bool) error { } } - if err := pipeCmdPassThrough("wsl", content, "-d", dist, "sh", "-c", proxyConfigAttempt); err != nil { + if err := wslPipe(content, dist, "sh", "-c", proxyConfigAttempt); err != nil { const failMessage = "Failure creating proxy configuration" if exitErr, isExit := err.(*exec.ExitError); isExit && exitErr.ExitCode() != 42 { return fmt.Errorf("%v: %w", failMessage, err) } fmt.Println("Installing proxy support") - _ = pipeCmdPassThrough("wsl", proxyConfigSetup, "-d", dist, "sh", "-c", + _ = wslPipe(proxyConfigSetup, dist, "sh", "-c", "cat > /usr/local/bin/proxyinit; chmod 755 /usr/local/bin/proxyinit") - if err = pipeCmdPassThrough("wsl", content, "-d", dist, "/usr/local/bin/proxyinit"); err != nil { + if err = wslPipe(content, dist, "/usr/local/bin/proxyinit"); err != nil { return fmt.Errorf("%v: %w", failMessage, err) } } @@ -581,7 +603,7 @@ func configureProxy(dist string, useProxy bool) error { func enableUserLinger(v *MachineVM, dist string) error { lingerCmd := "mkdir -p /var/lib/systemd/linger; touch /var/lib/systemd/linger/" + v.RemoteUsername - if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", lingerCmd); err != nil { + if err := wslInvoke(dist, "sh", "-c", lingerCmd); err != nil { return fmt.Errorf("could not enable linger for remote user on guest OS: %w", err) } @@ -589,26 +611,26 @@ func enableUserLinger(v *MachineVM, dist string) error { } func installScripts(dist string) error { - if err := pipeCmdPassThrough("wsl", enterns, "-d", dist, "sh", "-c", + if err := wslPipe(enterns, dist, "sh", "-c", "cat > /usr/local/bin/enterns; chmod 755 /usr/local/bin/enterns"); err != nil { return fmt.Errorf("could not create enterns script for guest OS: %w", err) } - if err := pipeCmdPassThrough("wsl", profile, "-d", dist, "sh", "-c", + if err := wslPipe(profile, dist, "sh", "-c", "cat > /etc/profile.d/enterns.sh"); err != nil { return fmt.Errorf("could not create motd profile script for guest OS: %w", err) } - if err := pipeCmdPassThrough("wsl", wslmotd, "-d", dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil { + if err := wslPipe(wslmotd, dist, "sh", "-c", "cat > /etc/wslmotd"); err != nil { return fmt.Errorf("could not create a WSL MOTD for guest OS: %w", err) } - if err := pipeCmdPassThrough("wsl", bootstrap, "-d", dist, "sh", "-c", + if err := wslPipe(bootstrap, dist, "sh", "-c", "cat > /root/bootstrap; chmod 755 /root/bootstrap"); err != nil { return fmt.Errorf("could not create bootstrap script for guest OS: %w", err) } - if err := pipeCmdPassThrough("wsl", proxyConfigSetup, "-d", dist, "sh", "-c", + if err := wslPipe(proxyConfigSetup, dist, "sh", "-c", "cat > /usr/local/bin/proxyinit; chmod 755 /usr/local/bin/proxyinit"); err != nil { return fmt.Errorf("could not create proxyinit script for guest OS: %w", err) } @@ -844,6 +866,22 @@ func withUser(s string, user string) string { return strings.ReplaceAll(s, "[USER]", user) } +func wslInvoke(dist string, arg ...string) error { + newArgs := []string{"-u", "root", "-d", dist} + newArgs = append(newArgs, arg...) + return runCmdPassThrough("wsl", newArgs...) +} + +func wslPipe(input string, dist string, arg ...string) error { + newArgs := []string{"-u", "root", "-d", dist} + newArgs = append(newArgs, arg...) + return pipeCmdPassThrough("wsl", input, newArgs...) +} + +func wslCreateKeys(sshDir string, name string, dist string) (string, error) { + return machine.CreateSSHKeysPrefix(sshDir, name, true, true, "wsl", "-u", "root", "-d", dist) +} + func runCmdPassThrough(name string, arg ...string) error { logrus.Debugf("Running command: %s %v", name, arg) cmd := exec.Command(name, arg...) @@ -935,7 +973,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { return err } - err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap") + err := wslInvoke(dist, "/root/bootstrap") if err != nil { return fmt.Errorf("the WSL bootstrap script failed: %w", err) } @@ -1124,7 +1162,7 @@ func isWSLRunning(dist string) (bool, error) { } func isSystemdRunning(dist string) (bool, error) { - cmd := exec.Command("wsl", "-d", dist, "sh") + cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh") cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n") out, err := cmd.StdoutPipe() if err != nil { @@ -1174,13 +1212,13 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error()) } - cmd := exec.Command("wsl", "-d", dist, "sh") + cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh") cmd.Stdin = strings.NewReader(waitTerm) if err = cmd.Start(); err != nil { return fmt.Errorf("executing wait command: %w", err) } - exitCmd := exec.Command("wsl", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0") + exitCmd := exec.Command("wsl", "-u", "root", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0") if err = exitCmd.Run(); err != nil { return fmt.Errorf("stopping sysd: %w", err) } @@ -1189,12 +1227,12 @@ func (v *MachineVM) Stop(name string, _ machine.StopOptions) error { return err } - cmd = exec.Command("wsl", "--terminate", dist) - if err = cmd.Run(); err != nil { - return err - } + return terminateDist(dist) +} - return nil +func terminateDist(dist string) error { + cmd := exec.Command("wsl", "--terminate", dist) + return cmd.Run() } func (v *MachineVM) State(bypass bool) (machine.Status, error) { @@ -1438,7 +1476,7 @@ func getCPUs(vm *MachineVM) (uint64, error) { if run, _ := isWSLRunning(dist); !run { return 0, nil } - cmd := exec.Command("wsl", "-d", dist, "nproc") + cmd := exec.Command("wsl", "-u", "root", "-d", dist, "nproc") out, err := cmd.StdoutPipe() if err != nil { return 0, err @@ -1462,7 +1500,7 @@ func getMem(vm *MachineVM) (uint64, error) { if run, _ := isWSLRunning(dist); !run { return 0, nil } - cmd := exec.Command("wsl", "-d", dist, "cat", "/proc/meminfo") + cmd := exec.Command("wsl", "-u", "root", "-d", dist, "cat", "/proc/meminfo") out, err := cmd.StdoutPipe() if err != nil { return 0, err diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 8cfac924b..20cacc10d 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -191,16 +191,24 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat // - "container" denotes the container should join the VM of the SandboxID // (the infra container) if len(s.Pod) > 0 { - annotations[ann.SandboxID] = s.Pod + p, err := r.LookupPod(s.Pod) + if err != nil { + return nil, err + } + sandboxID := p.ID() + if p.HasInfraContainer() { + infra, err := p.InfraContainer() + if err != nil { + return nil, err + } + sandboxID = infra.ID() + } + annotations[ann.SandboxID] = sandboxID annotations[ann.ContainerType] = ann.ContainerTypeContainer // Check if this is an init-ctr and if so, check if // the pod is running. we do not want to add init-ctrs to // a running pod because it creates confusion for us. if len(s.InitContainerType) > 0 { - p, err := r.LookupPod(s.Pod) - if err != nil { - return nil, err - } containerStatuses, err := p.Status() if err != nil { return nil, err diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 66905202d..1f8c519b7 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -378,6 +378,9 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst fs.StringArrayP("env", "e", nil, "") fs.String("sdnotify", "", "") fs.String("restart", "", "") + // have to define extra -h flag to prevent help error when parsing -h hostname + // https://github.com/containers/podman/issues/15124 + fs.StringP("help", "h", "", "") if err := fs.Parse(remainingCmd); err != nil { return "", fmt.Errorf("parsing remaining command-line arguments: %w", err) } diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 9a9e03a58..873cbfbb3 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -815,6 +815,37 @@ NotifyAccess=all WantedBy=default.target ` + goodNewWithHostname := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run \ + --cidfile=%t/%n.ctr-id \ + --cgroups=no-conmon \ + --rm \ + --sdnotify=conmon \ + -d \ + -h hostname awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id +Type=notify +NotifyAccess=all + +[Install] +WantedBy=default.target +` + templateGood := `# container-foo@.service # autogenerated by Podman CI @@ -1432,6 +1463,25 @@ WantedBy=default.target false, false, }, + {"good with -h hostname", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerNameOrID: "jadda-jadda", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + CreateCommand: []string{"I'll get stripped", "create", "-h", "hostname", "awesome-image:latest"}, + EnvVariable: define.EnvVariable, + }, + goodNewWithHostname, + true, + false, + false, + false, + }, {"good template", containerInfo{ Executable: "/usr/bin/podman", diff --git a/podman.spec.rpkg b/podman.spec.rpkg index 7068c9745..f27b31108 100644 --- a/podman.spec.rpkg +++ b/podman.spec.rpkg @@ -59,6 +59,7 @@ BuildRequires: go-rpm-macros %endif %if 0%{?rhel} <= 8 BuildRequires: pkgconfig(devmapper) +BuildRequires: python3 %endif BuildRequires: gpgme-devel BuildRequires: libassuan-devel diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 845aa60ce..5e9881c4f 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -1228,4 +1228,27 @@ USER test1` Expect(pod.Spec.Containers[0].Env).To(HaveLen(2)) }) + + It("podman generate kube omit secret if empty", func() { + dir, err := os.MkdirTemp(tempdir, "podman") + Expect(err).Should(BeNil()) + + defer os.RemoveAll(dir) + + podCreate := podmanTest.Podman([]string{"run", "-d", "--pod", "new:" + "noSecretsPod", "--name", "noSecretsCtr", "--volume", dir + ":/foobar", ALPINE}) + podCreate.WaitWithDefaultTimeout() + Expect(podCreate).Should(Exit(0)) + + kube := podmanTest.Podman([]string{"generate", "kube", "noSecretsPod"}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + Expect(kube.OutputToString()).ShouldNot(ContainSubstring("secret")) + + pod := new(v1.Pod) + err = yaml.Unmarshal(kube.Out.Contents(), pod) + Expect(err).To(BeNil()) + + Expect(pod.Spec.Volumes[0].Secret).To(BeNil()) + }) }) diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go index 893210a1f..e80772aed 100644 --- a/test/e2e/manifest_test.go +++ b/test/e2e/manifest_test.go @@ -173,13 +173,15 @@ var _ = Describe("Podman manifest", func() { session = podmanTest.Podman([]string{"manifest", "add", "foo", imageListInstance}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - session = podmanTest.Podman([]string{"manifest", "annotate", "--arch", "bar", "foo", imageListARM64InstanceDigest}) + session = podmanTest.Podman([]string{"manifest", "annotate", "--annotation", "hello=world", "--arch", "bar", "foo", imageListARM64InstanceDigest}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) session = podmanTest.Podman([]string{"manifest", "inspect", "foo"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring(`"architecture": "bar"`)) + // Check added annotation + Expect(session.OutputToString()).To(ContainSubstring(`"hello": "world"`)) }) It("remove digest", func() { diff --git a/test/e2e/mount_rootless_test.go b/test/e2e/mount_rootless_test.go index 994a5899b..b0452deda 100644 --- a/test/e2e/mount_rootless_test.go +++ b/test/e2e/mount_rootless_test.go @@ -52,9 +52,16 @@ var _ = Describe("Podman mount", func() { Expect(setup).Should(Exit(0)) cid := setup.OutputToString() - session := podmanTest.Podman([]string{"unshare", PODMAN_BINARY, "mount", cid}) + // command: podman <options> unshare podman <options> mount cid + args := []string{"unshare", podmanTest.PodmanBinary} + opts := podmanTest.PodmanMakeOptions([]string{"mount", cid}, false, false) + args = append(args, opts...) + + // container root file system location is /tmp/... because "--root /tmp/..." + session := podmanTest.Podman(args) session.WaitWithDefaultTimeout() - Expect(setup).Should(Exit(0)) + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("/tmp")) }) It("podman image mount", func() { @@ -71,8 +78,15 @@ var _ = Describe("Podman mount", func() { setup.WaitWithDefaultTimeout() Expect(setup).Should(Exit(0)) - session := podmanTest.Podman([]string{"unshare", PODMAN_BINARY, "image", "mount", ALPINE}) + // command: podman <options> unshare podman <options> image mount ALPINE + args := []string{"unshare", podmanTest.PodmanBinary} + opts := podmanTest.PodmanMakeOptions([]string{"image", "mount", ALPINE}, false, false) + args = append(args, opts...) + + // image location is /tmp/... because "--root /tmp/..." + session := podmanTest.Podman(args) session.WaitWithDefaultTimeout() - Expect(setup).Should(Exit(0)) + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("/tmp")) }) }) diff --git a/test/e2e/rm_test.go b/test/e2e/rm_test.go index 7dbe5fed8..e76451824 100644 --- a/test/e2e/rm_test.go +++ b/test/e2e/rm_test.go @@ -1,6 +1,7 @@ package integration import ( + "fmt" "io/ioutil" "os" @@ -51,6 +52,7 @@ var _ = Describe("Podman rm", func() { result := podmanTest.Podman([]string{"rm", cid}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(2)) + Expect(result.ErrorToString()).To(ContainSubstring("containers cannot be removed without force")) }) It("podman rm created container", func() { @@ -140,11 +142,9 @@ var _ = Describe("Podman rm", func() { output := result.OutputToString() Expect(output).To(ContainSubstring(cid)) Expect(podmanTest.NumberOfContainers()).To(Equal(1)) - }) It("podman rm --cidfile", func() { - tmpDir, err := ioutil.TempDir("", "") Expect(err).To(BeNil()) tmpFile := tmpDir + "cid" @@ -166,7 +166,6 @@ var _ = Describe("Podman rm", func() { }) It("podman rm multiple --cidfile", func() { - tmpDir, err := ioutil.TempDir("", "") Expect(err).To(BeNil()) tmpFile1 := tmpDir + "cid-1" @@ -201,18 +200,22 @@ var _ = Describe("Podman rm", func() { result := podmanTest.Podman([]string{"rm", "--cidfile", "foobar", "--latest"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(125)) + Expect(result.ErrorToString()).To(ContainSubstring("--all, --latest, and --cidfile cannot be used together")) result = podmanTest.Podman([]string{"rm", "--cidfile", "foobar", "--all"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(125)) + Expect(result.ErrorToString()).To(ContainSubstring("--all, --latest, and --cidfile cannot be used together")) result = podmanTest.Podman([]string{"rm", "--cidfile", "foobar", "--all", "--latest"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(125)) + Expect(result.ErrorToString()).To(ContainSubstring("--all, --latest, and --cidfile cannot be used together")) result = podmanTest.Podman([]string{"rm", "--latest", "--all"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(125)) + Expect(result.ErrorToString()).To(ContainSubstring("--all and --latest cannot be used together")) }) It("podman rm --all", func() { @@ -242,10 +245,17 @@ var _ = Describe("Podman rm", func() { session = podmanTest.Podman([]string{"rm", "bogus", cid}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(1)) + Expect(session.ErrorToString()).To(ContainSubstring("\"bogus\" found: no such container")) + if IsRemote() { + Expect(session.OutputToString()).To(BeEquivalentTo(cid)) + } session = podmanTest.Podman([]string{"rm", "--ignore", "bogus", cid}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) + if !IsRemote() { + Expect(session.OutputToString()).To(BeEquivalentTo(cid)) + } Expect(podmanTest.NumberOfContainers()).To(Equal(0)) }) @@ -253,6 +263,7 @@ var _ = Describe("Podman rm", func() { session := podmanTest.Podman([]string{"rm", "bogus"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(1)) + Expect(session.ErrorToString()).To(ContainSubstring("\"bogus\" found: no such container")) }) It("podman rm bogus container and a running container", func() { @@ -263,10 +274,12 @@ var _ = Describe("Podman rm", func() { session = podmanTest.Podman([]string{"rm", "bogus", "test1"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(1)) + Expect(session.ErrorToString()).To(ContainSubstring("\"bogus\" found: no such container")) session = podmanTest.Podman([]string{"rm", "test1", "bogus"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(1)) + Expect(session.ErrorToString()).To(ContainSubstring("\"bogus\" found: no such container")) }) It("podman rm --ignore bogus container and a running container", func() { @@ -274,12 +287,52 @@ var _ = Describe("Podman rm", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - session = podmanTest.Podman([]string{"rm", "-t", "0", "--force", "--ignore", "bogus", "test1"}) + session = podmanTest.Podman([]string{"rm", "--ignore", "test1", "bogus"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) + Expect(session).Should(Exit(2)) + Expect(session.ErrorToString()).To(ContainSubstring("containers cannot be removed without force")) - session = podmanTest.Podman([]string{"rm", "--ignore", "test1", "bogus"}) + session = podmanTest.Podman([]string{"rm", "-t", "0", "--force", "--ignore", "bogus", "test1"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(BeEquivalentTo("test1")) + }) + + It("podman rm --filter", func() { + session1 := podmanTest.RunTopContainer("test1") + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + cid1 := session1.OutputToString() + + session1 = podmanTest.RunTopContainer("test2") + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + cid2 := session1.OutputToString() + + session1 = podmanTest.RunTopContainer("test3") + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + cid3 := session1.OutputToString() + shortCid3 := cid3[0:5] + + session1 = podmanTest.Podman([]string{"rm", cid1, "-f", "--filter", "status=running"}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(125)) + Expect(session1.ErrorToString()).To(ContainSubstring("--filter takes no arguments")) + + session1 = podmanTest.Podman([]string{"rm", "-a", "-f", "--filter", fmt.Sprintf("id=%swrongid", shortCid3)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(HaveLen(0)) + + session1 = podmanTest.Podman([]string{"rm", "-a", "-f", "--filter", fmt.Sprintf("id=%s", shortCid3)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(BeEquivalentTo(cid3)) + + session1 = podmanTest.Podman([]string{"rm", "-f", "--filter", fmt.Sprintf("id=%s", cid2)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(BeEquivalentTo(cid2)) }) }) diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index d1a0cd6f5..f87f65c34 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -307,4 +307,82 @@ RUN touch %s`, CIRROS_IMAGE, imageName) } wg.Wait() }) + + It("podman rmi --no-prune with dangling parents", func() { + podmanTest.AddImageToRWStore(ALPINE) + session := podmanTest.Podman([]string{"create", "--name", "c_test1", ALPINE, "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test1", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"create", "--name", "c_test2", "test1", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test2", "test2"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + imageID2 := session.OutputToString() + + session = podmanTest.Podman([]string{"create", "--name", "c_test3", "test2", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test3", "test3"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + imageID3 := session.OutputToString() + + session = podmanTest.Podman([]string{"untag", "test2"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"untag", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"rmi", "-f", "--no-prune", "test3"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring(imageID3)) + Expect(session.OutputToString()).NotTo(ContainSubstring(imageID2)) + }) + + It("podman rmi --no-prune with undangling parents", func() { + podmanTest.AddImageToRWStore(ALPINE) + session := podmanTest.Podman([]string{"create", "--name", "c_test1", ALPINE, "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test1", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"create", "--name", "c_test2", "test1", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test2", "test2"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + imageID2 := session.OutputToString() + + session = podmanTest.Podman([]string{"create", "--name", "c_test3", "test2", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"commit", "-q", "c_test3", "test3"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + imageID3 := session.OutputToString() + + session = podmanTest.Podman([]string{"rmi", "-f", "--no-prune", "test3"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring(imageID3)) + Expect(session.OutputToString()).NotTo(ContainSubstring(imageID2)) + }) }) diff --git a/test/e2e/run_cleanup_test.go b/test/e2e/run_cleanup_test.go index ea2caf907..5aa81140d 100644 --- a/test/e2e/run_cleanup_test.go +++ b/test/e2e/run_cleanup_test.go @@ -36,7 +36,7 @@ var _ = Describe("Podman run exit", func() { It("podman run -d mount cleanup test", func() { SkipIfRemote("podman-remote does not support mount") - SkipIfRootless("TODO rootless podman mount requires podman unshare first") + SkipIfRootless("rootless podman mount requires podman unshare first") result := podmanTest.Podman([]string{"run", "-dt", ALPINE, "top"}) result.WaitWithDefaultTimeout() @@ -69,6 +69,49 @@ var _ = Describe("Podman run exit", func() { pmount.WaitWithDefaultTimeout() Expect(pmount).Should(Exit(0)) Expect(pmount.OutputToString()).NotTo(ContainSubstring(cid)) + }) + + It("podman run -d mount cleanup rootless test", func() { + SkipIfRemote("podman-remote does not support mount") + SkipIfNotRootless("Use unshare in rootless only") + + result := podmanTest.Podman([]string{"run", "-dt", ALPINE, "top"}) + result.WaitWithDefaultTimeout() + cid := result.OutputToString() + Expect(result).Should(Exit(0)) + + mount := podmanTest.Podman([]string{"unshare", "mount"}) + mount.WaitWithDefaultTimeout() + Expect(mount).Should(Exit(0)) + Expect(mount.OutputToString()).To(ContainSubstring(cid)) + + // command: podman <options> unshare podman <options> image mount ALPINE + args := []string{"unshare", podmanTest.PodmanBinary} + opts := podmanTest.PodmanMakeOptions([]string{"mount", "--no-trunc"}, false, false) + args = append(args, opts...) + + pmount := podmanTest.Podman(args) + pmount.WaitWithDefaultTimeout() + Expect(pmount).Should(Exit(0)) + Expect(pmount.OutputToString()).To(ContainSubstring(cid)) + stop := podmanTest.Podman([]string{"stop", cid}) + stop.WaitWithDefaultTimeout() + Expect(stop).Should(Exit(0)) + + // We have to force cleanup so the unmount happens + podmanCleanupSession := podmanTest.Podman([]string{"container", "cleanup", cid}) + podmanCleanupSession.WaitWithDefaultTimeout() + Expect(podmanCleanupSession).Should(Exit(0)) + + mount = podmanTest.Podman([]string{"unshare", "mount"}) + mount.WaitWithDefaultTimeout() + Expect(mount).Should(Exit(0)) + Expect(mount.OutputToString()).NotTo(ContainSubstring(cid)) + + pmount = podmanTest.Podman(args) + pmount.WaitWithDefaultTimeout() + Expect(pmount).Should(Exit(0)) + Expect(pmount.OutputToString()).NotTo(ContainSubstring(cid)) }) }) diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index 613727118..f247b2dac 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -307,6 +307,30 @@ var _ = Describe("Podman UserNS support", func() { } }) + + It("podman --userns= conflicts with ui[dg]map and sub[ug]idname", func() { + session := podmanTest.Podman([]string{"run", "--userns=host", "--uidmap=0:1:500", "alpine", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(125)) + Expect(session.ErrorToString()).To(ContainSubstring("--userns and --uidmap/--gidmap/--subuidname/--subgidname are mutually exclusive")) + + session = podmanTest.Podman([]string{"run", "--userns=host", "--gidmap=0:200:5000", "alpine", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(125)) + Expect(session.ErrorToString()).To(ContainSubstring("--userns and --uidmap/--gidmap/--subuidname/--subgidname are mutually exclusive")) + + // with sub[ug]idname we don't check for the error output since the error message could be different, depending on the + // system configuration since the specified user could not be defined and cause a different earlier error. + // In any case, make sure the command doesn't succeed. + session = podmanTest.Podman([]string{"run", "--userns=private", "--subuidname=containers", "alpine", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Not(Exit(0))) + + session = podmanTest.Podman([]string{"run", "--userns=private", "--subgidname=containers", "alpine", "true"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Not(Exit(0))) + }) + It("podman PODMAN_USERNS", func() { SkipIfNotRootless("keep-id only works in rootless mode") diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 0c60c084a..f3e8cc015 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -1,6 +1,7 @@ package integration import ( + "fmt" "io/ioutil" "os" "strconv" @@ -211,4 +212,42 @@ var _ = Describe("Podman start", func() { _, err = strconv.Atoi(containerPID) // Make sure it's a proper integer Expect(err).To(BeNil()) }) + + It("podman start container --filter", func() { + session1 := podmanTest.Podman([]string{"container", "create", ALPINE}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + cid1 := session1.OutputToString() + + session1 = podmanTest.Podman([]string{"container", "create", ALPINE}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + cid2 := session1.OutputToString() + + session1 = podmanTest.Podman([]string{"container", "create", ALPINE}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + cid3 := session1.OutputToString() + shortCid3 := cid3[0:5] + + session1 = podmanTest.Podman([]string{"start", cid1, "-f", "status=running"}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(HaveLen(0)) + + session1 = podmanTest.Podman([]string{"start", "--all", "--filter", fmt.Sprintf("id=%swrongid", shortCid3)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(HaveLen(0)) + + session1 = podmanTest.Podman([]string{"start", "--all", "--filter", fmt.Sprintf("id=%s", shortCid3)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(BeEquivalentTo(cid3)) + + session1 = podmanTest.Podman([]string{"start", "-f", fmt.Sprintf("id=%s", cid2)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(BeEquivalentTo(cid2)) + }) }) diff --git a/test/system/010-images.bats b/test/system/010-images.bats index aa390f236..16ee681a3 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -259,8 +259,8 @@ Labels.created_at | 20[0-9-]\\\+T[0-9:]\\\+Z run_podman 2 rmi -a is "$output" "Error: 2 errors occurred: -.** image used by .*: image is in use by a container -.** image used by .*: image is in use by a container" +.** image used by .*: image is in use by a container: consider listing external containers and force-removing image +.** image used by .*: image is in use by a container: consider listing external containers and force-removing image" run_podman rmi -af is "$output" "Untagged: $IMAGE @@ -292,7 +292,7 @@ Deleted: $pauseID" "infra images gets removed as well" pauseID=$output run_podman 2 rmi $pauseImage - is "$output" "Error: image used by .* image is in use by a container" + is "$output" "Error: image used by .* image is in use by a container: consider listing external containers and force-removing image" run_podman rmi -f $pauseImage is "$output" "Untagged: $pauseImage diff --git a/test/system/055-rm.bats b/test/system/055-rm.bats index dcd679a1f..613c60c96 100644 --- a/test/system/055-rm.bats +++ b/test/system/055-rm.bats @@ -18,6 +18,7 @@ load helpers # Remove container; now 'inspect' should fail run_podman rm $rand + is "$output" "$rand" "display raw input" run_podman 125 inspect $rand } diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index cbbd62ffb..b1b9ee5e1 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -482,6 +482,7 @@ spec: skip_if_remote "resource limits only implemented on non-remote" skip_if_rootless "resource limits only work with root" skip_if_cgroupsv1 "resource limits only meaningful on cgroups V2" + skip_if_aarch64 "FIXME: #15074 - flakes often on aarch64" # create loopback device lofile=${PODMAN_TMPDIR}/disk.img diff --git a/test/system/255-auto-update.bats b/test/system/255-auto-update.bats index 6cee939fb..c6f9600b6 100644 --- a/test/system/255-auto-update.bats +++ b/test/system/255-auto-update.bats @@ -234,6 +234,8 @@ function _confirm_update() { _confirm_update $cname $ori_image } +# This test can fail in dev. environment because of SELinux. +# quick fix: chcon -t container_runtime_exec_t ./bin/podman @test "podman auto-update - label io.containers.autoupdate=local with rollback" { # sdnotify fails with runc 1.0.0-3-dev2 on Ubuntu. Let's just # assume that we work only with crun, nothing else. diff --git a/test/system/700-play.bats b/test/system/700-play.bats index 72602a9e6..e1955cfd1 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -182,8 +182,11 @@ EOF run_podman container inspect --format "{{.HostConfig.NetworkMode}}" $infraID is "$output" "none" "network mode none is set for the container" - run_podman stop -a -t 0 - run_podman pod rm -t 0 -f test_pod + run_podman kube down - < $PODMAN_TMPDIR/test.yaml + run_podman 125 inspect test_pod-test + is "$output" ".*Error: inspecting object: no such object: \"test_pod-test\"" + run_podman pod rm -a + run_podman rm -a } @test "podman play with user from image" { @@ -325,7 +328,6 @@ spec: - name: TERM value: xterm - name: container - value: podman image: quay.io/libpod/userimage name: test @@ -353,6 +355,9 @@ status: {} run_podman inspect --format "{{.HostConfig.LogConfig.Type}}" test_pod-test is "$output" "$default_driver" "play kube uses default log driver" - run_podman stop -a -t 0 - run_podman pod rm -t 0 -f test_pod + run_podman kube down $PODMAN_TMPDIR/test.yaml + run_podman 125 inspect test_pod-test + is "$output" ".*Error: inspecting object: no such object: \"test_pod-test\"" + run_podman pod rm -a + run_podman rm -a } diff --git a/test/upgrade/test-upgrade.bats b/test/upgrade/test-upgrade.bats index 5efe05d49..dca97e324 100644 --- a/test/upgrade/test-upgrade.bats +++ b/test/upgrade/test-upgrade.bats @@ -345,10 +345,10 @@ failed | exited | 17 @test "rm a stopped container" { run_podman rm myfailedcontainer - is "$output" "[0-9a-f]\\{64\\}" "podman rm myfailedcontainer" + is "$output" "myfailedcontainer" "podman rm myfailedcontainer" run_podman rm mydonecontainer - is "$output" "[0-9a-f]\\{64\\}" "podman rm mydonecontainer" + is "$output" "mydonecontainer" "podman rm mydonecontainer" } diff --git a/vendor/github.com/containers/common/libimage/image.go b/vendor/github.com/containers/common/libimage/image.go index b1866fa9b..d1548eb23 100644 --- a/vendor/github.com/containers/common/libimage/image.go +++ b/vendor/github.com/containers/common/libimage/image.go @@ -470,6 +470,9 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma } if _, err := i.runtime.store.DeleteImage(i.ID(), true); handleError(err) != nil { + if errors.Is(err, storage.ErrImageUsedByContainer) { + err = fmt.Errorf("%w: consider listing external containers and force-removing image", err) + } return processedIDs, err } report.Untagged = append(report.Untagged, i.Names()...) @@ -478,6 +481,11 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma report.Removed = true } + // Do not delete any parents if NoPrune is true + if options.NoPrune { + return processedIDs, nil + } + // Check if can remove the parent image. if parent == nil { return processedIDs, nil @@ -495,7 +503,6 @@ func (i *Image) removeRecursive(ctx context.Context, rmMap map[string]*RemoveIma if !danglingParent { return processedIDs, nil } - // Recurse into removing the parent. return parent.removeRecursive(ctx, rmMap, processedIDs, "", options) } diff --git a/vendor/github.com/containers/common/libimage/runtime.go b/vendor/github.com/containers/common/libimage/runtime.go index 6030a179b..7cbf9c95e 100644 --- a/vendor/github.com/containers/common/libimage/runtime.go +++ b/vendor/github.com/containers/common/libimage/runtime.go @@ -608,6 +608,8 @@ type RemoveImagesOptions struct { // much space was freed. However, computing the size of an image is // comparatively expensive, so it is made optional. WithSize bool + // NoPrune will not remove dangling images + NoPrune bool } // RemoveImages removes images specified by names. If no names are specified, @@ -653,7 +655,6 @@ func (r *Runtime) RemoveImages(ctx context.Context, names []string, options *Rem toDelete := []string{} // Look up images in the local containers storage and fill out // toDelete and the deleteMap. - switch { case len(names) > 0: // prepare lookupOptions diff --git a/vendor/github.com/containers/common/version/version.go b/vendor/github.com/containers/common/version/version.go index 7b44a84fc..34e9fe6ba 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.49.1-dev" +const Version = "0.49.2-dev" diff --git a/vendor/modules.txt b/vendor/modules.txt index 2a1beb611..1a13785eb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -114,7 +114,7 @@ github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/util -# github.com/containers/common v0.49.1-0.20220729221035-246800047d46 +# github.com/containers/common v0.49.2-0.20220804143628-dc97077782d5 ## explicit github.com/containers/common/libimage github.com/containers/common/libimage/define |