diff options
103 files changed, 2304 insertions, 757 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 14d3540c1..b9c284002 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -588,11 +588,14 @@ rootless_integration_test_task: podman_machine_task: name: *std_name_fmt alias: podman_machine + # Required_pr_labels does not apply to non-PRs. + # Do not run on tags, branches, [CI:BUILD], or [CI:DOCS]. only_if: *not_tag_branch_build_docs - # Manually-triggered task: This is "expensive" to run. + # This task costs about $4 per attempt to execute. + # Only run it if a magic PR label is present. # DO NOT ADD THIS TASK AS DEPENDENCY FOR `success_task` - # it will cause 'success' to block. - trigger_type: manual + # it will cause an infinate-block / never completing build. + required_pr_labels: test_podman_machine depends_on: - build - local_integration_test @@ -879,8 +882,8 @@ success_task: - remote_integration_test - container_integration_test - rootless_integration_test - # Manually triggered task. If made automatic, remove bypass - # in contrib/cirrus/cirrus_yaml_test.py for this task. + # Label triggered task. If made automatic, remove line below + # AND bypass in contrib/cirrus/cirrus_yaml_test.py for this name. # - podman_machine - local_system_test - remote_system_test diff --git a/.github/actions/check_cirrus_cron/cron_failures.sh b/.github/actions/check_cirrus_cron/cron_failures.sh index 4fb3af98f..f4dddff8b 100755 --- a/.github/actions/check_cirrus_cron/cron_failures.sh +++ b/.github/actions/check_cirrus_cron/cron_failures.sh @@ -67,11 +67,6 @@ jq --indent 4 --color-output . <./artifacts/reply.json || \ cat ./artifacts/reply.json echo "::endgroup::" -# Desirable to catch non-JSON encoded errors in reply. -if grep -qi 'error' ./artifacts/reply.json; then - err "Found the word 'error' in reply" -fi - # e.x. reply.json # { # "data": { @@ -102,8 +97,19 @@ fi # } # } # } -_filt='.data.ownerRepository.cronSettings | map(select(.lastInvocationBuild.status=="FAILED") | { name:.name, id:.lastInvocationBuild.id} | join(" ")) | join("\n")' -jq --raw-output "$_filt" ./artifacts/reply.json > "$NAME_ID_FILEPATH" + +# This should never ever return an empty-list, unless there are no cirrus-cron +# jobs defined for the repository. In that case, this monitoring script shouldn't +# be running anyway. +filt_head='.data.ownerRepository.cronSettings' +if ! jq -e "$filt_head" ./artifacts/reply.json &> /dev/null +then + # Actual colorized JSON reply was printed above + err "Null/empty result filtering reply with '$filt_head'" +fi + +filt="$filt_head | map(select(.lastInvocationBuild.status==\"FAILED\") | { name:.name, id:.lastInvocationBuild.id} | join(\" \")) | join(\"\n\")" +jq --raw-output "$filt" ./artifacts/reply.json > "$NAME_ID_FILEPATH" echo "<Cron Name> <Failed Build ID>" cat "$NAME_ID_FILEPATH" diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 6e6c33f9b..02369c74a 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -18,7 +18,6 @@ import ( "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/signal" systemdDefine "github.com/containers/podman/v4/pkg/systemd/define" "github.com/containers/podman/v4/pkg/util" @@ -54,7 +53,7 @@ func setupContainerEngine(cmd *cobra.Command) (entities.ContainerEngine, error) cobra.CompErrorln(err.Error()) return nil, err } - if !registry.IsRemote() && rootless.IsRootless() { + if !registry.IsRemote() { _, noMoveProcess := cmd.Annotations[registry.NoMoveProcess] err := containerEngine.SetupRootless(registry.Context(), noMoveProcess) diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 923d0517f..d2646aa43 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -849,9 +849,9 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, _ = cmd.RegisterFlagCompletionFunc(cpuRtRuntimeFlagName, completion.AutocompleteNone) cpuSharesFlagName := "cpu-shares" - createFlags.Uint64Var( + createFlags.Uint64VarP( &cf.CPUShares, - cpuSharesFlagName, 0, + cpuSharesFlagName, "c", 0, "CPU shares (relative weight)", ) _ = cmd.RegisterFlagCompletionFunc(cpuSharesFlagName, completion.AutocompleteNone) diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go index 2ddd169a1..261f441c3 100644 --- a/cmd/podman/containers/stop.go +++ b/cmd/podman/containers/stop.go @@ -49,7 +49,9 @@ var ( ) var ( - stopOptions = entities.StopOptions{} + stopOptions = entities.StopOptions{ + Filters: make(map[string][]string), + } stopTimeout uint ) @@ -67,6 +69,10 @@ func stopFlags(cmd *cobra.Command) { flags.UintVarP(&stopTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container") _ = cmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone) + filterFlagName := "filter" + flags.StringSliceVarP(&filters, filterFlagName, "f", []string{}, "Filter output based on conditions given") + _ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePsFilters) + if registry.IsRemote() { _ = flags.MarkHidden("cidfile") _ = flags.MarkHidden("ignore") @@ -97,7 +103,6 @@ func stop(cmd *cobra.Command, args []string) error { if cmd.Flag("time").Changed { stopOptions.Timeout = &stopTimeout } - for _, cidFile := range cidFiles { content, err := ioutil.ReadFile(cidFile) if err != nil { @@ -107,6 +112,14 @@ func stop(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) + } + stopOptions.Filters[split[0]] = append(stopOptions.Filters[split[0]], split[1]) + } + responses, err := registry.ContainerEngine().ContainerStop(context.Background(), args, stopOptions) if err != nil { return err diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index 6e3ec1517..8211ceba5 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -77,7 +77,7 @@ func init() { func pullFlags(cmd *cobra.Command) { flags := cmd.Flags() - flags.BoolVar(&pullOptions.AllTags, "all-tags", false, "All tagged images in the repository will be pulled") + flags.BoolVarP(&pullOptions.AllTags, "all-tags", "a", false, "All tagged images in the repository will be pulled") credsFlagName := "creds" flags.StringVar(&pullOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") diff --git a/cmd/podman/kube/kube.go b/cmd/podman/kube/kube.go new file mode 100644 index 000000000..68f55a157 --- /dev/null +++ b/cmd/podman/kube/kube.go @@ -0,0 +1,35 @@ +package pods + +import ( + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/cmd/podman/validate" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _kube_ + kubeCmd = &cobra.Command{ + Use: "kube", + Short: "Play containers, pods or volumes from a structured file", + Long: "Play structured data (e.g., Kubernetes YAML) based on containers, pods or volumes.", + RunE: validate.SubCommandExists, + } + + playKubeParentCmd = &cobra.Command{ + Use: "play", + Short: "Play containers, pods or volumes from a structured file", + Long: "Play structured data (e.g., Kubernetes YAML) based on containers, pods or volumes.", + Hidden: true, + RunE: validate.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: kubeCmd, + }) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: playKubeParentCmd, + }) +} diff --git a/cmd/podman/play/kube.go b/cmd/podman/kube/play.go index 8fd12baaf..685cb521c 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/kube/play.go @@ -29,23 +29,38 @@ type playKubeOptionsWrapper struct { CredentialsCLI string StartCLI bool BuildCLI bool + annotations []string + macs []string } var ( - annotations []string - macs []string // https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/ defaultSeccompRoot = "/var/lib/kubelet/seccomp" - kubeOptions = playKubeOptionsWrapper{} - kubeDescription = `Command reads in a structured file of Kubernetes YAML. + playOptions = playKubeOptionsWrapper{} + playDescription = `Command 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.` - kubeCmd = &cobra.Command{ + playCmd = &cobra.Command{ + Use: "play [options] KUBEFILE|-", + Short: "Play a pod or volume based on Kubernetes YAML.", + Long: playDescription, + RunE: Play, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteDefaultOneArg, + Example: `podman kube play nginx.yml + cat nginx.yml | podman kube play - + podman kube play --creds user:password --seccomp-profile-root /custom/path apache.yml`, + } +) + +var ( + playKubeCmd = &cobra.Command{ Use: "kube [options] KUBEFILE|-", Short: "Play a pod or volume based on Kubernetes YAML.", - Long: kubeDescription, - RunE: kube, + Long: playDescription, + Hidden: true, + RunE: playKube, Args: cobra.ExactArgs(1), ValidArgsFunction: common.AutocompleteDefaultOneArg, Example: `podman play kube nginx.yml @@ -56,170 +71,183 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ - Command: kubeCmd, - Parent: playCmd, + Command: playCmd, + Parent: kubeCmd, }) + playFlags(playCmd) - flags := kubeCmd.Flags() + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: playKubeCmd, + Parent: playKubeParentCmd, + }) + playFlags(playKubeCmd) +} + +func playFlags(cmd *cobra.Command) { + flags := cmd.Flags() flags.SetNormalizeFunc(utils.AliasFlags) annotationFlagName := "annotation" flags.StringSliceVar( - &annotations, + &playOptions.annotations, annotationFlagName, []string{}, "Add annotations to pods (key=value)", ) - _ = kubeCmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone) + _ = cmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone) credsFlagName := "creds" - flags.StringVar(&kubeOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") - _ = kubeCmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) + flags.StringVar(&playOptions.CredentialsCLI, credsFlagName, "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + _ = cmd.RegisterFlagCompletionFunc(credsFlagName, completion.AutocompleteNone) staticMACFlagName := "mac-address" - flags.StringSliceVar(&macs, staticMACFlagName, nil, "Static MAC addresses to assign to the pods") - _ = kubeCmd.RegisterFlagCompletionFunc(staticMACFlagName, completion.AutocompleteNone) + flags.StringSliceVar(&playOptions.macs, staticMACFlagName, nil, "Static MAC addresses to assign to the pods") + _ = cmd.RegisterFlagCompletionFunc(staticMACFlagName, completion.AutocompleteNone) networkFlagName := "network" - flags.StringArrayVar(&kubeOptions.Networks, networkFlagName, nil, "Connect pod to network(s) or network mode") - _ = kubeCmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) + flags.StringArrayVar(&playOptions.Networks, networkFlagName, nil, "Connect pod to network(s) or network mode") + _ = cmd.RegisterFlagCompletionFunc(networkFlagName, common.AutocompleteNetworkFlag) staticIPFlagName := "ip" - flags.IPSliceVar(&kubeOptions.StaticIPs, staticIPFlagName, nil, "Static IP addresses to assign to the pods") - _ = kubeCmd.RegisterFlagCompletionFunc(staticIPFlagName, completion.AutocompleteNone) + flags.IPSliceVar(&playOptions.StaticIPs, staticIPFlagName, nil, "Static IP addresses to assign to the pods") + _ = cmd.RegisterFlagCompletionFunc(staticIPFlagName, completion.AutocompleteNone) logDriverFlagName := "log-driver" - flags.StringVar(&kubeOptions.LogDriver, logDriverFlagName, common.LogDriver(), "Logging driver for the container") - _ = kubeCmd.RegisterFlagCompletionFunc(logDriverFlagName, common.AutocompleteLogDriver) + flags.StringVar(&playOptions.LogDriver, logDriverFlagName, common.LogDriver(), "Logging driver for the container") + _ = cmd.RegisterFlagCompletionFunc(logDriverFlagName, common.AutocompleteLogDriver) logOptFlagName := "log-opt" flags.StringSliceVar( - &kubeOptions.LogOptions, + &playOptions.LogOptions, logOptFlagName, []string{}, "Logging driver options", ) - _ = kubeCmd.RegisterFlagCompletionFunc(logOptFlagName, common.AutocompleteLogOpt) + _ = cmd.RegisterFlagCompletionFunc(logOptFlagName, common.AutocompleteLogOpt) usernsFlagName := "userns" - flags.StringVar(&kubeOptions.Userns, usernsFlagName, os.Getenv("PODMAN_USERNS"), + flags.StringVar(&playOptions.Userns, usernsFlagName, os.Getenv("PODMAN_USERNS"), "User namespace to use", ) - _ = kubeCmd.RegisterFlagCompletionFunc(usernsFlagName, common.AutocompleteUserNamespace) + _ = cmd.RegisterFlagCompletionFunc(usernsFlagName, common.AutocompleteUserNamespace) - flags.BoolVar(&kubeOptions.NoHosts, "no-hosts", false, "Do not create /etc/hosts within the pod's containers, instead use the version from the image") - flags.BoolVarP(&kubeOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") - flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") - flags.BoolVar(&kubeOptions.StartCLI, "start", true, "Start the pod after creating it") + flags.BoolVar(&playOptions.NoHosts, "no-hosts", false, "Do not create /etc/hosts within the pod's containers, instead use the version from the image") + flags.BoolVarP(&playOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + flags.BoolVar(&playOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + flags.BoolVar(&playOptions.StartCLI, "start", true, "Start the pod after creating it") authfileFlagName := "authfile" - flags.StringVar(&kubeOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") - _ = kubeCmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) + flags.StringVar(&playOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + _ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault) downFlagName := "down" - flags.BoolVar(&kubeOptions.Down, downFlagName, false, "Stop pods defined in the YAML file") + flags.BoolVar(&playOptions.Down, downFlagName, false, "Stop pods defined in the YAML file") replaceFlagName := "replace" - flags.BoolVar(&kubeOptions.Replace, replaceFlagName, false, "Delete and recreate pods defined in the YAML file") + flags.BoolVar(&playOptions.Replace, replaceFlagName, false, "Delete and recreate pods defined in the YAML file") if !registry.IsRemote() { certDirFlagName := "cert-dir" - flags.StringVar(&kubeOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys") - _ = kubeCmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) + flags.StringVar(&playOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys") + _ = cmd.RegisterFlagCompletionFunc(certDirFlagName, completion.AutocompleteDefault) seccompProfileRootFlagName := "seccomp-profile-root" - flags.StringVar(&kubeOptions.SeccompProfileRoot, seccompProfileRootFlagName, defaultSeccompRoot, "Directory path for seccomp profiles") - _ = kubeCmd.RegisterFlagCompletionFunc(seccompProfileRootFlagName, completion.AutocompleteDefault) + flags.StringVar(&playOptions.SeccompProfileRoot, seccompProfileRootFlagName, defaultSeccompRoot, "Directory path for seccomp profiles") + _ = cmd.RegisterFlagCompletionFunc(seccompProfileRootFlagName, completion.AutocompleteDefault) configmapFlagName := "configmap" - flags.StringSliceVar(&kubeOptions.ConfigMaps, configmapFlagName, []string{}, "`Pathname` of a YAML file containing a kubernetes configmap") - _ = kubeCmd.RegisterFlagCompletionFunc(configmapFlagName, completion.AutocompleteDefault) + flags.StringSliceVar(&playOptions.ConfigMaps, configmapFlagName, []string{}, "`Pathname` of a YAML file containing a kubernetes configmap") + _ = cmd.RegisterFlagCompletionFunc(configmapFlagName, completion.AutocompleteDefault) buildFlagName := "build" - flags.BoolVar(&kubeOptions.BuildCLI, buildFlagName, false, "Build all images in a YAML (given Containerfiles exist)") + flags.BoolVar(&playOptions.BuildCLI, buildFlagName, false, "Build all images in a YAML (given Containerfiles exist)") contextDirFlagName := "context-dir" - flags.StringVar(&kubeOptions.ContextDir, contextDirFlagName, "", "Path to top level of context directory") - _ = kubeCmd.RegisterFlagCompletionFunc(contextDirFlagName, completion.AutocompleteDefault) + flags.StringVar(&playOptions.ContextDir, contextDirFlagName, "", "Path to top level of context directory") + _ = cmd.RegisterFlagCompletionFunc(contextDirFlagName, completion.AutocompleteDefault) // NOTE: The service-container flag is marked as hidden as it - // is purely designed for running play-kube in systemd units. + // is purely designed for running kube-play in systemd units. // It is not something users should need to know or care about. // // Having a flag rather than an env variable is cleaner. serviceFlagName := "service-container" - flags.BoolVar(&kubeOptions.ServiceContainer, serviceFlagName, false, "Starts a service container before all pods") + flags.BoolVar(&playOptions.ServiceContainer, serviceFlagName, false, "Starts a service container before all pods") _ = flags.MarkHidden("service-container") - flags.StringVar(&kubeOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.StringVar(&playOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") _ = flags.MarkHidden("signature-policy") } } -func kube(cmd *cobra.Command, args []string) error { +func Play(cmd *cobra.Command, args []string) error { // TLS verification in c/image is controlled via a `types.OptionalBool` // which allows for distinguishing among set-true, set-false, unspecified // which is important to implement a sane way of dealing with defaults of // boolean CLI flags. if cmd.Flags().Changed("tls-verify") { - kubeOptions.SkipTLSVerify = types.NewOptionalBool(!kubeOptions.TLSVerifyCLI) + playOptions.SkipTLSVerify = types.NewOptionalBool(!playOptions.TLSVerifyCLI) } if cmd.Flags().Changed("start") { - kubeOptions.Start = types.NewOptionalBool(kubeOptions.StartCLI) + playOptions.Start = types.NewOptionalBool(playOptions.StartCLI) } if cmd.Flags().Changed("build") { - kubeOptions.Build = types.NewOptionalBool(kubeOptions.BuildCLI) + playOptions.Build = types.NewOptionalBool(playOptions.BuildCLI) } - if kubeOptions.Authfile != "" { - if _, err := os.Stat(kubeOptions.Authfile); err != nil { + if playOptions.Authfile != "" { + if _, err := os.Stat(playOptions.Authfile); err != nil { return err } } - if kubeOptions.ContextDir != "" && kubeOptions.Build != types.OptionalBoolTrue { + if playOptions.ContextDir != "" && playOptions.Build != types.OptionalBoolTrue { return errors.New("--build must be specified when using --context-dir option") } - if kubeOptions.CredentialsCLI != "" { - creds, err := util.ParseRegistryCreds(kubeOptions.CredentialsCLI) + if playOptions.CredentialsCLI != "" { + creds, err := util.ParseRegistryCreds(playOptions.CredentialsCLI) if err != nil { return err } - kubeOptions.Username = creds.Username - kubeOptions.Password = creds.Password + playOptions.Username = creds.Username + playOptions.Password = creds.Password } - for _, annotation := range annotations { + for _, annotation := range playOptions.annotations { splitN := strings.SplitN(annotation, "=", 2) if len(splitN) > 2 { return fmt.Errorf("annotation %q must include an '=' sign", annotation) } - if kubeOptions.Annotations == nil { - kubeOptions.Annotations = make(map[string]string) + if playOptions.Annotations == nil { + playOptions.Annotations = make(map[string]string) } annotation := splitN[1] if len(annotation) > define.MaxKubeAnnotation { return fmt.Errorf("annotation exceeds maximum size, %d, of kubernetes annotation: %s", define.MaxKubeAnnotation, annotation) } - kubeOptions.Annotations[splitN[0]] = annotation + playOptions.Annotations[splitN[0]] = annotation } yamlfile := args[0] if yamlfile == "-" { yamlfile = "/dev/stdin" } - for _, mac := range macs { + for _, mac := range playOptions.macs { m, err := net.ParseMAC(mac) if err != nil { return err } - kubeOptions.StaticMACs = append(kubeOptions.StaticMACs, m) + playOptions.StaticMACs = append(playOptions.StaticMACs, m) } - if kubeOptions.Down { + if playOptions.Down { return teardown(yamlfile) } - if kubeOptions.Replace { + if playOptions.Replace { if err := teardown(yamlfile); err != nil && !errorhandling.Contains(err, define.ErrNoSuchPod) { return err } } - return playkube(yamlfile) + return kubeplay(yamlfile) +} + +func playKube(cmd *cobra.Command, args []string) error { + return Play(cmd, args) } func teardown(yamlfile string) error { @@ -265,13 +293,13 @@ func teardown(yamlfile string) error { return podRmErrors.PrintErrors() } -func playkube(yamlfile string) error { +func kubeplay(yamlfile string) error { f, err := os.Open(yamlfile) if err != nil { return err } defer f.Close() - report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), f, kubeOptions.PlayKubeOptions) + report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), f, playOptions.PlayKubeOptions) if err != nil { return fmt.Errorf("%s: %w", yamlfile, err) } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 929c8a757..dc21807b4 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -9,10 +9,10 @@ import ( _ "github.com/containers/podman/v4/cmd/podman/generate" _ "github.com/containers/podman/v4/cmd/podman/healthcheck" _ "github.com/containers/podman/v4/cmd/podman/images" + _ "github.com/containers/podman/v4/cmd/podman/kube" _ "github.com/containers/podman/v4/cmd/podman/machine" _ "github.com/containers/podman/v4/cmd/podman/manifest" _ "github.com/containers/podman/v4/cmd/podman/networks" - _ "github.com/containers/podman/v4/cmd/podman/play" _ "github.com/containers/podman/v4/cmd/podman/pods" "github.com/containers/podman/v4/cmd/podman/registry" _ "github.com/containers/podman/v4/cmd/podman/secrets" diff --git a/cmd/podman/play/play.go b/cmd/podman/play/play.go deleted file mode 100644 index e277ec9bd..000000000 --- a/cmd/podman/play/play.go +++ /dev/null @@ -1,23 +0,0 @@ -package pods - -import ( - "github.com/containers/podman/v4/cmd/podman/registry" - "github.com/containers/podman/v4/cmd/podman/validate" - "github.com/spf13/cobra" -) - -var ( - // Command: podman _play_ - playCmd = &cobra.Command{ - Use: "play", - Short: "Play containers, pods or volumes from a structured file", - Long: "Play structured data (e.g., Kubernetes YAML) based on containers, pods or volumes.", - RunE: validate.SubCommandExists, - } -) - -func init() { - registry.Commands = append(registry.Commands, registry.CliCommand{ - Command: playCmd, - }) -} diff --git a/cmd/podman/root.go b/cmd/podman/root.go index f28d92e2f..0520a0784 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -19,7 +19,6 @@ import ( "github.com/containers/podman/v4/pkg/checkpoint/crutils" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/parallel" - "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/version" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -265,7 +264,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error { // 2) running as non-root // 3) command doesn't require Parent Namespace _, found := cmd.Annotations[registry.ParentNSRequired] - if !registry.IsRemote() && rootless.IsRootless() && !found { + if !registry.IsRemote() && !found { _, noMoveProcess := cmd.Annotations[registry.NoMoveProcess] err := registry.ContainerEngine().SetupRootless(registry.Context(), noMoveProcess) if err != nil { diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go index 09e589d3c..b04668f86 100644 --- a/cmd/podman/system/events.go +++ b/cmd/podman/system/events.go @@ -46,7 +46,7 @@ func init() { flags := eventsCommand.Flags() filterFlagName := "filter" - flags.StringArrayVar(&eventOptions.Filter, filterFlagName, []string{}, "filter output") + flags.StringArrayVarP(&eventOptions.Filter, filterFlagName, "f", []string{}, "filter output") _ = eventsCommand.RegisterFlagCompletionFunc(filterFlagName, common.AutocompleteEventFilter) formatFlagName := "format" diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go index 6823d77ba..8d0240a8d 100644 --- a/cmd/podman/system/service_abi.go +++ b/cmd/podman/system/service_abi.go @@ -11,7 +11,6 @@ import ( "os" "path/filepath" - "github.com/containers/common/pkg/cgroups" "github.com/containers/podman/v4/cmd/podman/registry" api "github.com/containers/podman/v4/pkg/api/server" "github.com/containers/podman/v4/pkg/domain/entities" @@ -24,26 +23,6 @@ import ( "golang.org/x/sys/unix" ) -// maybeMoveToSubCgroup moves the current process in a sub cgroup when -// it is running in the root cgroup on a system that uses cgroupv2. -func maybeMoveToSubCgroup() error { - unifiedMode, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return err - } - if !unifiedMode { - return nil - } - cgroup, err := utils.GetOwnCgroup() - if err != nil { - return err - } - if cgroup == "/" { - return utils.MoveUnderCgroupSubtree("init") - } - return nil -} - func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities.ServiceOptions) error { var ( listener net.Listener @@ -125,7 +104,7 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities return err } - if err := maybeMoveToSubCgroup(); err != nil { + if err := utils.MaybeMoveToSubCgroup(); err != nil { return err } diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go index 39eedca64..6d212665d 100644 --- a/cmd/podman/validate/args.go +++ b/cmd/podman/validate/args.go @@ -86,6 +86,13 @@ func CheckAllLatestAndIDFile(c *cobra.Command, args []string, ignoreArgLen bool, specifiedIDFile = true } + if c.Flags().Changed("filter") { + if argLen > 0 { + return errors.New("--filter takes no arguments") + } + return nil + } + if specifiedIDFile && (specifiedAll || specifiedLatest) { return fmt.Errorf("--all, --latest, and --%s cannot be used together", idFileFlag) } else if specifiedAll && specifiedLatest { diff --git a/docs/MANPAGE_SYNTAX.md b/docs/MANPAGE_SYNTAX.md index 7fff167ba..60bffdace 100644 --- a/docs/MANPAGE_SYNTAX.md +++ b/docs/MANPAGE_SYNTAX.md @@ -60,15 +60,15 @@ Example sentence: Use **[podman-generate-systemd --new](./source/markdown/podman #### **--version**, **-v** -OPTIONS can be put after the command in two different ways. Either the long version with **--option** or as the short version **-o**. If there are two ways to write an OPTION they are separated by a comma. If there are two versions of one command the long version is always shown in front. If the arguments behind the OPTION are boolean, it is not shown behind the OPTION itself. The default boolean argument is shown in the same way normal default arguments are displayed.\ +OPTIONS can be put after the command in two different ways. Either the long version with **--option** or as the short version **-o**. If there are two ways to write an OPTION they are separated by a comma. If there are two versions of one command the long version is always shown in front. If OPTION is boolean, *true/false* are not enumerated. The default boolean argument is shown in the same way normal default arguments are displayed.\ Example: The default is **false**.\ *IMPORTANT: This OPTION is not available with the remote Podman client.* #### **--exit** -An example of an OPTION that has only one possible structure. Thus, it cannot be executed by the extension **-e**. +An example of a boolean OPTION that is only available in long form. -#### **--answer**=, **-a**=**active** | *disable* +#### **--answer**, **-a**=**active** | *disable* The **--answer** OPTION above is an example of an OPTION that accepts two possible arguments as inputs. If a default argument is selected when the OPTION is not used in the command, it is shown in **bold**. If the OPTION is used, it must include an argument afterward. It must always be ensured that the standard argument is in the first position after the OPTION. In this example, there are two different ways to execute the command. Both possible OPTIONS have to be shown with the arguments following them.\ The default value is shown as **active**. diff --git a/docs/play_kube_support.md b/docs/kubernetes_support.md index 3cfd3fa50..851e692cb 100644 --- a/docs/play_kube_support.md +++ b/docs/kubernetes_support.md @@ -1,6 +1,6 @@ -# Podman Play Kube Support +# Podman Kube Play Support -This document outlines the kube yaml fields that are currently supported by the **podman play kube** command. +This document outlines the kube yaml fields that are currently supported by the **podman kube play** command. Note: **N/A** means that the option cannot be supported in a single-node Podman environment. diff --git a/docs/source/Commands.rst b/docs/source/Commands.rst index 2911efe18..6b26ed1d9 100644 --- a/docs/source/Commands.rst +++ b/docs/source/Commands.rst @@ -47,6 +47,8 @@ Commands :doc:`kill <markdown/podman-kill.1>` Kill one or more running containers with a specific signal +:doc:`kube <markdown/podman-kube.1>` Play a pod + :doc:`load <markdown/podman-load.1>` Load an image from container archive :doc:`login <markdown/podman-login.1>` Login to a container registry @@ -65,8 +67,6 @@ Commands :doc:`pause <markdown/podman-pause.1>` Pause all the processes in one or more containers -:doc:`play <markdown/podman-play.1>` Play a pod - :doc:`pod <markdown/podman-pod.1>` Manage pods :doc:`port <markdown/podman-port.1>` List port mappings or a specific mapping for the container diff --git a/docs/source/Tutorials.rst b/docs/source/Tutorials.rst index 024e6847c..1c8e946db 100644 --- a/docs/source/Tutorials.rst +++ b/docs/source/Tutorials.rst @@ -13,3 +13,4 @@ Here are a number of useful tutorials to get you up and running with Podman. If * `How to use libpod for custom/derivative projects <https://github.com/containers/podman/blob/main/docs/tutorials/podman-derivative-api.md>`_: How the libpod API can be used within your own project. * `How to use Podman's Go RESTful bindings <https://github.com/containers/podman/tree/main/pkg/bindings>`_: An introduction to using our RESTful Golang bindings in an external application. * `Common network setups <https://github.com/containers/podman/blob/main/docs/tutorials/basic_networking.md>`_: A basic guide to common network setups for Podman. +* `Socket activation <https://github.com/containers/podman/blob/main/docs/tutorials/socket_activation.md>`_: Learn how to run containers that support socket activation. diff --git a/docs/source/markdown/links/podman-play-kube.1 b/docs/source/markdown/links/podman-play-kube.1 new file mode 100644 index 000000000..ffa073b9a --- /dev/null +++ b/docs/source/markdown/links/podman-play-kube.1 @@ -0,0 +1 @@ +.so man1/podman-kube-play.1 diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index bf710022e..ba7081ff5 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -259,7 +259,7 @@ keys and/or certificates. Decryption will be tried with all keys. If the key is protected by a passphrase, it is required to be passed in the argument and omitted otherwise. -#### **--device**=_host-device_[**:**_container-device_][**:**_permissions_] +#### **--device**=*host-device[:container-device][:permissions]* Add a host device to the container. Optional *permissions* parameter can be used to specify device permissions, it is combination of @@ -267,13 +267,13 @@ can be used to specify device permissions, it is combination of Example: **--device=/dev/sdc:/dev/xvdc:rwm**. -Note: if _host_device_ is a symbolic link then it will be resolved first. +Note: if *host-device* is a symbolic link then it will be resolved first. The container will only store the major and minor numbers of the host device. Note: if the user only has access rights via a group, accessing the device from inside a rootless container will fail. The **[crun(1)](https://github.com/containers/crun/tree/main/crun.1.md)** runtime offers a workaround for this by adding the option -#### **--annotation run.oci.keep_original_groups=1**. +**--annotation run.oci.keep_original_groups=1**. #### **--disable-compression**, **-D** @@ -311,7 +311,7 @@ Set custom DNS options to be used during the build. Set custom DNS search domains to be used during the build. -#### **--env** *env[=value]* +#### **--env**=*env[=value]* Add a value (e.g. env=*value*) to the built image. Can be used multiple times. If neither `=` nor a `*value*` are specified, but *env* is set in the current @@ -430,7 +430,7 @@ specified file instead of to standard output and standard error. This option is not supported on the remote client, including Mac and Windows (excluding WSL2) machines. -#### **--logsplit** *bool-value* +#### **--logsplit**=*bool-value* If `--logfile` and `--platform` are specified, the `--logsplit` option allows end-users to split the log file for each platform into different files in the @@ -438,7 +438,7 @@ following format: `${logfile}_${platform-os}_${platform-arch}`. This option is not supported on the remote client, including Mac and Windows (excluding WSL2) machines. -#### **--manifest** "manifest" +#### **--manifest**=*manifest* Name of the manifest list to which the image will be added. Creates the manifest list if it does not exist. This option is useful for building multi architecture images. @@ -507,7 +507,7 @@ Set the OS of the image to be built, and that of the base image to be pulled, if the build uses one, instead of using the current operating system of the build host. -#### **--os-feature** *feature* +#### **--os-feature**=*feature* Set the name of a required operating system *feature* for the image which will be built. By default, if the image is not based on *scratch*, the base image's @@ -517,7 +517,7 @@ is typically only meaningful when the image's OS is Windows. If *feature* has a trailing `-`, then the *feature* is removed from the set of required features which will be listed in the image. -#### **--os-version** *version* +#### **--os-version**=*version* Set the exact required operating system *version* for the image which will be built. By default, if the image is not based on *scratch*, the base image's @@ -525,7 +525,7 @@ required OS version is kept, if the base image specified one. This option is typically only meaningful when the image's OS is Windows, and is typically set in Windows base images, so using this option is usually unnecessary. -#### **--output**, **-o**="" +#### **--output**, **-o**=*output-opts* Output destination (format: type=local,dest=path) @@ -553,9 +553,9 @@ that the PID namespace in which `podman` itself is being run should be reused, or it can be the path to a PID namespace which is already in use by another process. -#### **--platform**="OS/ARCH[/VARIANT][,...]" +#### **--platform**=*os/arch[/variant][,...]* -Set the OS/ARCH of the built image (and its base image, if your build uses one) +Set the *os/arch* of the built image (and its base image, if your build uses one) to the provided value instead of using the current operating system and architecture of the host (for example `linux/arm`). If `--platform` is set, then the values of the `--arch`, `--os`, and `--variant` options will be @@ -566,8 +566,8 @@ comma-separated list of values as its argument. When more than one platform is specified, the `--manifest` option should be used instead of the `--tag` option. -OS/ARCH pairs are those used by the Go Programming Language. In several cases -the ARCH value for a platform differs from one produced by other tools such as +Os/arch pairs are those used by the Go Programming Language. In several cases +the *arch* value for a platform differs from one produced by other tools such as the `arch` command. Valid OS and architecture name combinations are listed as values for $GOOS and $GOARCH at https://golang.org/doc/install/source#environment, and can also be found by running `go tool dist list`. @@ -576,7 +576,7 @@ While `podman build` is happy to use base images and build images for any platform that exists, `RUN` instructions will not be able to succeed without the help of emulation provided by packages like `qemu-user-static`. -#### **--pull**=**always**|**missing**|**never**|**newer** +#### **--pull**=*policy* Pull image policy. The default is **always**. @@ -655,7 +655,7 @@ layers are not squashed. Squash all of the new image's layers (including those inherited from a base image) into a single new layer. -#### **--ssh**=*default|id[=socket>|[,]* +#### **--ssh**=*default* | *id[=socket>* SSH agent socket or keys to expose to the build. The socket path can be left empty to use the value of `default=$SSH_AUTH_SOCK` @@ -683,7 +683,7 @@ Set the target build stage to build. When building a Containerfile with multiple build stages, --target can be used to specify an intermediate build stage by name as the final stage for the resulting image. Commands after the target stage will be skipped. -#### **--timestamp** *seconds* +#### **--timestamp**=*seconds* Set the create timestamp to seconds since epoch to allow for deterministic builds (defaults to current time). By default, the created timestamp is changed @@ -699,7 +699,7 @@ timestamp. Require HTTPS and verify certificates when talking to container registries (defaults to true). (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) -#### **--ulimit**=*type*=*soft-limit*[:*hard-limit*] +#### **--ulimit**=*type=soft-limit[:hard-limit]* Specifies resource limits to apply to processes launched when processing `RUN` instructions. This option can be specified multiple times. Recognized resource @@ -720,7 +720,7 @@ types include: "sigpending": maximum number of pending signals (ulimit -i) "stack": maximum stack size (ulimit -s) -#### **--unsetenv** *env* +#### **--unsetenv**=*env* Unset environment variables from the final image. @@ -814,19 +814,20 @@ that the UTS namespace in which `podman` itself is being run should be reused, or it can be the path to a UTS namespace which is already in use by another process. -#### **--variant**="" +#### **--variant**=*variant* Set the architecture variant of the image to be built, and that of the base image to be pulled, if the build uses one, to the provided value instead of using the architecture variant of the build host. -#### **--volume**, **-v**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] +#### **--volume**, **-v**=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]* - Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Podman - bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman - container. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) +Create a bind mount. If you specify `-v /HOST-DIR:/CONTAINER-DIR`, Podman +bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman +container. (This option is not available with the remote Podman client, +including Mac and Windows (excluding WSL2) machines) - The `OPTIONS` are a comma-separated list and can be: <sup>[[1]](#Footnote1)</sup> +The `OPTIONS` are a comma-separated list and can be: <sup>[[1]](#Footnote1)</sup> * [rw|ro] * [z|Z|O] diff --git a/docs/source/markdown/podman-commit.1.md b/docs/source/markdown/podman-commit.1.md index 3df931254..b7b52e3a7 100644 --- a/docs/source/markdown/podman-commit.1.md +++ b/docs/source/markdown/podman-commit.1.md @@ -36,7 +36,7 @@ Apply the following possible instructions to the created image: Can be set multiple times. -#### **--format**, **-f** =**oci** | *docker* +#### **--format**, **-f**=**oci** | *docker* Set the format of the image manifest and metadata. The currently supported formats are **oci** and *docker*.\ The default is **oci**. diff --git a/docs/source/markdown/podman-container-clone.1.md b/docs/source/markdown/podman-container-clone.1.md index 6d552db75..3b9d79862 100644 --- a/docs/source/markdown/podman-container-clone.1.md +++ b/docs/source/markdown/podman-container-clone.1.md @@ -61,7 +61,7 @@ The sum of all runtimes across containers cannot exceed the amount allotted to t This option is not supported on cgroups V2 systems. -#### **--cpu-shares**=*shares* +#### **--cpu-shares**, **-c**=*shares* CPU shares (relative weight) @@ -89,14 +89,15 @@ cores. Even if a container is limited to less than 100% of CPU time, it can use 100% of each individual CPU core. For example, consider a system with more than three cores. -container **{C0}** is started with **-c=512** running one process, and another container -**{C1}** with **-c=1024** running two processes, this can result in the following -division of CPU shares: - -PID container CPU CPU share -100 {C0} 0 100% of CPU0 -101 {C1} 1 100% of CPU1 -102 {C1} 2 100% of CPU2 +If the container _C0_ is started with **--cpu-shares=512** running one process, +and another container _C1_ with **--cpu-shares=1024** running two processes, +this can result in the following division of CPU shares: + +| PID | container | CPU | CPU share | +| ---- | ----------- | ------- | ------------ | +| 100 | C0 | 0 | 100% of CPU0 | +| 101 | C1 | 1 | 100% of CPU1 | +| 102 | C1 | 2 | 100% of CPU2 | If none are specified, the original container's CPU shares are used. diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 9bee25220..67bb573e2 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -201,7 +201,7 @@ The sum of all runtimes across containers cannot exceed the amount allotted to t This flag is not supported on cgroups V2 systems. -#### **--cpu-shares**=*shares* +#### **--cpu-shares**, **-c**=*shares* CPU shares (relative weight) @@ -228,21 +228,22 @@ On a multi-core system, the shares of CPU time are distributed over all CPU cores. Even if a container is limited to less than 100% of CPU time, it can use 100% of each individual CPU core. -For example, consider a system with more than three cores. If you start one -container **{C0}** with **-c=512** running one process, and another container -**{C1}** with **-c=1024** running two processes, this can result in the following -division of CPU shares: +For example, consider a system with more than three cores. +If the container _C0_ is started with **--cpu-shares=512** running one process, +and another container _C1_ with **--cpu-shares=1024** running two processes, +this can result in the following division of CPU shares: -PID container CPU CPU share -100 {C0} 0 100% of CPU0 -101 {C1} 1 100% of CPU1 -102 {C1} 2 100% of CPU2 +| PID | container | CPU | CPU share | +| ---- | ----------- | ------- | ------------ | +| 100 | C0 | 0 | 100% of CPU0 | +| 101 | C1 | 1 | 100% of CPU1 | +| 102 | C1 | 2 | 100% of CPU2 | #### **--cpus**=*number* Number of CPUs. The default is *0.0* which means no limit. This is shorthand for **--cpu-period** and **--cpu-quota**, so you may only set either -#### **--cpus** or **--cpu-period** and **--cpu-quota**. +**--cpus** or **--cpu-period** and **--cpu-quota**. On some systems, changing the CPU limits may not be allowed for non-root users. For more details, see @@ -260,7 +261,7 @@ If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1` then processes in your container will only use memory from the first two memory nodes. -#### **--device**=_host-device_[**:**_container-device_][**:**_permissions_] +#### **--device**=*host-device[:container-device][:permissions]* Add a host device to the container. Optional *permissions* parameter can be used to specify device permissions, it is combination of @@ -268,7 +269,7 @@ can be used to specify device permissions, it is combination of Example: **--device=/dev/sdc:/dev/xvdc:rwm**. -Note: if _host_device_ is a symbolic link then it will be resolved first. +Note: if *host-device* is a symbolic link then it will be resolved first. The container will only store the major and minor numbers of the host device. Note: if the user only has access rights via a group, accessing the device @@ -279,7 +280,7 @@ Podman may load kernel modules required for using the specified device. The devices that podman will load modules when necessary are: /dev/fuse. -#### **--device-cgroup-rule**="type major:minor mode" +#### **--device-cgroup-rule**=*"type major:minor mode"* Add a rule to the cgroup allowed devices list. The rule is expected to be in the format specified in the Linux kernel documentation (Documentation/cgroup-v1/devices.txt): - type: a (all), c (char), or b (block); @@ -375,7 +376,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* +#### **--group-add**=*group* | *keep-groups* Assign additional groups to the primary user running within the container process. @@ -418,7 +419,7 @@ value can be expressed in a time format such as `1m22s`. The default value is `3 Print usage statement -#### **--hostname**=*name*, **-h** +#### **--hostname**, **-h**=*name* Container host name @@ -449,7 +450,7 @@ container: Defaults to `true` -#### **--image-volume**=*bind|tmpfs|ignore* +#### **--image-volume**=**bind** | *tmpfs* | *ignore* Tells Podman how to handle the builtin image volumes. Default is **bind**. @@ -464,8 +465,9 @@ 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-ctr**=*type* (pods only) +#### **--init-ctr**=*type* +(Pods only). When using pods, create an init style container, which is run after the infra container is started but before regular pod containers are started. Init containers are useful for running setup operations for the pod's applications. @@ -530,7 +532,7 @@ Read in a line delimited file of labels Not implemented -#### **--log-driver**="*k8s-file*" +#### **--log-driver**=*driver* Logging driver for the container. Currently available options are *k8s-file*, *journald*, *none* and *passthrough*, with *json-file* aliased to *k8s-file* for scripting compatibility. @@ -543,7 +545,7 @@ The *passthrough* driver passes down the standard streams (stdin, stdout, stderr container. It is not allowed with the remote Podman client, including Mac and Windows (excluding WSL2) machines, and on a tty, since it is vulnerable to attacks via TIOCSTI. -#### **--log-opt**=*name*=*value* +#### **--log-opt**=*name=value* Set custom logging configuration. The following *name*s are supported: @@ -827,11 +829,11 @@ container. Rootless containers cannot have more privileges than the account that launched them. -#### **--publish**, **-p**=[[_ip_:][_hostPort_]:]_containerPort_[/_protocol_] +#### **--publish**, **-p**=*[[ip:][hostPort]:]containerPort[/protocol]* Publish a container's port, or range of ports, to the host. -Both hostPort and containerPort can be specified as a range of ports. +Both *hostPort* and *containerPort* can be specified as a range of ports. When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. @@ -867,7 +869,7 @@ port to a random port on the host within an *ephemeral port range* defined by `/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host ports and the exposed ports, use `podman port`. -#### **--pull**=**always**|**missing**|**never**|**newer** +#### **--pull**=*policy* Pull image policy. The default is **missing**. @@ -896,7 +898,7 @@ If container is running in --read-only mode, then mount a read-write tmpfs on /r If another container with the same name already exists, replace and remove it. The default is **false**. -#### **--requires**=**container** +#### **--requires**=*container* Specify one or more requirements. A requirement is a dependency container that will be started before this container. @@ -939,7 +941,7 @@ 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** +#### **--sdnotify**=**container** | *conmon* | *ignore* Determines how to use the NOTIFY_SOCKET, as passed with systemd and Type=notify. @@ -956,7 +958,7 @@ Specify the policy to select the seccomp profile. If set to *image*, Podman will Note that this feature is experimental and may change in the future. -#### **--secret**=*secret*[,opt=opt ...] +#### **--secret**=*secret[,opt=opt ...]* Give the container access to a secret. Can be specified multiple times. @@ -1051,7 +1053,7 @@ Network Namespace - current sysctls allowed: Note: if you use the --network=host option these sysctls will not be allowed. -#### **--systemd**=*true|false|always* +#### **--systemd**=*true* | *false* | *always* Run container in systemd mode. The default is *true*. @@ -1119,7 +1121,7 @@ standard input. 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 -#### **--uidmap**=*container_uid*:*from_uid*:*amount* +#### **--uidmap**=*container_uid:from_uid:amount* Run the container in a new user namespace using the supplied UID mapping. This option conflicts with the **--userns** and **--subuidname** options. This @@ -1214,7 +1216,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**=*true|false* +#### **--unsetenv-all** Unset all default environment variables for the container. Default environment variables include variables provided natively by Podman, environment variables @@ -1284,7 +1286,7 @@ Set the UTS namespace mode for the container. The following values are supported #### **--variant**=*VARIANT* Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7. -#### **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] +#### **--volume**, **-v**=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]* Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman @@ -1454,7 +1456,7 @@ Note: if the user only has access rights via a group, accessing the volume from inside a rootless container will fail. Use the `--group-add keep-groups` flag to pass the user's supplementary group access into the container. -#### **--volumes-from**[=*CONTAINER*[:*OPTIONS*]] +#### **--volumes-from**=*CONTAINER[:OPTIONS]]* Mount volumes from the specified container(s). Used to share volumes between containers. The *options* is a comma-separated list with the following available elements: diff --git a/docs/source/markdown/podman-events.1.md b/docs/source/markdown/podman-events.1.md index 5d5199e66..526a7fa10 100644 --- a/docs/source/markdown/podman-events.1.md +++ b/docs/source/markdown/podman-events.1.md @@ -77,7 +77,7 @@ The *volume* type will report the following statuses: ## OPTIONS -#### **--filter**=*filter* +#### **--filter**, **-f**=*filter* Filter events that are displayed. They must be in the format of "filter=value". The following filters are supported: @@ -129,7 +129,7 @@ $ podman events Show only Podman create events ``` -$ podman events --filter event=create +$ podman events -f event=create 2019-03-02 10:36:01.375685062 -0600 CST container create 20dc581f6fbf (image=docker.io/library/alpine:latest, name=sharp_morse) 2019-03-02 10:36:08.561188337 -0600 CST container create 58e7e002344c (image=k8s.gcr.io/pause:3.1, name=3e701f270d54-infra) 2019-03-02 10:36:13.146899437 -0600 CST volume create cad6dc50e087 (image=, name=cad6dc50e0879568e7d656bd004bd343d6035e7fc4024e1711506fe2fd459e6f) diff --git a/docs/source/markdown/podman-generate-kube.1.md b/docs/source/markdown/podman-generate-kube.1.md index cbb875f60..bd7bc711c 100644 --- a/docs/source/markdown/podman-generate-kube.1.md +++ b/docs/source/markdown/podman-generate-kube.1.md @@ -30,7 +30,7 @@ Note that the generated Kubernetes YAML file can be used to re-run the deploymen ## OPTIONS -#### **--filename**, **-f**=**filename** +#### **--filename**, **-f**=*filename* Output to the given file, instead of STDOUT. If the file already exists, `generate kube` will refuse to replace it and return an error. @@ -213,7 +213,7 @@ status: ``` ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-container(1)](podman-container.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-play-kube(1)](podman-play-kube.1.md)** +**[podman(1)](podman.1.md)**, **[podman-container(1)](podman-container.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)** ## HISTORY December 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/source/markdown/podman-generate-systemd.1.md b/docs/source/markdown/podman-generate-systemd.1.md index 56ad4e446..50881a509 100644 --- a/docs/source/markdown/podman-generate-systemd.1.md +++ b/docs/source/markdown/podman-generate-systemd.1.md @@ -55,7 +55,7 @@ Use the name of the container for the start, stop, and description in the unit f Using this flag will yield unit files that do not expect containers and pods to exist. Instead, new containers and pods are created based on their configuration files. The unit files are created best effort and may need to be further edited; please review the generated files carefully before using them in production. -Note that `--new` only works on containers and pods created directly via Podman (i.e., `podman [container] {create,run}` or `podman pod create`). It does not work on containers or pods created via the REST API or via `podman play kube`. +Note that `--new` only works on containers and pods created directly via Podman (i.e., `podman [container] {create,run}` or `podman pod create`). It does not work on containers or pods created via the REST API or via `podman kube play`. #### **--no-header** @@ -83,11 +83,11 @@ Takes a value in seconds. Set the systemd unit name separator between the name/id of a container/pod and the prefix. The default is *-*. -#### **--start-timeout** =*value* +#### **--start-timeout**=*value* Override the default start timeout for the container with the given value in seconds. -#### **--stop-timeout** =*value* +#### **--stop-timeout**=*value* Override the default stop timeout for the container with the given value in seconds. diff --git a/docs/source/markdown/podman-image-trust.1.md b/docs/source/markdown/podman-image-trust.1.md index 66d492922..4e80bdcf5 100644 --- a/docs/source/markdown/podman-image-trust.1.md +++ b/docs/source/markdown/podman-image-trust.1.md @@ -42,12 +42,12 @@ Trust may be updated using the command **podman image trust set** for an existin ### set OPTIONS -#### **--pubkeysfile**=*KEY1*, **-f** +#### **--pubkeysfile**, **-f**=*KEY1* A path to an exported public key on the local system. Key paths will be referenced in policy.json. Any path to a file may be used but locating the file in **/etc/pki/containers** is recommended. Options may be used multiple times to require an image be signed by multiple keys. The **--pubkeysfile** option is required for the **signedBy** type. -#### **--type**=*value*, **-t** +#### **--type**, **-t**=*value* The trust type for this policy entry. Accepted values: **signedBy** (default): Require signatures with corresponding list of diff --git a/docs/source/markdown/podman-images.1.md b/docs/source/markdown/podman-images.1.md index e28df840d..923b22f1b 100644 --- a/docs/source/markdown/podman-images.1.md +++ b/docs/source/markdown/podman-images.1.md @@ -23,7 +23,7 @@ Show all images (by default filter out the intermediate image layers). The defau Show image digests -#### **--filter**=*filter*, **-f** +#### **--filter**, **-f**=*filter* Provide filter values. @@ -100,9 +100,9 @@ Omit the table headings from the listing of images. Lists only the image IDs. -#### **--sort**=*sort*=*created* +#### **--sort**=*sort* -Sort by created, id, repository, size or tag (default: created) +Sort by *created*, *id*, *repository*, *size* or *tag* (default: **created**) ## EXAMPLE diff --git a/docs/source/markdown/podman-import.1.md b/docs/source/markdown/podman-import.1.md index bfe0291de..4002f5255 100644 --- a/docs/source/markdown/podman-import.1.md +++ b/docs/source/markdown/podman-import.1.md @@ -23,7 +23,7 @@ Note: `:` is a restricted character and cannot be part of the file name. Set architecture of the imported image. -#### **--change**=*instruction*, **-c** +#### **--change**, **-c**=*instruction* Apply the following possible instructions to the created image: **CMD** | **ENTRYPOINT** | **ENV** | **EXPOSE** | **LABEL** | **STOPSIGNAL** | **USER** | **VOLUME** | **WORKDIR** diff --git a/docs/source/markdown/podman-info.1.md b/docs/source/markdown/podman-info.1.md index 28e4f3291..347d8d27b 100644 --- a/docs/source/markdown/podman-info.1.md +++ b/docs/source/markdown/podman-info.1.md @@ -19,7 +19,7 @@ Displays information pertinent to the host, current storage stats, configured co Show additional information -#### **--format**=*format*, **-f** +#### **--format**, **-f**=*format* Change output format to "json" or a Go template. diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-kube-play.1.md index 92cb694b0..ee68380e6 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-kube-play.1.md @@ -1,15 +1,15 @@ -% podman-play-kube(1) +% podman-kube-play(1) ## NAME -podman-play-kube - Create containers, pods or volumes based on Kubernetes YAML +podman-kube-play - Create containers, pods or volumes based on Kubernetes YAML ## SYNOPSIS -**podman play kube** [*options*] *file.yml|-* +**podman kube play** [*options*] *file.yml|-* ## DESCRIPTION -**podman play kube** will read in a structured file of Kubernetes YAML. It will then recreate the containers, pods or volumes described in the YAML. Containers within a pod are then started and the ID of the new Pod or the name of the new Volume is output. If the yaml file is specified as "-" then `podman play kube` will read the YAML file from stdin. -Using the `--down` command line option, it is also capable of tearing down the pods created by a previous run of `podman play kube`. -Using the `--replace` command line option, it will tear down the pods(if any) created by a previous run of `podman play kube` and recreate the pods with the Kubernetes YAML file. +**podman kube play** will read in a structured file of Kubernetes YAML. It will then recreate the containers, pods or volumes described in the YAML. Containers within a pod are then started and the ID of the new Pod or the name of the new Volume is output. If the yaml file is specified as "-" then `podman kube play` will read the YAML file from stdin. +Using the `--down` command line option, it is also capable of tearing down the pods created by a previous run of `podman kube play`. +Using the `--replace` command line option, it will tear down the pods(if any) created by a previous run of `podman kube play` and recreate the pods with the Kubernetes YAML file. Ideally the input file would be one created by Podman (see podman-generate-kube(1)). This would guarantee a smooth import and expected results. Currently, the supported Kubernetes kinds are: @@ -20,14 +20,16 @@ Currently, the supported Kubernetes kinds are: `Kubernetes Pods or Deployments` -Only two volume types are supported by play kube, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume. +Only two volume types are supported by kube play, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume. Note: When playing a kube YAML with init containers, the init container will be created with init type value `always`. -Note: *hostPath* volume types created by play kube will be given an SELinux shared label (z), bind mounts are not relabeled (use `chcon -t container_file_t -R <directory>`). +Note: *hostPath* volume types created by kube play will be given an SELinux shared label (z), bind mounts are not relabeled (use `chcon -t container_file_t -R <directory>`). Note: If the `:latest` tag is used, Podman will attempt to pull the image from a registry. If the image was built locally with Podman or Buildah, it will have `localhost` as the domain, in that case, Podman will use the image from the local store even if it has the `:latest` tag. +Note: The command `podman play kube` is an alias of `podman kube play`, and will perform the same function. + `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. @@ -39,7 +41,7 @@ A Kubernetes PersistentVolumeClaim represents a Podman named volume. Only the Pe - volume.podman.io/gid - volume.podman.io/mount-options -Play kube is capable of building images on the fly given the correct directory layout and Containerfiles. This +Kube play is capable of building images on the fly given the correct directory layout and Containerfiles. This option is not available for remote clients, including Mac and Windows (excluding WSL2) machines, yet. Consider the following excerpt from a YAML file: ``` apiVersion: v1 @@ -57,7 +59,7 @@ spec: ``` If there is a directory named `foobar` in the current working directory with a file named `Containerfile` or `Dockerfile`, -Podman play kube will build that image and name it `foobar`. An example directory structure for this example would look +Podman kube play will build that image and name it `foobar`. An example directory structure for this example would look like: ``` |- mykubefiles @@ -103,19 +105,6 @@ spec: and as a result environment variable `FOO` will be set to `bar` for container `container-1`. -### Systemd Integration - -A Kubernetes YAML can be executed in systemd via the `podman-kube@.service` systemd template. The template's argument is the path to the YAML file. Given a `workload.yaml` file in the home directory, it can be executed as follows: - -``` -$ escaped=$(systemd-escape ~/sysadmin.yaml) -$ systemctl --user start podman-kube@$escaped.service -$ systemctl --user is-active podman-kube@$escaped.service -active -``` - -Note that the path to the YAML file must be escaped via `systemd-escape`. - ## OPTIONS #### **--annotation**=*key=value* @@ -158,7 +147,7 @@ value can be entered. The password is entered without echo. #### **--down** -Tears down the pods that were created by a previous run of `play kube`. The pods are stopped and then +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** @@ -167,14 +156,14 @@ Print usage statement #### **--ip**=*IP address* -Assign a static ip address to the pod. This option can be specified several times when play kube creates more than one pod. +Assign a static ip address to the pod. This option can be specified several times when kube play creates more than one pod. Note: When joining multiple networks you should use the **--network name:ip=\<ip\>** syntax. -#### **--log-driver**=driver +#### **--log-driver**=*driver* Set logging driver for all created containers. -#### **--log-opt**=*name*=*value* +#### **--log-opt**=*name=value* Set custom logging configuration. The following *name*s are supported: @@ -193,7 +182,7 @@ This option is currently supported only by the **journald** log driver. #### **--mac-address**=*MAC address* -Assign a static mac address to the pod. This option can be specified several times when play kube creates more than one pod. +Assign a static mac address to the pod. This option can be specified several times when kube play creates more than one pod. Note: When joining multiple networks you should use the **--network name:mac=\<mac\>** syntax. #### **--network**=*mode*, **--net** @@ -240,7 +229,7 @@ Suppress output information when pulling images #### **--replace** -Tears down the pods created by a previous run of `play kube` and recreates the pods. This option is used to keep the existing pods up to date based upon the Kubernetes YAML. +Tears down the pods created by a previous run of `kube play` and recreates the pods. This option is used to keep the existing pods up to date based upon the Kubernetes YAML. #### **--seccomp-profile-root**=*path* @@ -299,19 +288,19 @@ Podman allocates unique ranges of UIDs and GIDs from the `containers` subordinat Recreate the pod and containers as described in a file called `demo.yml` ``` -$ podman play kube demo.yml +$ podman kube play demo.yml 52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 ``` Recreate the pod and containers as described in a file `demo.yml` sent to stdin ``` -$ cat demo.yml | podman play kube - +$ cat demo.yml | podman kube play - 52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 ``` Teardown the pod and containers as described in a file `demo.yml` ``` -$ podman play kube --down demo.yml +$ podman kube play --down demo.yml Pods stopped: 52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 Pods removed: @@ -320,23 +309,23 @@ Pods removed: Provide `configmap-foo.yml` and `configmap-bar.yml` as sources for environment variables within the containers. ``` -$ podman play kube demo.yml --configmap configmap-foo.yml,configmap-bar.yml +$ podman kube play demo.yml --configmap configmap-foo.yml,configmap-bar.yml 52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 -$ podman play kube demo.yml --configmap configmap-foo.yml --configmap configmap-bar.yml +$ podman kube play demo.yml --configmap configmap-foo.yml --configmap configmap-bar.yml 52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 ``` Create a pod connected to two networks (called net1 and net2) with a static ip ``` -$ podman play kube demo.yml --network net1:ip=10.89.1.5 --network net2:ip=10.89.10.10 +$ podman kube play demo.yml --network net1:ip=10.89.1.5 --network net2:ip=10.89.10.10 52182811df2b1e73f36476003a66ec872101ea59034ac0d4d3a7b40903b955a6 ``` Please take into account that CNI networks must be created first using podman-network-create(1). ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-play(1)](podman-play.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-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-play.1.md b/docs/source/markdown/podman-kube.1.md index e5f0d315e..f815ffae2 100644 --- a/docs/source/markdown/podman-play.1.md +++ b/docs/source/markdown/podman-kube.1.md @@ -1,20 +1,20 @@ -% podman-play(1) +% podman-kube(1) ## NAME -podman\-play - Play containers, pods or volumes based on a structured input file +podman\-kube - Play containers, pods or volumes based on a structured input file ## SYNOPSIS -**podman play** *subcommand* +**podman kube** *subcommand* ## DESCRIPTION -The play command will recreate containers, pods or volumes based on the input from a structured (like YAML) +The kube command will recreate containers, pods or volumes based on the input from a structured (like YAML) file input. Containers will be automatically started. ## COMMANDS | Command | Man Page | Description | | ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- | -| kube | [podman-play-kube(1)](podman-play-kube.1.md) | Create containers, pods or volumes based on Kubernetes YAML. | +| play | [podman-kube-play(1)](podman-kube-play.1.md) | Create containers, pods or 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-play-kube(1)](podman-play-kube.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)** diff --git a/docs/source/markdown/podman-machine-info.1.md b/docs/source/markdown/podman-machine-info.1.md index 33c207d32..926d3d8b3 100644 --- a/docs/source/markdown/podman-machine-info.1.md +++ b/docs/source/markdown/podman-machine-info.1.md @@ -13,7 +13,7 @@ Rootless only, as all `podman machine` commands can be only be used with rootles ## OPTIONS -#### **--format**=*format*, **-f** +#### **--format**, **-f**=*format* Change output format to "json" or a Go template. diff --git a/docs/source/markdown/podman-machine-init.1.md b/docs/source/markdown/podman-machine-init.1.md index 21c98b2c7..07273a111 100644 --- a/docs/source/markdown/podman-machine-init.1.md +++ b/docs/source/markdown/podman-machine-init.1.md @@ -62,7 +62,7 @@ Memory (in MB). Start the virtual machine immediately after it has been initialized. -#### **--rootful**=*true|false* +#### **--rootful** Whether this machine should prefer rootful (`true`) or rootless (`false`) container execution. This option will also determine the remote connection default diff --git a/docs/source/markdown/podman-machine-set.1.md b/docs/source/markdown/podman-machine-set.1.md index 1daf97a61..52338cedb 100644 --- a/docs/source/markdown/podman-machine-set.1.md +++ b/docs/source/markdown/podman-machine-set.1.md @@ -33,7 +33,7 @@ Print usage statement. Memory (in MB). Only supported for QEMU machines. -#### **--rootful**=*true|false* +#### **--rootful** Whether this machine should prefer rootful (`true`) or rootless (`false`) container execution. This option will also update the current podman diff --git a/docs/source/markdown/podman-manifest-add.1.md b/docs/source/markdown/podman-manifest-add.1.md index 40f841bf8..5aa7f8341 100644 --- a/docs/source/markdown/podman-manifest-add.1.md +++ b/docs/source/markdown/podman-manifest-add.1.md @@ -22,7 +22,7 @@ index, add all of the contents to the local list. By default, only one image from such a list or index will be added to the list or index. Combining *--all* with any of the other options described below is NOT recommended. -#### **--annotation** *annotation=value* +#### **--annotation**=*annotation=value* Set an annotation on the entry for the newly-added image. diff --git a/docs/source/markdown/podman-manifest-annotate.1.md b/docs/source/markdown/podman-manifest-annotate.1.md index 2b8b70aa4..36c35c7c8 100644 --- a/docs/source/markdown/podman-manifest-annotate.1.md +++ b/docs/source/markdown/podman-manifest-annotate.1.md @@ -12,7 +12,7 @@ Adds or updates information about an image included in a manifest list or image ## OPTIONS -#### **--annotation** *annotation=value* +#### **--annotation**=*annotation=value* Set an annotation on the entry for the specified image. diff --git a/docs/source/markdown/podman-network-create.1.md b/docs/source/markdown/podman-network-create.1.md index 1d89b12e3..3587ac2a8 100644 --- a/docs/source/markdown/podman-network-create.1.md +++ b/docs/source/markdown/podman-network-create.1.md @@ -67,7 +67,7 @@ Enable IPv6 (Dual Stack) networking. If not subnets are given it will allocate a Set metadata for a network (e.g., --label mykey=value). -#### **--opt**=*option*, **-o** +#### **--opt**, **-o**=*option* Set driver specific options. diff --git a/docs/source/markdown/podman-pod-clone.1.md b/docs/source/markdown/podman-pod-clone.1.md index d90d1efb9..49084386c 100644 --- a/docs/source/markdown/podman-pod-clone.1.md +++ b/docs/source/markdown/podman-pod-clone.1.md @@ -27,7 +27,7 @@ CPUs in which to allow execution (0-3, 0,1). If none are specified, the original Remove the original pod that we are cloning once used to mimic the configuration. -#### **--device**=_host-device_[**:**_container-device_][**:**_permissions_] +#### **--device**=*host-device[:container-device][:permissions]* Add a host device to the pod. Optional *permissions* parameter can be used to specify device permissions. It is a combination of @@ -56,7 +56,7 @@ GID map for the user namespace. Using this flag will run all containers in the p Print usage statement. -#### **--hostname**=name +#### **--hostname**=*name* Set a hostname to the pod. @@ -72,7 +72,7 @@ Write the pid of the infra container's **conmon** process to a file. As **conmon The name that will be used for the pod's infra container. -#### **--label**=*label*, **-l** +#### **--label**, **-l**=*label* Add metadata to a pod (e.g., --label com.example.key=value). @@ -153,7 +153,7 @@ Name for GID map from the `/etc/subgid` file. Using this flag will run the conta Name for UID map from the `/etc/subuid` file. Using this flag will run the container with user namespace enabled. This flag conflicts with `--userns` and `--uidmap`. -#### **--sysctl**=_name_=_value_ +#### **--sysctl**=*name=value* Configure namespace kernel parameters for all containers in the new pod. @@ -175,7 +175,7 @@ For the network namespace, only sysctls beginning with net.\* are allowed. Note: if the network namespace is not shared within the pod, these sysctls are not allowed. -#### **--uidmap**=*container_uid*:*from_uid*:*amount* +#### **--uidmap**=*container_uid:from_uid:amount* Run all containers in the pod in a new user namespace using the supplied mapping. This option conflicts with the **--userns** and **--subuidname** options. This @@ -220,7 +220,7 @@ Set the UTS namespace mode for the pod. The following values are supported: - **ns:[path]**: run the pod in the given existing UTS namespace. -#### **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] +#### **--volume**, **-v**=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]* Create a bind mount. If ` -v /HOST-DIR:/CONTAINER-DIR` is specified, Podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman @@ -381,7 +381,7 @@ change propagation properties of source mount. Say `/` is source mount for Note: if the user only has access rights via a group, accessing the volume from inside a rootless pod will fail. -#### **--volumes-from**[=*CONTAINER*[:*OPTIONS*]] +#### **--volumes-from**=*container[:options]]* Mount volumes from the specified container(s). Used to share volumes between containers and pods. The *options* is a comma-separated list with the following available elements: diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md index 53d1e3327..de9a34bfa 100644 --- a/docs/source/markdown/podman-pod-create.1.md +++ b/docs/source/markdown/podman-pod-create.1.md @@ -25,7 +25,7 @@ for it. The name is useful any place you need to identify a pod. ## OPTIONS -#### **--add-host**=_host_:_ip_ +#### **--add-host**=*host:ip* Add a custom host-to-IP mapping (host:ip) @@ -52,7 +52,7 @@ Examples of the List Format: 0-4,9 # bits 0, 1, 2, 3, 4, and 9 set 0-2,7,12-14 # bits 0, 1, 2, 7, 12, 13, and 14 set -#### **--device**=_host-device_[**:**_container-device_][**:**_permissions_] +#### **--device**=*host-device[:container-device][:permissions]* Add a host device to the pod. Optional *permissions* parameter can be used to specify device permissions. It is a combination of @@ -60,7 +60,7 @@ can be used to specify device permissions. It is a combination of Example: **--device=/dev/sdc:/dev/xvdc:rwm**. -Note: if _host_device_ is a symbolic link then it will be resolved first. +Note: if *host-device* is a symbolic link then it will be resolved first. The pod will only store the major and minor numbers of the host device. Note: the pod implements devices by storing the initial configuration passed by the user and recreating the device on each container added to the pod. @@ -92,7 +92,7 @@ Set the exit policy of the pod when the last container exits. Supported policie | Exit Policy | Description | | ------------------ | --------------------------------------------------------------------------- | | *continue* | The pod continues running when the last container exits. Used by default. | -| *stop* | The pod is stopped when the last container exits. Used in `play kube`. | +| *stop* | The pod is stopped when the last container exits. Used in `kube play`. | #### **--gidmap**=*container_gid:host_gid:amount* @@ -102,7 +102,7 @@ GID map for the user namespace. Using this flag will run the container with user Print usage statement. -#### **--hostname**=name +#### **--hostname**=*name* Set a hostname to the pod @@ -144,7 +144,7 @@ The address must be within the network's IPv6 address pool. To specify multiple static IPv6 addresses per pod, set multiple networks using the **--network** option with a static IPv6 address specified for each using the `ip6` mode for that option. -#### **--label**=*label*, **-l** +#### **--label**, **-l**=*label* Add metadata to a pod (e.g., --label com.example.key=value). @@ -175,7 +175,7 @@ not limited. The actual limit may be rounded up to a multiple of the operating system's page size (the value would be very large, that's millions of trillions). -#### **--name**=*name*, **-n** +#### **--name**, **-n**=*name* Assign a name to the pod. @@ -237,11 +237,11 @@ Set the PID mode for the pod. The default is to create a private PID namespace f Write the pod ID to the file. -#### **--publish**, **-p**=[[_ip_:][_hostPort_]:]_containerPort_[/_protocol_] +#### **--publish**, **-p**=*[[ip:][hostPort]:]containerPort[/protocol]* Publish a container's port, or range of ports, within this pod to the host. -Both hostPort and containerPort can be specified as a range of ports. +Both *hostPort* and *containerPort* can be specified as a range of ports. When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. @@ -323,7 +323,7 @@ Name for GID map from the `/etc/subgid` file. Using this flag will run the conta Name for UID map from the `/etc/subuid` file. Using this flag will run the container with user namespace enabled. This flag conflicts with `--userns` and `--uidmap`. -#### **--sysctl**=_name_=_value_ +#### **--sysctl**=*name=value* Configure namespace kernel parameters for all containers in the pod. @@ -345,7 +345,7 @@ For the network namespace, only sysctls beginning with net.\* are allowed. Note: if the network namespace is not shared within the pod, these sysctls are not allowed. -#### **--uidmap**=*container_uid*:*from_uid*:*amount* +#### **--uidmap**=*container_uid:from_uid:amount* Run the container in a new user namespace using the supplied mapping. This option conflicts with the **--userns** and **--subuidname** options. This @@ -389,7 +389,7 @@ Set the UTS namespace mode for the pod. The following values are supported: - **private**: create a new namespace for the pod (default). - **ns:[path]**: run the pod in the given existing UTS namespace. -#### **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] +#### **--volume**, **-v**=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]* Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Podman bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Podman @@ -548,7 +548,7 @@ change propagation properties of source mount. Say `/` is source mount for Note: if the user only has access rights via a group, accessing the volume from inside a rootless pod will fail. -#### **--volumes-from**[=*CONTAINER*[:*OPTIONS*]] +#### **--volumes-from**=*container[:options]]* Mount volumes from the specified container(s). Used to share volumes between containers and pods. The *options* is a comma-separated list with the following available elements: @@ -603,7 +603,7 @@ $ podman pod create --network net1:ip=10.89.1.5 --network net2:ip=10.89.10.10 ``` ## SEE ALSO -**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-play-kube(1)](podman-play-kube.1.md)**, **containers.conf(1)** +**[podman(1)](podman.1.md)**, **[podman-pod(1)](podman-pod.1.md)**, **[podman-kube-play(1)](podman-kube-play.1.md)**, **containers.conf(1)** ## HISTORY diff --git a/docs/source/markdown/podman-pod-inspect.1.md b/docs/source/markdown/podman-pod-inspect.1.md index 3105ebaab..d0a165b05 100644 --- a/docs/source/markdown/podman-pod-inspect.1.md +++ b/docs/source/markdown/podman-pod-inspect.1.md @@ -12,7 +12,7 @@ that belong to the pod. ## OPTIONS -#### **--format**=*format*, **-f** +#### **--format**, **-f**=*format* Change the default output format. This can be of a supported type like 'json' or a Go template. diff --git a/docs/source/markdown/podman-pull.1.md b/docs/source/markdown/podman-pull.1.md index 928bbc6fe..99e227226 100644 --- a/docs/source/markdown/podman-pull.1.md +++ b/docs/source/markdown/podman-pull.1.md @@ -43,7 +43,7 @@ $ podman pull oci-archive:/tmp/myimage ``` ## OPTIONS -#### **--all-tags** +#### **--all-tags**, **-a** All tagged images in the repository will be pulled. diff --git a/docs/source/markdown/podman-push.1.md b/docs/source/markdown/podman-push.1.md index 25c1e024a..3cda982ac 100644 --- a/docs/source/markdown/podman-push.1.md +++ b/docs/source/markdown/podman-push.1.md @@ -65,7 +65,7 @@ Please refer to containers-certs.d(5) for details. (This option is not available Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type, compressed or uncompressed, as source) Note: This flag can only be set when using the **dir** transport -#### **--compression-format** *COMPRESSION* +#### **--compression-format**=**gzip** | *zstd* | *zstd:chunked* Specifies the compression format to use. Supported values are: `gzip`, `zstd` and `zstd:chunked`. The default is `gzip`. @@ -75,7 +75,7 @@ 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. -#### **--digestfile** *Digestfile* +#### **--digestfile**=*Digestfile* After copying the image, write the digest of the resulting image to the file. (This option is not available with the remote Podman client, including Mac and Windows (excluding WSL2) machines) diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index a6285d4e0..4566a73d0 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -83,14 +83,14 @@ and specified with a _tag_. $ podman run oci-archive:/tmp/fedora echo hello ## OPTIONS -#### **--add-host**=_host_:_ip_ +#### **--add-host**=*host:ip* Add a custom host-to-IP mapping (host:ip) Add a line to /etc/hosts. The format is hostname:ip. The **--add-host** option can be set multiple times. -#### **--annotation**=_key_=_value_ +#### **--annotation**=*key=value* Add an annotation to the container. This option can be set multiple times. @@ -98,7 +98,7 @@ This option can be set multiple times. #### **--arch**=*ARCH* Override the architecture, defaults to hosts, of the image to be pulled. For example, `arm`. -#### **--attach**, **-a**=**stdin**|**stdout**|**stderr** +#### **--attach**, **-a**=*stdin* | *stdout* | *stderr* Attach to STDIN, STDOUT or STDERR. @@ -109,7 +109,7 @@ error. It can even pretend to be a TTY (this is what most commandline executables expect) and pass along signals. The **-a** option can be set for each of **stdin**, **stdout**, and **stderr**. -#### **--authfile**[=*path*] +#### **--authfile**=*[path]* Path to the authentication file. Default is *${XDG_RUNTIME_DIR}/containers/auth.json*. @@ -120,7 +120,7 @@ environment variable. Block IO relative weight. The _weight_ is a value between **10** and **1000**. -#### **--blkio-weight-device**=*device*:*weight* +#### **--blkio-weight-device**=*device:weight* Block IO relative device weight. @@ -151,7 +151,7 @@ Set the cgroup namespace mode for the container. If the host uses cgroups v1, the default is set to **host**. On cgroups v2, the default is **private**. -#### **--cgroups**=**enabled**|**disabled**|**no-conmon**|**split** +#### **--cgroups**=*how* Determines whether the container will create CGroups. @@ -220,7 +220,7 @@ The sum of all runtimes across containers cannot exceed the amount allotted to t This flag is not supported on cgroups V2 systems. -#### **--cpu-shares**=*shares* +#### **--cpu-shares**, **-c**=*shares* CPU shares (relative weight). @@ -244,22 +244,22 @@ On a multi-core system, the shares of CPU time are distributed over all CPU cores. Even if a container is limited to less than 100% of CPU time, it can use 100% of each individual CPU core. -For example, consider a system with more than three cores. If you start one -container **{C0}** with **--cpu-shares=512** running one process, and another container -**{C1}** with **--cpu-shares=1024** running two processes, this can result in the following -division of CPU shares: +For example, consider a system with more than three cores. +If the container _C0_ is started with **--cpu-shares=512** running one process, +and another container _C1_ with **--cpu-shares=1024** running two processes, +this can result in the following division of CPU shares: | PID | container | CPU | CPU share | | ---- | ----------- | ------- | ------------ | -| 100 | {C0} | 0 | 100% of CPU0 | -| 101 | {C1} | 1 | 100% of CPU1 | -| 102 | {C1} | 2 | 100% of CPU2 | +| 100 | C0 | 0 | 100% of CPU0 | +| 101 | C1 | 1 | 100% of CPU1 | +| 102 | C1 | 2 | 100% of CPU2 | #### **--cpus**=*number* Number of CPUs. The default is *0.0* which means no limit. This is shorthand for **--cpu-period** and **--cpu-quota**, so you may only set either -#### **--cpus** or **--cpu-period** and **--cpu-quota**. +**--cpus** or **--cpu-period** and **--cpu-quota**. On some systems, changing the CPU limits may not be allowed for non-root users. For more details, see @@ -297,7 +297,7 @@ Specify the key sequence for detaching a container. Format is a single character This option can also be set in **containers.conf**(5) file. -#### **--device**=_host-device_[**:**_container-device_][**:**_permissions_] +#### **--device**=*host-device[:container-device][:permissions]* Add a host device to the container. Optional *permissions* parameter can be used to specify device permissions by combining @@ -316,23 +316,23 @@ Podman may load kernel modules required for using the specified device. The devices that Podman will load modules when necessary are: /dev/fuse. -#### **--device-cgroup-rule**=rule +#### **--device-cgroup-rule**=*rule* Add a rule to the cgroup allowed devices list -#### **--device-read-bps**=_path_:_rate_ +#### **--device-read-bps**=*path:rate* Limit read rate (in bytes per second) from a device (e.g. **--device-read-bps=/dev/sda:1mb**). -#### **--device-read-iops**=_path_:_rate_ +#### **--device-read-iops**=*path:rate* Limit read rate (in IO operations per second) from a device (e.g. **--device-read-iops=/dev/sda:1000**). -#### **--device-write-bps**=_path_:_rate_ +#### **--device-write-bps**=*path:rate* Limit write rate (in bytes per second) to a device (e.g. **--device-write-bps=/dev/sda:1mb**). -#### **--device-write-iops**=_path_:_rate_ +#### **--device-write-iops**=*path:rate* Limit write rate (in IO operations per second) to a device (e.g. **--device-write-iops=/dev/sda:1000**). @@ -402,7 +402,7 @@ Use host environment inside of the container. See **Environment** note below for Expose a port, or a range of ports (e.g. **--expose=3300-3310**) to set up port redirection on the host system. -#### **--gidmap**=*container_gid*:*host_gid*:*amount* +#### **--gidmap**=*container_gid:host_gid:amount* Run the container in a new user namespace using the supplied GID mapping. This option conflicts with the **--userns** and **--subgidname** options. This @@ -411,7 +411,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* +#### **--group-add**=*group* | *keep-groups* Assign additional groups to the primary user running within the container process. @@ -454,7 +454,7 @@ value can be expressed in a time format such as **1m22s**. The default value is Print usage statement -#### **--hostname**=*name*, **-h** +#### **--hostname**, **-h**=*name* Container host name @@ -480,7 +480,7 @@ proxy environment at container build time.) (This option is not available with t Defaults to **true**. -#### **--image-volume**=**bind**|**tmpfs**|**ignore** +#### **--image-volume**=**bind** | *tmpfs* | *ignore* Tells Podman how to handle the builtin image volumes. Default is **bind**. @@ -534,7 +534,7 @@ a private IPC namespace. - **private**: private IPC namespace. = **shareable**: private IPC namespace with a possibility to share it with other containers. -#### **--label**, **-l**=*key*=*value* +#### **--label**, **-l**=*key=value* Add metadata to a container. @@ -546,9 +546,9 @@ Read in a line-delimited file of labels. Not implemented. -#### **--log-driver**="*driver*" +#### **--log-driver**=*driver* -Logging driver for the container. Currently available options are **k8s-file**, **journald**, **none** and **passthrough**, with **json-file** aliased to **k8s-file** for scripting compatibility. (Default journald) +Logging driver for the container. Currently available options are **k8s-file**, **journald**, **none** and **passthrough**, with **json-file** aliased to **k8s-file** for scripting compatibility. (Default **journald**) The podman info command below will display the default log-driver for the system. ``` @@ -560,7 +560,7 @@ container. It is not allowed with the remote Podman client, including Mac and W vulnerable to attacks via TIOCSTI. -#### **--log-opt**=*name*=*value* +#### **--log-opt**=*name=value* Logging driver specific options. @@ -589,7 +589,7 @@ according to RFC4862. To specify multiple static MAC addresses per container, set multiple networks using the **--network** option with a static MAC address specified for each using the `mac` mode for that option. -#### **--memory**, **-m**=_number_[_unit_] +#### **--memory**, **-m**=*number[unit]* Memory limit. A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). @@ -599,7 +599,7 @@ RAM. If a limit of 0 is specified (not using **-m**), the container's memory is not limited. The actual limit may be rounded up to a multiple of the operating system's page size (the value would be very large, that's millions of trillions). -#### **--memory-reservation**=_number_[_unit_] +#### **--memory-reservation**=*number[unit]* Memory soft limit. A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). @@ -609,7 +609,7 @@ reservation. So you should always set the value below **--memory**, otherwise th hard limit will take precedence. By default, memory reservation will be the same as memory limit. -#### **--memory-swap**=_number_[_unit_] +#### **--memory-swap**=*number[unit]* A limit value equal to memory plus swap. A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). @@ -858,11 +858,11 @@ points, Apparmor/SELinux separation, and Seccomp filters are all disabled. Rootless containers cannot have more privileges than the account that launched them. -#### **--publish**, **-p**=[[_ip_:][_hostPort_]:]_containerPort_[/_protocol_] +#### **--publish**, **-p**=*[[ip:][hostPort]:]containerPort[/protocol]* Publish a container's port, or range of ports, to the host. -Both hostPort and containerPort can be specified as a range of ports. +Both *hostPort* and *containerPort* can be specified as a range of ports. When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. @@ -899,7 +899,7 @@ When using this option, Podman will bind any exposed port to a random port on th within an ephemeral port range defined by */proc/sys/net/ipv4/ip_local_port_range*. To find the mapping between the host ports and the exposed ports, use **podman port**. -#### **--pull**=**always**|**missing**|**never**|**newer** +#### **--pull**=*policy* Pull image policy. The default is **missing**. @@ -928,7 +928,7 @@ If container is running in **--read-only** mode, then mount a read-write tmpfs o If another container with the same name already exists, replace and remove it. The default is **false**. -#### **--requires**=**container** +#### **--requires**=*container* Specify one or more requirements. A requirement is a dependency container that will be started before this container. @@ -979,7 +979,7 @@ 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** +#### **--sdnotify**=**container** | *conmon* | *ignore* Determines how to use the NOTIFY_SOCKET, as passed with systemd and Type=notify. @@ -996,7 +996,7 @@ Specify the policy to select the seccomp profile. If set to *image*, Podman will Note that this feature is experimental and may change in the future. -#### **--secret**=*secret*[,opt=opt ...] +#### **--secret**=*secret[,opt=opt ...]* Give the container access to a secret. Can be specified multiple times. @@ -1051,7 +1051,7 @@ Note: Labeling can be disabled for all containers by setting label=false in the Note: Labeling can be disabled for all containers by setting **label=false** in the **containers.conf**(5) file. -#### **--shm-size**=_number_[_unit_] +#### **--shm-size**=*number[unit]* Size of _/dev/shm_. A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). If you omit the unit, the system uses bytes. If you omit the size entirely, the default is **64m**. @@ -1082,7 +1082,7 @@ Run the container in a new user namespace using the map with _name_ in the _/etc If calling **podman run** as an unprivileged user, the user needs to have the right to use the mapping. See **subuid**(5). This flag conflicts with **--userns** and **--uidmap**. -#### **--sysctl**=_name_=_value_ +#### **--sysctl**=*name=value* Configure namespaced kernel parameters at runtime. @@ -1106,7 +1106,7 @@ For the network namespace, the following sysctls are allowed: Note: if you use the **--network=host** option, these sysctls will not be allowed. -#### **--systemd**=**true**|**false**|**always** +#### **--systemd**=*true* | *false* | *always* Run container in systemd mode. The default is **true**. @@ -1180,7 +1180,7 @@ echo "asdf" | podman run --rm -i someimage /bin/cat 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 -#### **--uidmap**=*container_uid*:*from_uid*:*amount* +#### **--uidmap**=*container_uid:from_uid:amount* Run the container in a new user namespace using the supplied UID mapping. This option conflicts with the **--userns** and **--subuidname** options. This @@ -1275,15 +1275,15 @@ 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**=*true|false* +#### **--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. -#### **--user**, **-u**=[_user_ | _user_:_group_ | _uid_ | _uid_:_gid_ | _user_:_gid_ | _uid_:_group_ ] +#### **--user**, **-u**=*user[:group]* -Sets the username or UID used and optionally the groupname or GID for the specified command. +Sets the username or UID used and, optionally, the groupname or GID for the specified command. Both *user* and *group* may be symbolic or numeric. Without this argument, the command will run as the user specified in the container image. Unless overridden by a `USER` command in the Containerfile or by a value passed to this option, this user generally defaults to root. @@ -1345,7 +1345,7 @@ Set the UTS namespace mode for the container. The following values are supported #### **--variant**=*VARIANT* Use _VARIANT_ instead of the default architecture variant of the container image. Some images can use multiple variants of the arm architectures, such as arm/v5 and arm/v7. -#### **--volume**, **-v**[=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] +#### **--volume**, **-v**=*[[SOURCE-VOLUME|HOST-DIR:]CONTAINER-DIR[:OPTIONS]]* Create a bind mount. If you specify _/HOST-DIR_:_/CONTAINER-DIR_, Podman bind mounts _host-dir_ in the host to _CONTAINER-DIR_ in the Podman @@ -1519,7 +1519,7 @@ Note: if the user only has access rights via a group, accessing the volume from inside a rootless container will fail. Use the `--group-add keep-groups` flag to pass the user's supplementary group access into the container. -#### **--volumes-from**[=*CONTAINER*[:*OPTIONS*]] +#### **--volumes-from**=*CONTAINER[:OPTIONS]* Mount volumes from the specified container(s). Used to share volumes between containers. The *options* is a comma-separated list with the following available elements: diff --git a/docs/source/markdown/podman-stats.1.md b/docs/source/markdown/podman-stats.1.md index 472cbfbcf..d87da6a60 100644 --- a/docs/source/markdown/podman-stats.1.md +++ b/docs/source/markdown/podman-stats.1.md @@ -44,7 +44,7 @@ Valid placeholders for the Go template are listed below: When using a GO template, you may precede the format with `table` to print headers. -#### **--interval**=*seconds*, **-i**=*seconds* +#### **--interval**, **-i**=*seconds* Time in seconds between stats reports, defaults to 5 seconds. diff --git a/docs/source/markdown/podman-stop.1.md b/docs/source/markdown/podman-stop.1.md index e35ab9182..cfc49afa1 100644 --- a/docs/source/markdown/podman-stop.1.md +++ b/docs/source/markdown/podman-stop.1.md @@ -25,6 +25,30 @@ Stop all running containers. This does not include paused containers. Read container ID from the specified file and remove the container. Can be specified multiple times. +#### **--filter**, **-f**=*filter* + +Filter what containers are going to be stopped. +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 | + #### **--ignore**, **-i** Ignore errors when specified containers are not in the container store. A user diff --git a/docs/source/markdown/podman-system-connection-add.1.md b/docs/source/markdown/podman-system-connection-add.1.md index 76f222f2b..d608ab7a7 100644 --- a/docs/source/markdown/podman-system-connection-add.1.md +++ b/docs/source/markdown/podman-system-connection-add.1.md @@ -17,9 +17,9 @@ The user will be prompted for the remote ssh login password or key file pass phr ## OPTIONS -#### **--default**=*false*, **-d** +#### **--default**, **-d** -Make the new destination the default for this user. +Make the new destination the default for this user. The default is **false**. #### **--identity**=*path* @@ -27,7 +27,7 @@ Path to ssh identity file. If the identity file has been encrypted, Podman promp If no identity file is provided and no user is given, Podman defaults to the user running the podman command. Podman prompts for the login password on the remote server. -#### **--port**=*port*, **-p** +#### **--port**, **-p**=*port* Port for ssh destination. The default value is `22`. diff --git a/docs/source/markdown/podman-system-connection-remove.1.md b/docs/source/markdown/podman-system-connection-remove.1.md index 4914ac9e9..348b49876 100644 --- a/docs/source/markdown/podman-system-connection-remove.1.md +++ b/docs/source/markdown/podman-system-connection-remove.1.md @@ -11,7 +11,7 @@ Delete named ssh destination. ## OPTIONS -#### **--all**=*false*, **-a** +#### **--all**, **-a** Remove all connections. diff --git a/docs/source/markdown/podman-volume-create.1.md b/docs/source/markdown/podman-volume-create.1.md index f43e647bf..65b788851 100644 --- a/docs/source/markdown/podman-volume-create.1.md +++ b/docs/source/markdown/podman-volume-create.1.md @@ -23,11 +23,11 @@ Specify the volume driver name (default **local**). Setting this to a value othe Print usage statement -#### **--label**=*label*, **-l** +#### **--label**, **-l**=*label* Set metadata for a volume (e.g., --label mykey=value). -#### **--opt**=*option*, **-o** +#### **--opt**, **-o**=*option* Set driver specific options. For the default driver, **local**, this allows a volume to be configured to mount a filesystem on the host. diff --git a/docs/source/markdown/podman-volume-inspect.1.md b/docs/source/markdown/podman-volume-inspect.1.md index dda83e558..9be0f9c2d 100644 --- a/docs/source/markdown/podman-volume-inspect.1.md +++ b/docs/source/markdown/podman-volume-inspect.1.md @@ -20,7 +20,7 @@ Volumes can be queried individually by providing their full name or a unique par Inspect all volumes. -#### **--format**=*format*, **-f** +#### **--format**, **-f**=*format* Format volume output using Go template diff --git a/docs/source/markdown/podman-volume-ls.1.md b/docs/source/markdown/podman-volume-ls.1.md index 4a3489425..86896b0a2 100644 --- a/docs/source/markdown/podman-volume-ls.1.md +++ b/docs/source/markdown/podman-volume-ls.1.md @@ -14,7 +14,7 @@ flag. Use the **--quiet** flag to print only the volume names. ## OPTIONS -#### **--filter**=*filter*, **-f** +#### **--filter**, **-f**=*filter* Volumes can be filtered by the following attributes: diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index 3d1578ea1..4c019ae97 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -325,7 +325,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. | | [podman-network(1)](podman-network.1.md) | Manage Podman networks. | | [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. | -| [podman-play(1)](podman-play.1.md) | Play containers, pods or volumes based on a structured input file. | +| [podman-kube(1)](podman-kube.1.md) | Play containers, pods or volumes based on a structured input file. | | [podman-pod(1)](podman-pod.1.md) | Management tool for groups of containers, called pods. | | [podman-port(1)](podman-port.1.md) | List port mappings for a container. | | [podman-ps(1)](podman-ps.1.md) | Prints out information about containers. | diff --git a/docs/tutorials/README.md b/docs/tutorials/README.md index c7c1a3616..2cdb86fa0 100644 --- a/docs/tutorials/README.md +++ b/docs/tutorials/README.md @@ -31,3 +31,7 @@ Learn how to set up and use image signing with Podman. **[Basic Networking](basic_networking.md)** A basic guide to common network setups with Podman + +**[Socket activation](socket_activation.md)** + +Learn how to run containers that support socket activation. diff --git a/docs/tutorials/socket_activation.md b/docs/tutorials/socket_activation.md new file mode 100644 index 000000000..9b4b02b81 --- /dev/null +++ b/docs/tutorials/socket_activation.md @@ -0,0 +1,212 @@ +# Podman socket activation + +Socket activation conceptually works by having systemd create a socket (e.g. TCP, UDP or Unix +socket). As soon as a client connects to the socket, systemd will start the systemd service that is +configured for the socket. The newly started program inherits the file descriptor of the socket +and can then accept the incoming connection (in other words run the system call `accept()`). +This description corresponds to the default systemd socket configuration +[`Accept=no`](https://www.freedesktop.org/software/systemd/man/systemd.socket.html#Accept=) +that lets the service accept the socket. + +Podman supports two forms of socket activation: + +* Socket activation of the API service +* Socket activation of containers + +## Socket activation of the API service + +The architecture looks like this + +``` mermaid +stateDiagram-v2 + [*] --> systemd: client connects + systemd --> podman: socket inherited via fork/exec +``` + +The file _/usr/lib/systemd/user/podman.socket_ on a Fedora system defines the Podman API socket for +rootless users: + +``` +$ cat /usr/lib/systemd/user/podman.socket +[Unit] +Description=Podman API Socket +Documentation=man:podman-system-service(1) + +[Socket] +ListenStream=%t/podman/podman.sock +SocketMode=0660 + +[Install] +WantedBy=sockets.target +``` + +The socket is configured to be a Unix socket and can be started like this + +``` +$ systemctl --user start podman.socket +$ ls $XDG_RUNTIME_DIR/podman/podman.sock +/run/user/1000/podman/podman.sock +$ +``` +The socket can later be used by for instance __docker-compose__ that needs a Docker-compatible API + +``` +$ export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/podman/podman.sock +$ docker-compose up +``` + +## Socket activation of containers + +Since version 3.4.0 Podman supports socket activation of containers, i.e., passing +a socket-activated socket to the container. Thanks to the fork/exec model of Podman, the socket will be first +inherited by conmon and then by the OCI runtime and finally by the container +as can be seen in the following diagram: + + +``` mermaid +stateDiagram-v2 + [*] --> systemd: client connects + systemd --> podman: socket inherited via fork/exec + state "OCI runtime" as s2 + podman --> conmon: socket inherited via double fork/exec + conmon --> s2: socket inherited via fork/exec + s2 --> container: socket inherited via exec +``` + +This type of socket activation can be used in systemd services that are generated with the command +[`podman generate systemd`](https://docs.podman.io/en/latest/markdown/podman-generate-systemd.1.html). +The container must also support socket activation. Not all software daemons support socket activation +but it's getting more popular. For instance Apache HTTP server, MariaDB, DBUS, PipeWire, Gunicorn, CUPS +all have socket activation support. + +### Example: socket-activated echo server container in a systemd service + +Let's try out [socket-activate-echo](https://github.com/eriksjolund/socket-activate-echo/pkgs/container/socket-activate-echo), a simple echo server container that supports socket activation. + +Create the container + +``` +$ podman create --rm --name echo --network none ghcr.io/eriksjolund/socket-activate-echo +``` + +Generate the systemd service unit + +``` +$ mkdir -p ~/.config/systemd/user +$ podman generate systemd --name --new echo > ~/.config/systemd/user/echo.service +``` + +A socket activated service also requires a systemd socket unit. +Create the file _~/.config/systemd/user/echo.socket_ that defines the +sockets that the container should use + +``` +[Unit] +Description=echo server + +[Socket] +ListenStream=127.0.0.1:3000 +ListenDatagram=127.0.0.1:3000 +ListenStream=[::1]:3000 +ListenDatagram=[::1]:3000 +ListenStream=%h/echo_stream_sock + +# VMADDR_CID_ANY (-1U) = 2^32 -1 = 4294967295 +# See "man vsock" +ListenStream=vsock:4294967295:3000 + +[Install] +WantedBy=default.target +``` + +`%h` is a systemd specifier that expands to the user's home directory. + +After editing the unit files, systemd needs to reload its configuration + +``` +$ systemctl --user daemon-reload +``` + +Start the socket unit + +``` +$ systemctl --user start echo.socket +``` + +Test the echo server with the program __socat__ + +``` +$ echo hello | socat - tcp4:127.0.0.1:3000 +hello +$ echo hello | socat - tcp6:[::1]:3000 +hello +$ echo hello | socat - udp4:127.0.0.1:3000 +hello +$ echo hello | socat - udp6:[::1]:3000 +hello +$ echo hello | socat - unix:$HOME/echo_stream_sock +hello +$ echo hello | socat - VSOCK-CONNECT:1:3000 +hello +``` + +The echo server works as expected. It replies _"hello"_ after receiving the text _"hello"_. + +### Example: socket activate an Apache HTTP server with systemd-socket-activate + +Instead of setting up a systemd service to test out socket activation, an alternative is to use the command-line +tool [__systemd-socket-activate__](https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html#). + +Let's build a container image for the Apache HTTP server that is configured to support socket activation on port 8080. + +Create a new directory _ctr_ and a file _ctr/Containerfile_ with this contents + +``` +FROM docker.io/library/fedora +RUN dnf -y update && dnf install -y httpd && dnf clean all +RUN sed -i "s/Listen 80/Listen 127.0.0.1:8080/g" /etc/httpd/conf/httpd.conf +CMD ["/usr/sbin/httpd", "-DFOREGROUND"] +``` + +Build the container image + +``` +$ podman build -t socket-activate-httpd ctr +``` + +In one shell, start __systemd-socket-activate__. + +``` +$ systemd-socket-activate -l 8080 podman run --rm --network=none localhost/socket-activate-httpd +``` + +The TCP port number 8080 is given as an option to __systemd-socket-activate__. The __--publish__ (__-p__) +option for `podman run` is not used. + +In another shell, fetch a web page from _localhost:8080_ + +``` +$ curl -s localhost:8080 | head -6 +<!doctype html> +<html> + <head> +<meta charset='utf-8'> +<meta name='viewport' content='width=device-width, initial-scale=1'> +<title>Test Page for the HTTP Server on Fedora</title> +$ +``` + +### Disabling the network with _--network=none_ + +If the container only needs to communicate over the socket-activated socket, it's possible to disable +the network by passing __--network=none__ to `podman run`. This improves security because the +container then runs with less privileges. + +### Native network performance over the socket-activated socket + +When using rootless Podman, network traffic is normally passed through slirp4netns. This comes with +a performance penalty. Fortunately, communication over the socket-activated socket does not pass through +slirp4netns so it has the same performance characteristics as the normal network on the host. +Note, there is a delay when the first connection is made because the container needs to +start up. To minimize this delay, consider passing __--pull=never__ to `podman run` and instead +pull the container image beforehand. @@ -4,7 +4,7 @@ go 1.16 require ( github.com/BurntSushi/toml v1.1.0 - github.com/blang/semver v3.5.1+incompatible + github.com/blang/semver/v4 v4.0.0 github.com/buger/goterm v1.0.4 github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0 github.com/checkpoint-restore/go-criu/v5 v5.3.0 @@ -171,6 +171,8 @@ github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnweb github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blizzy78/varnamelen v0.3.0/go.mod h1:hbwRdBvoBqxk34XyQ6HA0UH3G0/1TKuv5AC4eaBT0Ec= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= diff --git a/hack/swagger-check b/hack/swagger-check index b4481f5bb..1e5b95c3a 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/play/kube") { - $action = "KubeDown" + elsif ($action eq "delete" && $endpoint eq "/libpod/kube/play") { + $action = "PlayDown" } # Grrrrrr, this one is annoying: some operations get an extra 'All' elsif ($action =~ /^(delete|get|stats)$/ && $endpoint !~ /\{/) { diff --git a/hack/xref-helpmsgs-manpages b/hack/xref-helpmsgs-manpages index 7c07a4f01..de9ef8630 100755 --- a/hack/xref-helpmsgs-manpages +++ b/hack/xref-helpmsgs-manpages @@ -350,7 +350,7 @@ sub podman_man { # This is a while-loop because there may be multiple long # option names, e.g. --net/--network my $is_first = 1; - while ($line =~ s/^\*\*(--[a-z0-9-]+)\*\*(=\*[a-zA-Z0-9-]+\*)?(,\s+)?//g) { + while ($line =~ s/^\*\*(--[a-z0-9-]+)\*\*(,\s+)?//g) { my $flag = $1; $man{$flag} = 1; if ($flag lt $previous_flag && $is_first) { @@ -365,11 +365,50 @@ sub podman_man { $is_first = 0; } # Short form - if ($line =~ s/^\*\*(-[a-zA-Z0-9])\*\*(=\*[a-zA-Z0-9-]+\*)?//g) { - $man{$1} = 1; + if ($line =~ s/^\*\*(-[a-zA-Z0-9])\*\*//) { + my $flag = $1; + $man{$flag} = 1; # Keep track of them, in case we see 'Not implemented' below - push @most_recent_flags, $1; + push @most_recent_flags, $flag; + } + + # Options with no '=whatever' + next if !$line; + + # Anything remaining *must* be of the form '=<possibilities>' + if ($line !~ /^=/) { + warn "$ME: $subpath:$.: could not parse '$line' in option description\n"; + ++$Errs; + } + + # For some years it was traditional, albeit wrong, to write + # **--foo**=*bar*, **-f** + # The correct way is to add =*bar* at the end. + if ($line =~ s/,\s\*\*(-[a-zA-Z])\*\*//) { + $man{$1} = 1; + warn "$ME: $subpath:$.: please rewrite as ', **$1**$line'\n"; + ++$Errs; + } + + # List of possibilities ('=*a* | *b*') must be space-separated + if ($line =~ /\|/) { + if ($line =~ /[^\s]\|[^\s]/) { + # Sigh, except for this one special case + if ($line !~ /SOURCE-VOLUME.*HOST-DIR.*CONTAINER-DIR/) { + warn "$ME: $subpath:$.: values must be space-separated: '$line'\n"; + ++$Errs; + } + } + my $copy = $line; + if ($copy =~ s/\**true\**//) { + if ($copy =~ s/\**false\**//) { + if ($copy !~ /[a-z]/) { + warn "$ME: $subpath:$.: Do not enumerate true/false for boolean-only options\n"; + ++$Errs; + } + } + } } } } diff --git a/pkg/api/handlers/compat/containers_stop.go b/pkg/api/handlers/compat/containers_stop.go index 33bb3a679..c9a27dd83 100644 --- a/pkg/api/handlers/compat/containers_stop.go +++ b/pkg/api/handlers/compat/containers_stop.go @@ -33,9 +33,7 @@ func StopContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } - name := utils.GetName(r) - options := entities.StopOptions{ Ignore: query.Ignore, } diff --git a/pkg/api/handlers/libpod/kube.go b/pkg/api/handlers/libpod/kube.go new file mode 100644 index 000000000..6cad58795 --- /dev/null +++ b/pkg/api/handlers/libpod/kube.go @@ -0,0 +1,123 @@ +package libpod + +import ( + "fmt" + "net" + "net/http" + + "github.com/containers/image/v5/types" + "github.com/containers/podman/v4/libpod" + "github.com/containers/podman/v4/pkg/api/handlers/utils" + api "github.com/containers/podman/v4/pkg/api/types" + "github.com/containers/podman/v4/pkg/auth" + "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/domain/infra/abi" + "github.com/gorilla/schema" +) + +func KubePlay(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) + query := struct { + Annotations map[string]string `schema:"annotations"` + Network []string `schema:"network"` + TLSVerify bool `schema:"tlsVerify"` + LogDriver string `schema:"logDriver"` + LogOptions []string `schema:"logOptions"` + Start bool `schema:"start"` + StaticIPs []string `schema:"staticIPs"` + StaticMACs []string `schema:"staticMACs"` + NoHosts bool `schema:"noHosts"` + }{ + TLSVerify: true, + Start: true, + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) + return + } + + staticIPs := make([]net.IP, 0, len(query.StaticIPs)) + for _, ipString := range query.StaticIPs { + ip := net.ParseIP(ipString) + if ip == nil { + utils.Error(w, http.StatusBadRequest, fmt.Errorf("invalid IP address %s", ipString)) + return + } + staticIPs = append(staticIPs, ip) + } + + staticMACs := make([]net.HardwareAddr, 0, len(query.StaticMACs)) + for _, macString := range query.StaticMACs { + mac, err := net.ParseMAC(macString) + if err != nil { + utils.Error(w, http.StatusBadRequest, err) + return + } + staticMACs = append(staticMACs, mac) + } + + authConf, authfile, err := auth.GetCredentials(r) + if err != nil { + utils.Error(w, http.StatusBadRequest, err) + return + } + defer auth.RemoveAuthfile(authfile) + var username, password string + if authConf != nil { + username = authConf.Username + password = authConf.Password + } + + logDriver := query.LogDriver + if logDriver == "" { + config, err := runtime.GetConfig() + if err != nil { + utils.Error(w, http.StatusInternalServerError, err) + return + } + logDriver = config.Containers.LogDriver + } + + containerEngine := abi.ContainerEngine{Libpod: runtime} + options := entities.PlayKubeOptions{ + Annotations: query.Annotations, + Authfile: authfile, + Username: username, + Password: password, + Networks: query.Network, + NoHosts: query.NoHosts, + Quiet: true, + LogDriver: logDriver, + LogOptions: query.LogOptions, + StaticIPs: staticIPs, + StaticMACs: staticMACs, + } + if _, found := r.URL.Query()["tlsVerify"]; found { + options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + if _, found := r.URL.Query()["start"]; found { + options.Start = types.NewOptionalBool(query.Start) + } + report, err := containerEngine.PlayKube(r.Context(), r.Body, options) + _ = r.Body.Close() + if err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error playing YAML file: %w", err)) + return + } + utils.WriteResponse(w, http.StatusOK, report) +} + +func KubePlayDown(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + containerEngine := abi.ContainerEngine{Libpod: runtime} + options := new(entities.PlayKubeDownOptions) + report, err := containerEngine.PlayKubeDown(r.Context(), r.Body, *options) + _ = r.Body.Close() + if err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error tearing down YAML file: %w", err)) + return + } + utils.WriteResponse(w, http.StatusOK, report) +} diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go index f8ce52a72..74830badb 100644 --- a/pkg/api/handlers/libpod/play.go +++ b/pkg/api/handlers/libpod/play.go @@ -1,123 +1,13 @@ package libpod import ( - "fmt" - "net" "net/http" - - "github.com/containers/image/v5/types" - "github.com/containers/podman/v4/libpod" - "github.com/containers/podman/v4/pkg/api/handlers/utils" - api "github.com/containers/podman/v4/pkg/api/types" - "github.com/containers/podman/v4/pkg/auth" - "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/containers/podman/v4/pkg/domain/infra/abi" - "github.com/gorilla/schema" ) func PlayKube(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) - query := struct { - Annotations map[string]string `schema:"annotations"` - Network []string `schema:"network"` - TLSVerify bool `schema:"tlsVerify"` - LogDriver string `schema:"logDriver"` - LogOptions []string `schema:"logOptions"` - Start bool `schema:"start"` - StaticIPs []string `schema:"staticIPs"` - StaticMACs []string `schema:"staticMACs"` - NoHosts bool `schema:"noHosts"` - }{ - TLSVerify: true, - Start: true, - } - - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) - return - } - - staticIPs := make([]net.IP, 0, len(query.StaticIPs)) - for _, ipString := range query.StaticIPs { - ip := net.ParseIP(ipString) - if ip == nil { - utils.Error(w, http.StatusBadRequest, fmt.Errorf("invalid IP address %s", ipString)) - return - } - staticIPs = append(staticIPs, ip) - } - - staticMACs := make([]net.HardwareAddr, 0, len(query.StaticMACs)) - for _, macString := range query.StaticMACs { - mac, err := net.ParseMAC(macString) - if err != nil { - utils.Error(w, http.StatusBadRequest, err) - return - } - staticMACs = append(staticMACs, mac) - } - - authConf, authfile, err := auth.GetCredentials(r) - if err != nil { - utils.Error(w, http.StatusBadRequest, err) - return - } - defer auth.RemoveAuthfile(authfile) - var username, password string - if authConf != nil { - username = authConf.Username - password = authConf.Password - } - - logDriver := query.LogDriver - if logDriver == "" { - config, err := runtime.GetConfig() - if err != nil { - utils.Error(w, http.StatusInternalServerError, err) - return - } - logDriver = config.Containers.LogDriver - } - - containerEngine := abi.ContainerEngine{Libpod: runtime} - options := entities.PlayKubeOptions{ - Annotations: query.Annotations, - Authfile: authfile, - Username: username, - Password: password, - Networks: query.Network, - NoHosts: query.NoHosts, - Quiet: true, - LogDriver: logDriver, - LogOptions: query.LogOptions, - StaticIPs: staticIPs, - StaticMACs: staticMACs, - } - if _, found := r.URL.Query()["tlsVerify"]; found { - options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) - } - if _, found := r.URL.Query()["start"]; found { - options.Start = types.NewOptionalBool(query.Start) - } - report, err := containerEngine.PlayKube(r.Context(), r.Body, options) - _ = r.Body.Close() - if err != nil { - utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error playing YAML file: %w", err)) - return - } - utils.WriteResponse(w, http.StatusOK, report) + KubePlay(w, r) } func PlayKubeDown(w http.ResponseWriter, r *http.Request) { - runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - containerEngine := abi.ContainerEngine{Libpod: runtime} - options := new(entities.PlayKubeDownOptions) - report, err := containerEngine.PlayKubeDown(r.Context(), r.Body, *options) - _ = r.Body.Close() - if err != nil { - utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error tearing down YAML file: %w", err)) - return - } - utils.WriteResponse(w, http.StatusOK, report) + KubePlayDown(w, r) } diff --git a/pkg/api/handlers/swagger/responses.go b/pkg/api/handlers/swagger/responses.go index 93a508b39..5731f8edd 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 playKubeResponseLibpod struct { +type kubePlayResponseLibpod struct { // in:body Body entities.PlayKubeReport } diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go index 9562ebbbc..f2f8ab1dc 100644 --- a/pkg/api/handlers/utils/handler.go +++ b/pkg/api/handlers/utils/handler.go @@ -10,7 +10,7 @@ import ( "strings" "unsafe" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/containers/podman/v4/version" "github.com/gorilla/mux" jsoniter "github.com/json-iterator/go" diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_kube.go index 35da80ccc..6ae9e8123 100644 --- a/pkg/api/server/register_play.go +++ b/pkg/api/server/register_kube.go @@ -7,8 +7,8 @@ import ( "github.com/gorilla/mux" ) -func (s *APIServer) registerPlayHandlers(r *mux.Router) error { - // swagger:operation POST /libpod/play/kube libpod PlayKubeLibpod +func (s *APIServer) registerKubeHandlers(r *mux.Router) error { + // swagger:operation POST /libpod/kube/play libpod KubePlayLibpod // --- // tags: // - containers @@ -57,24 +57,26 @@ func (s *APIServer) registerPlayHandlers(r *mux.Router) error { // - application/json // responses: // 200: - // $ref: "#/responses/playKubeResponseLibpod" + // $ref: "#/responses/kubePlayResponseLibpod" // 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/play/kube libpod PlayKubeDownLibpod + // swagger:operation DELETE /libpod/kube/play libpod KubePlayDownLibpod // --- // tags: // - containers // - pods - // summary: Remove pods from play kube + // summary: Remove pods from kube play // description: Tears down pods defined in a YAML file // produces: // - application/json // responses: // 200: - // $ref: "#/responses/playKubeResponseLibpod" + // $ref: "#/responses/kubePlayResponseLibpod" // 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) return nil } diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 5482a8ec2..a6d8b5e4c 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/bindings/connection.go b/pkg/bindings/connection.go index 7dda955a2..b994a5857 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -13,7 +13,7 @@ import ( "strings" "time" - "github.com/blang/semver" + "github.com/blang/semver/v4" "github.com/containers/podman/v4/pkg/terminal" "github.com/containers/podman/v4/version" "github.com/sirupsen/logrus" diff --git a/pkg/bindings/kube/kube.go b/pkg/bindings/kube/kube.go new file mode 100644 index 000000000..b9cc0efa7 --- /dev/null +++ b/pkg/bindings/kube/kube.go @@ -0,0 +1,96 @@ +package kube + +import ( + "context" + "io" + "net/http" + "os" + "strconv" + + "github.com/containers/image/v5/types" + "github.com/containers/podman/v4/pkg/auth" + "github.com/containers/podman/v4/pkg/bindings" + "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/sirupsen/logrus" +) + +func Play(ctx context.Context, path string, options *PlayOptions) (*entities.KubePlayReport, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + return PlayWithBody(ctx, f, options) +} + +func PlayWithBody(ctx context.Context, body io.Reader, options *PlayOptions) (*entities.KubePlayReport, error) { + var report entities.KubePlayReport + if options == nil { + options = new(PlayOptions) + } + + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + params, err := options.ToParams() + if err != nil { + return nil, err + } + if options.SkipTLSVerify != nil { + params.Set("tlsVerify", strconv.FormatBool(options.GetSkipTLSVerify())) + } + if options.Start != nil { + params.Set("start", strconv.FormatBool(options.GetStart())) + } + + header, err := auth.MakeXRegistryAuthHeader(&types.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(ctx, body, http.MethodPost, "/kube/play", params, header) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if err := response.Process(&report); err != nil { + return nil, err + } + + return &report, nil +} + +func Down(ctx context.Context, path string) (*entities.KubePlayReport, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer func() { + if err := f.Close(); err != nil { + logrus.Warn(err) + } + }() + + return DownWithBody(ctx, f) +} + +func DownWithBody(ctx context.Context, body io.Reader) (*entities.KubePlayReport, error) { + var report entities.KubePlayReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + response, err := conn.DoRequest(ctx, body, http.MethodDelete, "/kube/play", nil, nil) + if err != nil { + return nil, err + } + if err := response.Process(&report); err != nil { + return nil, err + } + return &report, nil +} diff --git a/pkg/bindings/play/types.go b/pkg/bindings/kube/types.go index 5aaa87b8c..783d1912a 100644 --- a/pkg/bindings/play/types.go +++ b/pkg/bindings/kube/types.go @@ -1,12 +1,12 @@ -package play +package kube import ( "net" ) -//go:generate go run ../generator/generator.go KubeOptions -// KubeOptions are optional options for replaying kube YAML files -type KubeOptions struct { +//go:generate go run ../generator/generator.go PlayOptions +// PlayOptions are optional options for replaying kube YAML files +type PlayOptions struct { // Annotations - Annotations to add to Pods Annotations map[string]string // Authfile - path to an authentication file. diff --git a/pkg/bindings/play/types_kube_options.go b/pkg/bindings/kube/types_play_options.go index 54c9a8e74..cdc2e9dd8 100644 --- a/pkg/bindings/play/types_kube_options.go +++ b/pkg/bindings/kube/types_play_options.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -package play +package kube import ( "net" @@ -9,23 +9,23 @@ import ( ) // Changed returns true if named field has been set -func (o *KubeOptions) Changed(fieldName string) bool { +func (o *PlayOptions) Changed(fieldName string) bool { return util.Changed(o, fieldName) } // ToParams formats struct fields to be passed to API service -func (o *KubeOptions) ToParams() (url.Values, error) { +func (o *PlayOptions) ToParams() (url.Values, error) { return util.ToParams(o) } // WithAnnotations set field Annotations to given value -func (o *KubeOptions) WithAnnotations(value map[string]string) *KubeOptions { +func (o *PlayOptions) WithAnnotations(value map[string]string) *PlayOptions { o.Annotations = value return o } // GetAnnotations returns value of field Annotations -func (o *KubeOptions) GetAnnotations() map[string]string { +func (o *PlayOptions) GetAnnotations() map[string]string { if o.Annotations == nil { var z map[string]string return z @@ -34,13 +34,13 @@ func (o *KubeOptions) GetAnnotations() map[string]string { } // WithAuthfile set field Authfile to given value -func (o *KubeOptions) WithAuthfile(value string) *KubeOptions { +func (o *PlayOptions) WithAuthfile(value string) *PlayOptions { o.Authfile = &value return o } // GetAuthfile returns value of field Authfile -func (o *KubeOptions) GetAuthfile() string { +func (o *PlayOptions) GetAuthfile() string { if o.Authfile == nil { var z string return z @@ -49,13 +49,13 @@ func (o *KubeOptions) GetAuthfile() string { } // WithCertDir set field CertDir to given value -func (o *KubeOptions) WithCertDir(value string) *KubeOptions { +func (o *PlayOptions) WithCertDir(value string) *PlayOptions { o.CertDir = &value return o } // GetCertDir returns value of field CertDir -func (o *KubeOptions) GetCertDir() string { +func (o *PlayOptions) GetCertDir() string { if o.CertDir == nil { var z string return z @@ -64,13 +64,13 @@ func (o *KubeOptions) GetCertDir() string { } // WithUsername set field Username to given value -func (o *KubeOptions) WithUsername(value string) *KubeOptions { +func (o *PlayOptions) WithUsername(value string) *PlayOptions { o.Username = &value return o } // GetUsername returns value of field Username -func (o *KubeOptions) GetUsername() string { +func (o *PlayOptions) GetUsername() string { if o.Username == nil { var z string return z @@ -79,13 +79,13 @@ func (o *KubeOptions) GetUsername() string { } // WithPassword set field Password to given value -func (o *KubeOptions) WithPassword(value string) *KubeOptions { +func (o *PlayOptions) WithPassword(value string) *PlayOptions { o.Password = &value return o } // GetPassword returns value of field Password -func (o *KubeOptions) GetPassword() string { +func (o *PlayOptions) GetPassword() string { if o.Password == nil { var z string return z @@ -94,13 +94,13 @@ func (o *KubeOptions) GetPassword() string { } // WithNetwork set field Network to given value -func (o *KubeOptions) WithNetwork(value []string) *KubeOptions { +func (o *PlayOptions) WithNetwork(value []string) *PlayOptions { o.Network = &value return o } // GetNetwork returns value of field Network -func (o *KubeOptions) GetNetwork() []string { +func (o *PlayOptions) GetNetwork() []string { if o.Network == nil { var z []string return z @@ -109,13 +109,13 @@ func (o *KubeOptions) GetNetwork() []string { } // WithNoHosts set field NoHosts to given value -func (o *KubeOptions) WithNoHosts(value bool) *KubeOptions { +func (o *PlayOptions) WithNoHosts(value bool) *PlayOptions { o.NoHosts = &value return o } // GetNoHosts returns value of field NoHosts -func (o *KubeOptions) GetNoHosts() bool { +func (o *PlayOptions) GetNoHosts() bool { if o.NoHosts == nil { var z bool return z @@ -124,13 +124,13 @@ func (o *KubeOptions) GetNoHosts() bool { } // WithQuiet set field Quiet to given value -func (o *KubeOptions) WithQuiet(value bool) *KubeOptions { +func (o *PlayOptions) WithQuiet(value bool) *PlayOptions { o.Quiet = &value return o } // GetQuiet returns value of field Quiet -func (o *KubeOptions) GetQuiet() bool { +func (o *PlayOptions) GetQuiet() bool { if o.Quiet == nil { var z bool return z @@ -139,13 +139,13 @@ func (o *KubeOptions) GetQuiet() bool { } // WithSignaturePolicy set field SignaturePolicy to given value -func (o *KubeOptions) WithSignaturePolicy(value string) *KubeOptions { +func (o *PlayOptions) WithSignaturePolicy(value string) *PlayOptions { o.SignaturePolicy = &value return o } // GetSignaturePolicy returns value of field SignaturePolicy -func (o *KubeOptions) GetSignaturePolicy() string { +func (o *PlayOptions) GetSignaturePolicy() string { if o.SignaturePolicy == nil { var z string return z @@ -154,13 +154,13 @@ func (o *KubeOptions) GetSignaturePolicy() string { } // WithSkipTLSVerify set field SkipTLSVerify to given value -func (o *KubeOptions) WithSkipTLSVerify(value bool) *KubeOptions { +func (o *PlayOptions) WithSkipTLSVerify(value bool) *PlayOptions { o.SkipTLSVerify = &value return o } // GetSkipTLSVerify returns value of field SkipTLSVerify -func (o *KubeOptions) GetSkipTLSVerify() bool { +func (o *PlayOptions) GetSkipTLSVerify() bool { if o.SkipTLSVerify == nil { var z bool return z @@ -169,13 +169,13 @@ func (o *KubeOptions) GetSkipTLSVerify() bool { } // WithSeccompProfileRoot set field SeccompProfileRoot to given value -func (o *KubeOptions) WithSeccompProfileRoot(value string) *KubeOptions { +func (o *PlayOptions) WithSeccompProfileRoot(value string) *PlayOptions { o.SeccompProfileRoot = &value return o } // GetSeccompProfileRoot returns value of field SeccompProfileRoot -func (o *KubeOptions) GetSeccompProfileRoot() string { +func (o *PlayOptions) GetSeccompProfileRoot() string { if o.SeccompProfileRoot == nil { var z string return z @@ -184,13 +184,13 @@ func (o *KubeOptions) GetSeccompProfileRoot() string { } // WithStaticIPs set field StaticIPs to given value -func (o *KubeOptions) WithStaticIPs(value []net.IP) *KubeOptions { +func (o *PlayOptions) WithStaticIPs(value []net.IP) *PlayOptions { o.StaticIPs = &value return o } // GetStaticIPs returns value of field StaticIPs -func (o *KubeOptions) GetStaticIPs() []net.IP { +func (o *PlayOptions) GetStaticIPs() []net.IP { if o.StaticIPs == nil { var z []net.IP return z @@ -199,13 +199,13 @@ func (o *KubeOptions) GetStaticIPs() []net.IP { } // WithStaticMACs set field StaticMACs to given value -func (o *KubeOptions) WithStaticMACs(value []net.HardwareAddr) *KubeOptions { +func (o *PlayOptions) WithStaticMACs(value []net.HardwareAddr) *PlayOptions { o.StaticMACs = &value return o } // GetStaticMACs returns value of field StaticMACs -func (o *KubeOptions) GetStaticMACs() []net.HardwareAddr { +func (o *PlayOptions) GetStaticMACs() []net.HardwareAddr { if o.StaticMACs == nil { var z []net.HardwareAddr return z @@ -214,13 +214,13 @@ func (o *KubeOptions) GetStaticMACs() []net.HardwareAddr { } // WithConfigMaps set field ConfigMaps to given value -func (o *KubeOptions) WithConfigMaps(value []string) *KubeOptions { +func (o *PlayOptions) WithConfigMaps(value []string) *PlayOptions { o.ConfigMaps = &value return o } // GetConfigMaps returns value of field ConfigMaps -func (o *KubeOptions) GetConfigMaps() []string { +func (o *PlayOptions) GetConfigMaps() []string { if o.ConfigMaps == nil { var z []string return z @@ -229,13 +229,13 @@ func (o *KubeOptions) GetConfigMaps() []string { } // WithLogDriver set field LogDriver to given value -func (o *KubeOptions) WithLogDriver(value string) *KubeOptions { +func (o *PlayOptions) WithLogDriver(value string) *PlayOptions { o.LogDriver = &value return o } // GetLogDriver returns value of field LogDriver -func (o *KubeOptions) GetLogDriver() string { +func (o *PlayOptions) GetLogDriver() string { if o.LogDriver == nil { var z string return z @@ -244,13 +244,13 @@ func (o *KubeOptions) GetLogDriver() string { } // WithLogOptions set field LogOptions to given value -func (o *KubeOptions) WithLogOptions(value []string) *KubeOptions { +func (o *PlayOptions) WithLogOptions(value []string) *PlayOptions { o.LogOptions = &value return o } // GetLogOptions returns value of field LogOptions -func (o *KubeOptions) GetLogOptions() []string { +func (o *PlayOptions) GetLogOptions() []string { if o.LogOptions == nil { var z []string return z @@ -259,13 +259,13 @@ func (o *KubeOptions) GetLogOptions() []string { } // WithStart set field Start to given value -func (o *KubeOptions) WithStart(value bool) *KubeOptions { +func (o *PlayOptions) WithStart(value bool) *PlayOptions { o.Start = &value return o } // GetStart returns value of field Start -func (o *KubeOptions) GetStart() bool { +func (o *PlayOptions) GetStart() bool { if o.Start == nil { var z bool return z @@ -274,13 +274,13 @@ func (o *KubeOptions) GetStart() bool { } // WithUserns set field Userns to given value -func (o *KubeOptions) WithUserns(value string) *KubeOptions { +func (o *PlayOptions) WithUserns(value string) *PlayOptions { o.Userns = &value return o } // GetUserns returns value of field Userns -func (o *KubeOptions) GetUserns() string { +func (o *PlayOptions) GetUserns() string { if o.Userns == nil { var z string return z diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go index 0261b0250..d5d649135 100644 --- a/pkg/bindings/play/play.go +++ b/pkg/bindings/play/play.go @@ -3,95 +3,25 @@ package play import ( "context" "io" - "net/http" - "os" - "strconv" - "github.com/containers/image/v5/types" - "github.com/containers/podman/v4/pkg/auth" - "github.com/containers/podman/v4/pkg/bindings" + "github.com/containers/podman/v4/pkg/bindings/kube" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/sirupsen/logrus" ) -func Kube(ctx context.Context, path string, options *KubeOptions) (*entities.PlayKubeReport, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() +type KubeOptions = kube.PlayOptions - return KubeWithBody(ctx, f, options) +func Kube(ctx context.Context, path string, options *KubeOptions) (*entities.PlayKubeReport, error) { + return kube.Play(ctx, path, options) } func KubeWithBody(ctx context.Context, body io.Reader, options *KubeOptions) (*entities.PlayKubeReport, error) { - var report entities.PlayKubeReport - if options == nil { - options = new(KubeOptions) - } - - conn, err := bindings.GetClient(ctx) - if err != nil { - return nil, err - } - - params, err := options.ToParams() - if err != nil { - return nil, err - } - if options.SkipTLSVerify != nil { - params.Set("tlsVerify", strconv.FormatBool(options.GetSkipTLSVerify())) - } - if options.Start != nil { - params.Set("start", strconv.FormatBool(options.GetStart())) - } - - header, err := auth.MakeXRegistryAuthHeader(&types.SystemContext{AuthFilePath: options.GetAuthfile()}, options.GetUsername(), options.GetPassword()) - if err != nil { - return nil, err - } - - response, err := conn.DoRequest(ctx, body, http.MethodPost, "/play/kube", params, header) - if err != nil { - return nil, err - } - defer response.Body.Close() - - if err := response.Process(&report); err != nil { - return nil, err - } - - return &report, nil + return kube.PlayWithBody(ctx, body, options) } -func KubeDown(ctx context.Context, path string) (*entities.PlayKubeReport, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer func() { - if err := f.Close(); err != nil { - logrus.Warn(err) - } - }() - - return KubeDownWithBody(ctx, f) +func Down(ctx context.Context, path string) (*entities.PlayKubeReport, error) { + return kube.Down(ctx, path) } -func KubeDownWithBody(ctx context.Context, body io.Reader) (*entities.PlayKubeReport, error) { - var report entities.PlayKubeReport - conn, err := bindings.GetClient(ctx) - if err != nil { - return nil, err - } - - response, err := conn.DoRequest(ctx, body, http.MethodDelete, "/play/kube", nil, nil) - if err != nil { - return nil, err - } - if err := response.Process(&report); err != nil { - return nil, err - } - - return &report, nil +func DownWithBody(ctx context.Context, body io.Reader) (*entities.PlayKubeReport, error) { + return kube.DownWithBody(ctx, body) } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 17408f12f..934a7cbdc 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -80,6 +80,7 @@ type PauseUnpauseReport struct { } type StopOptions struct { + Filters map[string][]string All bool Ignore bool Latest bool diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index f1ba21650..35a5d8a4a 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -90,6 +90,8 @@ type PlayKubeReport struct { PlayKubeTeardown } +type KubePlayReport = PlayKubeReport + // PlayKubeDownOptions are options for tearing down pods type PlayKubeDownOptions struct{} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 23a591604..04eb85504 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -37,12 +37,29 @@ import ( ) // getContainersAndInputByContext gets containers whether all, latest, or a slice of names/ids -// is specified. It also returns a list of the corresponding input name used to look up each container. -func getContainersAndInputByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, rawInput []string, err error) { +// 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 ctrs = []*libpod.Container{} + filterFuncs := make([]libpod.ContainerFilter, 0, len(filters)) switch { + case len(filters) > 0: + for k, v := range filters { + generatedFunc, err := dfilters.GenerateContainerFilterFuncs(k, v, runtime) + if err != nil { + return nil, nil, err + } + filterFuncs = append(filterFuncs, generatedFunc) + } + ctrs, err = runtime.GetContainers(filterFuncs...) + if err != nil { + return nil, nil, err + } + rawInput = []string{} + for _, candidate := range ctrs { + rawInput = append(rawInput, candidate.ID()) + } case all: ctrs, err = runtime.GetAllContainers() case latest: @@ -66,13 +83,13 @@ func getContainersAndInputByContext(all, latest bool, names []string, runtime *l } } } - return + return ctrs, rawInput, err } // getContainersByContext gets containers whether all, latest, or a slice of names/ids // is specified. func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) { - ctrs, _, err = getContainersAndInputByContext(all, latest, names, runtime) + ctrs, _, err = getContainersAndInputByContext(all, latest, names, nil, runtime) return } @@ -150,7 +167,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st } func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, options entities.StopOptions) ([]*entities.StopReport, error) { names := namesOrIds - ctrs, rawInputs, err := getContainersAndInputByContext(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 } @@ -228,7 +245,7 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin if err != nil { return nil, err } - ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, namesOrIds, nil, ic.Libpod) if err != nil { return nil, err } @@ -874,7 +891,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } } } - ctrs, rawInputs, err := getContainersAndInputByContext(all, options.Latest, containersNamesOrIds, ic.Libpod) + ctrs, rawInputs, err := getContainersAndInputByContext(all, options.Latest, containersNamesOrIds, options.Filters, ic.Libpod) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 0faae01c8..3389abd88 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -67,6 +67,22 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { } func (ic *ContainerEngine) SetupRootless(_ context.Context, noMoveProcess bool) error { + runsUnderSystemd := utils.RunsOnSystemd() + if !runsUnderSystemd { + isPid1 := os.Getpid() == 1 + if _, found := os.LookupEnv("container"); isPid1 || found { + if err := utils.MaybeMoveToSubCgroup(); err != nil { + // it is a best effort operation, so just print the + // error for debugging purposes. + logrus.Debugf("Could not move to subcgroup: %v", err) + } + } + } + + if !rootless.IsRootless() { + return nil + } + // do it only after podman has already re-execed and running with uid==0. hasCapSysAdmin, err := unshare.HasCapSysAdmin() if err != nil { @@ -82,7 +98,6 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, noMoveProcess bool) if err != nil { return err } - runsUnderSystemd := utils.RunsOnSystemd() unitName := fmt.Sprintf("podman-%d.scope", os.Getpid()) if runsUnderSystemd || conf.Engine.CgroupManager == config.SystemdCgroupsManager { if err := utils.RunUnderSystemdScope(os.Getpid(), "user.slice", unitName); err != nil { diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 5568ccde8..fcabff7c4 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -91,8 +91,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st } func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, opts entities.StopOptions) ([]*entities.StopReport, error) { - reports := []*entities.StopReport{} - ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, opts.Ignore, namesOrIds) + ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, opts.Ignore, namesOrIds, opts.Filters) if err != nil { return nil, err } @@ -104,6 +103,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin if to := opts.Timeout; to != nil { options.WithTimeout(*to) } + reports := []*entities.StopReport{} for _, c := range ctrs { report := entities.StopReport{ Id: c.ID, @@ -134,7 +134,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin } func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []string, opts entities.KillOptions) ([]*entities.KillReport, error) { - ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, false, namesOrIds) + ctrs, rawInputs, err := getContainersAndInputByContext(ic.ClientCtx, opts.All, false, namesOrIds, nil) if err != nil { return nil, err } diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index 24b2b619d..9ff1641f0 100644 --- a/pkg/domain/infra/tunnel/helpers.go +++ b/pkg/domain/infra/tunnel/helpers.go @@ -15,25 +15,29 @@ 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) { - ctrs, _, err := getContainersAndInputByContext(contextWithConnection, all, ignore, namesOrIDs) + ctrs, _, err := getContainersAndInputByContext(contextWithConnection, all, ignore, namesOrIDs, nil) return ctrs, err } -func getContainersAndInputByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string) ([]entities.ListContainer, []string, error) { +func getContainersAndInputByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string, filters map[string][]string) ([]entities.ListContainer, []string, error) { if all && len(namesOrIDs) > 0 { return nil, nil, errors.New("cannot look up containers and all") } - options := new(containers.ListOptions).WithAll(true).WithSync(true) + options := new(containers.ListOptions).WithAll(true).WithSync(true).WithFilters(filters) allContainers, err := containers.List(contextWithConnection, options) if err != nil { return nil, nil, err } rawInputs := []string{} - if all { + switch { + case len(filters) > 0: + for i := range allContainers { + namesOrIDs = append(namesOrIDs, allContainers[i].ID) + } + case all: for i := range allContainers { rawInputs = append(rawInputs, allContainers[i].ID) } - return allContainers, rawInputs, err } diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go index d731a1d6c..ee9195681 100644 --- a/pkg/domain/infra/tunnel/play.go +++ b/pkg/domain/infra/tunnel/play.go @@ -5,12 +5,13 @@ import ( "io" "github.com/containers/image/v5/types" + "github.com/containers/podman/v4/pkg/bindings/kube" "github.com/containers/podman/v4/pkg/bindings/play" "github.com/containers/podman/v4/pkg/domain/entities" ) func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, opts entities.PlayKubeOptions) (*entities.PlayKubeReport, error) { - options := new(play.KubeOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password) + options := new(kube.PlayOptions).WithAuthfile(opts.Authfile).WithUsername(opts.Username).WithPassword(opts.Password) options.WithCertDir(opts.CertDir).WithQuiet(opts.Quiet).WithSignaturePolicy(opts.SignaturePolicy).WithConfigMaps(opts.ConfigMaps) options.WithLogDriver(opts.LogDriver).WithNetwork(opts.Networks).WithSeccompProfileRoot(opts.SeccompProfileRoot) options.WithStaticIPs(opts.StaticIPs).WithStaticMACs(opts.StaticMACs) @@ -31,5 +32,5 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, opts en } func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, body io.Reader, _ entities.PlayKubeDownOptions) (*entities.PlayKubeReport, error) { - return play.KubeDownWithBody(ic.ClientCtx, body) + return play.DownWithBody(ic.ClientCtx, body) } diff --git a/pkg/machine/config.go b/pkg/machine/config.go index 29cd7bc00..253601dad 100644 --- a/pkg/machine/config.go +++ b/pkg/machine/config.go @@ -73,6 +73,7 @@ type Download struct { Arch string Artifact string CompressionType string + CacheDir string Format string ImageName string LocalPath string @@ -139,6 +140,7 @@ type VM interface { type DistributionDownload interface { HasUsableCache() (bool, error) Get() *Download + CleanCache() error } type InspectInfo struct { ConfigPath VMFile @@ -172,6 +174,19 @@ func (rc RemoteConnectionType) MakeSSHURL(host, path, port, userName string) url return uri } +// GetCacheDir returns the dir where VM images are downladed into when pulled +func GetCacheDir(vmType string) (string, error) { + dataDir, err := GetDataDir(vmType) + if err != nil { + return "", err + } + cacheDir := filepath.Join(dataDir, "cache") + if _, err := os.Stat(cacheDir); !errors.Is(err, os.ErrNotExist) { + return cacheDir, nil + } + return cacheDir, os.MkdirAll(cacheDir, 0755) +} + // GetDataDir returns the filepath where vm images should // live for podman-machine. func GetDataDir(vmType string) (string, error) { @@ -180,7 +195,7 @@ func GetDataDir(vmType string) (string, error) { return "", err } dataDir := filepath.Join(dataDirPrefix, vmType) - if _, err := os.Stat(dataDir); !os.IsNotExist(err) { + if _, err := os.Stat(dataDir); !errors.Is(err, os.ErrNotExist) { return dataDir, nil } mkdirErr := os.MkdirAll(dataDir, 0755) @@ -205,7 +220,7 @@ func GetConfDir(vmType string) (string, error) { return "", err } confDir := filepath.Join(confDirPrefix, vmType) - if _, err := os.Stat(confDir); !os.IsNotExist(err) { + if _, err := os.Stat(confDir); !errors.Is(err, os.ErrNotExist) { return confDir, nil } mkdirErr := os.MkdirAll(confDir, 0755) diff --git a/pkg/machine/e2e/list_test.go b/pkg/machine/e2e/list_test.go index fb855c61e..8b7443d47 100644 --- a/pkg/machine/e2e/list_test.go +++ b/pkg/machine/e2e/list_test.go @@ -135,7 +135,7 @@ var _ = Describe("podman machine list", func() { Expect(listSession2).To(Exit(0)) var listResponse []*entities.ListReporter - err = jsoniter.Unmarshal(listSession.Bytes(), &listResponse) + err = jsoniter.Unmarshal(listSession2.Bytes(), &listResponse) Expect(err).To(BeNil()) // table format includes the header diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index 4ccb99e96..246f92a19 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -13,6 +13,7 @@ import ( "path/filepath" "runtime" "strings" + "time" "github.com/coreos/stream-metadata-go/fedoracoreos" "github.com/coreos/stream-metadata-go/release" @@ -53,7 +54,7 @@ func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload return nil, err } - dataDir, err := GetDataDir(vmType) + cacheDir, err := GetCacheDir(vmType) if err != nil { return nil, err } @@ -62,15 +63,20 @@ func NewFcosDownloader(vmType, vmName, imageStream string) (DistributionDownload Download: Download{ Arch: getFcosArch(), Artifact: artifact, + CacheDir: cacheDir, Format: Format, ImageName: imageName, - LocalPath: filepath.Join(dataDir, imageName), + LocalPath: filepath.Join(cacheDir, imageName), Sha256sum: info.Sha256Sum, URL: url, VMName: vmName, }, } - fcd.Download.LocalUncompressedFile = fcd.getLocalUncompressedName() + dataDir, err := GetDataDir(vmType) + if err != nil { + return nil, err + } + fcd.Download.LocalUncompressedFile = fcd.getLocalUncompressedFile(dataDir) return fcd, nil } @@ -108,6 +114,13 @@ func (f FcosDownload) HasUsableCache() (bool, error) { return sum.Encoded() == f.Sha256sum, nil } +func (f FcosDownload) CleanCache() error { + // Set cached image to expire after 2 weeks + // FCOS refreshes around every 2 weeks, assume old images aren't needed + expire := 14 * 24 * time.Hour + return removeImageAfterExpire(f.CacheDir, expire) +} + func getFcosArch() string { var arch string // TODO fill in more architectures diff --git a/pkg/machine/fedora.go b/pkg/machine/fedora.go index 46e450418..7c80fc5d3 100644 --- a/pkg/machine/fedora.go +++ b/pkg/machine/fedora.go @@ -6,51 +6,53 @@ package machine import ( "errors" "fmt" - "io" - "io/ioutil" + "os" + "net/http" "net/url" - "os" "path/filepath" - "regexp" - - "github.com/sirupsen/logrus" + "time" ) const ( - githubURL = "http://github.com/fedora-cloud/docker-brew-fedora/" + githubLatestReleaseURL = "https://github.com/containers/podman-wsl-fedora/releases/latest/download/rootfs.tar.xz" ) -var fedoraxzRegex = regexp.MustCompile(`fedora[^\"]+xz`) - type FedoraDownload struct { Download } func NewFedoraDownloader(vmType, vmName, releaseStream string) (DistributionDownload, error) { - imageName, downloadURL, size, err := getFedoraDownload(releaseStream) + downloadURL, size, err := getFedoraDownload(githubLatestReleaseURL) if err != nil { return nil, err } - dataDir, err := GetDataDir(vmType) + cacheDir, err := GetCacheDir(vmType) if err != nil { return nil, err } + imageName := "rootfs.tar.xz" + f := FedoraDownload{ Download: Download{ Arch: getFcosArch(), Artifact: artifact, + CacheDir: cacheDir, Format: Format, ImageName: imageName, - LocalPath: filepath.Join(dataDir, imageName), + LocalPath: filepath.Join(cacheDir, imageName), URL: downloadURL, VMName: vmName, Size: size, }, } - f.Download.LocalUncompressedFile = f.getLocalUncompressedName() + dataDir, err := GetDataDir(vmType) + if err != nil { + return nil, err + } + f.Download.LocalUncompressedFile = f.getLocalUncompressedFile(dataDir) return f, nil } @@ -69,56 +71,27 @@ func (f FedoraDownload) HasUsableCache() (bool, error) { return info.Size() == f.Size, nil } -func truncRead(url string) ([]byte, error) { - resp, err := http.Get(url) - if err != nil { - return nil, err - } - - defer func() { - if err := resp.Body.Close(); err != nil { - logrus.Error(err) - } - }() - - body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 10*1024*1024)) - if err != nil { - return nil, err - } - - _, _ = io.Copy(io.Discard, resp.Body) - - return body, nil +func (f FedoraDownload) CleanCache() error { + // Set cached image to expire after 2 weeks + expire := 14 * 24 * time.Hour + return removeImageAfterExpire(f.CacheDir, expire) } -func getFedoraDownload(releaseStream string) (string, *url.URL, int64, error) { - dirURL := githubURL + "tree/" + releaseStream + "/" + getFcosArch() + "/" - body, err := truncRead(dirURL) - if err != nil { - return "", nil, -1, err - } - - file := fedoraxzRegex.FindString(string(body)) - if len(file) == 0 { - return "", nil, -1, fmt.Errorf("could not locate Fedora download at %s", dirURL) - } - - rawURL := githubURL + "raw/" + releaseStream + "/" + getFcosArch() + "/" - newLocation := rawURL + file - downloadURL, err := url.Parse(newLocation) +func getFedoraDownload(releaseURL string) (*url.URL, int64, error) { + downloadURL, err := url.Parse(releaseURL) if err != nil { - return "", nil, -1, fmt.Errorf("invalid URL generated from discovered Fedora file: %s: %w", newLocation, err) + return nil, -1, fmt.Errorf("invalid URL generated from discovered Fedora file: %s: %w", releaseURL, err) } - resp, err := http.Head(newLocation) + resp, err := http.Head(releaseURL) if err != nil { - return "", nil, -1, fmt.Errorf("head request failed: %s: %w", newLocation, err) + return nil, -1, fmt.Errorf("head request failed: %s: %w", releaseURL, err) } _ = resp.Body.Close() if resp.StatusCode != http.StatusOK { - return "", nil, -1, fmt.Errorf("head request failed [%d] on download: %s", resp.StatusCode, newLocation) + return nil, -1, fmt.Errorf("head request failed: %s: %w", releaseURL, err) } - return file, downloadURL, resp.ContentLength, nil + return downloadURL, resp.ContentLength, nil } diff --git a/pkg/machine/keys.go b/pkg/machine/keys.go index 0c7d7114e..94cbdac04 100644 --- a/pkg/machine/keys.go +++ b/pkg/machine/keys.go @@ -21,6 +21,9 @@ var sshCommand = []string{"ssh-keygen", "-N", "", "-t", "ed25519", "-f"} // CreateSSHKeys makes a priv and pub ssh key for interacting // the a VM. func CreateSSHKeys(writeLocation string) (string, error) { + if err := os.MkdirAll(filepath.Dir(writeLocation), 0700); err != nil { + return "", err + } if err := generatekeys(writeLocation); err != nil { return "", err } diff --git a/pkg/machine/pull.go b/pkg/machine/pull.go index 7e6f01bad..08baa7df8 100644 --- a/pkg/machine/pull.go +++ b/pkg/machine/pull.go @@ -5,6 +5,7 @@ package machine import ( "bufio" + "errors" "fmt" "io" "io/ioutil" @@ -39,6 +40,10 @@ func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload if err != nil { return nil, err } + cacheDir, err := GetCacheDir(vmType) + if err != nil { + return nil, err + } dl := Download{} // Is pullpath a file or url? getURL, err := url2.Parse(pullPath) @@ -48,25 +53,23 @@ func NewGenericDownloader(vmType, vmName, pullPath string) (DistributionDownload if len(getURL.Scheme) > 0 { urlSplit := strings.Split(getURL.Path, "/") imageName = urlSplit[len(urlSplit)-1] - dl.LocalUncompressedFile = filepath.Join(dataDir, imageName) dl.URL = getURL - dl.LocalPath = filepath.Join(dataDir, imageName) + dl.LocalPath = filepath.Join(cacheDir, imageName) } else { // Dealing with FilePath imageName = filepath.Base(pullPath) - dl.LocalUncompressedFile = filepath.Join(dataDir, imageName) dl.LocalPath = pullPath } dl.VMName = vmName dl.ImageName = imageName + dl.LocalUncompressedFile = filepath.Join(dataDir, imageName) // The download needs to be pulled into the datadir gd := GenericDownload{Download: dl} - gd.LocalUncompressedFile = gd.getLocalUncompressedName() return gd, nil } -func (d Download) getLocalUncompressedName() string { +func (d Download) getLocalUncompressedFile(dataDir string) string { var ( extension string ) @@ -78,8 +81,8 @@ func (d Download) getLocalUncompressedName() string { case strings.HasSuffix(d.LocalPath, ".xz"): extension = ".xz" } - uncompressedFilename := filepath.Join(filepath.Dir(d.LocalPath), d.VMName+"_"+d.ImageName) - return strings.TrimSuffix(uncompressedFilename, extension) + uncompressedFilename := d.VMName + "_" + d.ImageName + return filepath.Join(dataDir, strings.TrimSuffix(uncompressedFilename, extension)) } func (g GenericDownload) Get() *Download { @@ -91,6 +94,18 @@ func (g GenericDownload) HasUsableCache() (bool, error) { return g.URL == nil, nil } +// CleanCache cleans out downloaded uncompressed image files +func (g GenericDownload) CleanCache() error { + // Remove any image that has been downloaded via URL + // We never read from cache for generic downloads + if g.URL != nil { + if err := os.Remove(g.LocalPath); err != nil && !errors.Is(err, os.ErrNotExist) { + return err + } + } + return nil +} + func DownloadImage(d DistributionDownload) error { // check if the latest image is already present ok, err := d.HasUsableCache() @@ -101,8 +116,14 @@ func DownloadImage(d DistributionDownload) error { if err := DownloadVMImage(d.Get().URL, d.Get().LocalPath); err != nil { return err } + // Clean out old cached images, since we didn't find needed image in cache + defer func() { + if err = d.CleanCache(); err != nil { + logrus.Warnf("error cleaning machine image cache: %s", err) + } + }() } - return Decompress(d.Get().LocalPath, d.Get().getLocalUncompressedName()) + return Decompress(d.Get().LocalPath, d.Get().LocalUncompressedFile) } // DownloadVMImage downloads a VM image from url to given path @@ -253,3 +274,20 @@ func decompressEverythingElse(src string, output io.WriteCloser) error { _, err = io.Copy(output, uncompressStream) return err } + +func removeImageAfterExpire(dir string, expire time.Duration) error { + now := time.Now() + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + // Delete any cache files that are older than expiry date + if !info.IsDir() && (now.Sub(info.ModTime()) > expire) { + err := os.Remove(path) + if err != nil && !errors.Is(err, os.ErrNotExist) { + logrus.Warnf("unable to clean up cached image: %s", path) + } else { + logrus.Debugf("cleaning up cached image: %s", path) + } + } + return nil + }) + return err +} diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 450d8b877..189723ac7 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -386,7 +386,7 @@ func downloadDistro(v *MachineVM, opts machine.InitOptions) error { if _, e := strconv.Atoi(opts.ImagePath); e == nil { v.ImageStream = opts.ImagePath - dd, err = machine.NewFedoraDownloader(vmtype, v.Name, v.ImageStream) + dd, err = machine.NewFedoraDownloader(vmtype, v.Name, opts.ImagePath) } else { v.ImageStream = "custom" dd, err = machine.NewGenericDownloader(vmtype, v.Name, opts.ImagePath) @@ -449,34 +449,14 @@ func provisionWSLDist(v *MachineVM) (string, error) { } dist := toDist(v.Name) - fmt.Println("Importing operating system into WSL (this may take 5+ minutes on a new WSL install)...") + 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 { return "", fmt.Errorf("the WSL import of guest OS failed: %w", err) } - fmt.Println("Installing packages (this will take awhile)...") - if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "upgrade", "-y"); err != nil { - return "", fmt.Errorf("package upgrade on guest OS failed: %w", err) - } - - fmt.Println("Enabling Copr") - if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install", "-y", "'dnf-command(copr)'"); err != nil { - return "", fmt.Errorf("enabling copr failed: %w", err) - } - - fmt.Println("Enabling podman4 repo") - if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "-y", "copr", "enable", "rhcontainerbot/podman4"); err != nil { - return "", fmt.Errorf("enabling copr failed: %w", err) - } - - if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "install", - "podman", "podman-docker", "openssh-server", "procps-ng", "-y"); err != nil { - return "", fmt.Errorf("package installation on guest OS failed: %w", err) - } - // Fixes newuidmap - if err = runCmdPassThrough("wsl", "-d", dist, "dnf", "reinstall", "shadow-utils", "-y"); err != nil { - return "", fmt.Errorf("package reinstallation of shadow-utils on guest OS failed: %w", err) + if err = runCmdPassThrough("wsl", "-d", 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) } // Windows 11 (NT Version = 10, Build 22000) generates harmless but scary messages on every diff --git a/test/apiv2/25-containersMore.at b/test/apiv2/25-containersMore.at index c9fda8c6f..9cdc2a33f 100644 --- a/test/apiv2/25-containersMore.at +++ b/test/apiv2/25-containersMore.at @@ -53,34 +53,6 @@ t POST libpod/containers/foo/unmount 204 t DELETE libpod/containers/foo?force=true 200 -podman run $IMAGE true - -t GET libpod/containers/json?last=1 200 \ - length=1 \ - .[0].Id~[0-9a-f]\\{64\\} \ - .[0].Image=$IMAGE \ - .[0].Command[0]="true" \ - .[0].State~\\\(exited\\\|stopped\\\) \ - .[0].ExitCode=0 \ - .[0].IsInfra=false - -cid=$(jq -r '.[0].Id' <<<"$output") - -t GET libpod/generate/kube?names=$cid 200 -like "$output" ".*apiVersion:.*" "Check generated kube yaml - apiVersion" -like "$output" ".*kind:\\sPod.*" "Check generated kube yaml - kind: Pod" -like "$output" ".*metadata:.*" "Check generated kube yaml - metadata" -like "$output" ".*spec:.*" "Check generated kube yaml - spec" - -t GET "libpod/generate/kube?service=true&names=$cid" 200 -like "$output" ".*apiVersion:.*" "Check generated kube yaml(service=true) - apiVersion" -like "$output" ".*kind:\\sPod.*" "Check generated kube yaml(service=true) - kind: Pod" -like "$output" ".*metadata:.*" "Check generated kube yaml(service=true) - metadata" -like "$output" ".*spec:.*" "Check generated kube yaml(service=true) - spec" -like "$output" ".*kind:\\sService.*" "Check generated kube yaml(service=true) - kind: Service" - -t DELETE libpod/containers/$cid 200 .[0].Id=$cid - # Create 3 stopped containers to test containers prune podman run $IMAGE true podman run $IMAGE true diff --git a/test/apiv2/80-kube.at b/test/apiv2/80-kube.at new file mode 100644 index 000000000..f40a6556e --- /dev/null +++ b/test/apiv2/80-kube.at @@ -0,0 +1,66 @@ +# -*- sh -*- +# +# test more container-related endpoints +# + +podman create $IMAGE true + +t GET libpod/containers/json?last=1 200 \ + length=1 \ + .[0].Id~[0-9a-f]\\{64\\} \ + .[0].Image=$IMAGE \ + .[0].Command[0]="true" \ + .[0].IsInfra=false + +cid=$(jq -r '.[0].Id' <<<"$output") + +# Make sure that generate-kube works + +t GET libpod/generate/kube?names=$cid 200 +like "$output" ".*apiVersion:.*" "Check generated kube yaml - apiVersion" +like "$output" ".*kind:\\sPod.*" "Check generated kube yaml - kind: Pod" +like "$output" ".*metadata:.*" "Check generated kube yaml - metadata" +like "$output" ".*spec:.*" "Check generated kube yaml - spec" + +t GET "libpod/generate/kube?service=true&names=$cid" 200 +like "$output" ".*apiVersion:.*" "Check generated kube yaml(service=true) - apiVersion" +like "$output" ".*kind:\\sPod.*" "Check generated kube yaml(service=true) - kind: Pod" +like "$output" ".*metadata:.*" "Check generated kube yaml(service=true) - metadata" +like "$output" ".*spec:.*" "Check generated kube yaml(service=true) - spec" +like "$output" ".*kind:\\sService.*" "Check generated kube yaml(service=true) - kind: Service" + +TMPD=$(mktemp -d podman-apiv2-test-kube.XXXXXX) +YAML="${TMPD}/kube.yaml" +echo "$output" > $YAML + +t DELETE libpod/containers/$cid 200 .[0].Id=$cid + +# Make sure that kube-play works + +t POST libpod/kube/play $YAML 200 \ + .Pods[0].ID~[0-9a-f]\\{64\\} \ + .Pods[0].ContainerErrors=null \ + .Pods[0].Containers[0]~[0-9a-f]\\{64\\} + +t DELETE libpod/kube/play $YAML 200 \ + .Pods[0].ID~null \ + .Pods[0].ContainerErrors=null \ + .Pods[0].Containers[0]~null + +# Make sure that play-kube works + +t POST libpod/play/kube $YAML 200 \ + .Pods[0].ID~[0-9a-f]\\{64\\} \ + .Pods[0].ContainerErrors=null \ + .Pods[0].Containers[0]~[0-9a-f]\\{64\\} + +t DELETE libpod/play/kube $YAML 200 \ + .Pods[0].ID~null \ + .Pods[0].ContainerErrors=null \ + .Pods[0].Containers[0]~null \ + .StopReport[0].Id~[0-9a-f]\\{64\\} \ + .RmReport[0].Id~[0-9a-f]\\{64\\} + +rm -rf $TMPD + +# vim: filetype=sh diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2 index 0fd282854..0c3c6e672 100755 --- a/test/apiv2/test-apiv2 +++ b/test/apiv2/test-apiv2 @@ -252,7 +252,7 @@ function t() { fi # POST and PUT requests may be followed by one or more key=value pairs. # Slurp the command line until we see a 3-digit status code. - if [[ $method = "POST" || $method == "PUT" ]]; then + if [[ $method = "POST" || $method == "PUT" || $method = "DELETE" ]]; then local -a post_args for arg; do case "$arg" in @@ -261,6 +261,8 @@ function t() { *.tar) curl_args+=(--data-binary @$arg); content_type="application/x-tar"; shift;; + *.yaml) curl_args+=(--data-binary @$arg); + shift;; application/*) content_type="$arg"; shift;; [1-9][0-9][0-9]) break;; diff --git a/test/e2e/build/Containerfile.with-platform b/test/e2e/build/Containerfile.with-platform index 3bb585a0a..0b030d13c 100644 --- a/test/e2e/build/Containerfile.with-platform +++ b/test/e2e/build/Containerfile.with-platform @@ -1 +1,2 @@ +ARG TARGETPLATFORM FROM --platform=$TARGETPLATFORM alpine diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index 04b7a280d..12f14fdc8 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -108,6 +108,15 @@ var _ = Describe("Podman pull", func() { session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">=", 2), "Expected at least two images") + + session = podmanTest.Podman([]string{"pull", "-a", "quay.io/libpod/testdigest_v2s2"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"images"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(len(session.OutputToStringArray())).To(BeNumerically(">=", 2), "Expected at least two images") }) It("podman pull from docker with nonexistent --authfile", func() { diff --git a/test/e2e/run_cpu_test.go b/test/e2e/run_cpu_test.go index b21be5729..e57eb3b26 100644 --- a/test/e2e/run_cpu_test.go +++ b/test/e2e/run_cpu_test.go @@ -94,7 +94,7 @@ var _ = Describe("Podman run cpu", func() { Expect(result).Should(Exit(0)) Expect(result.OutputToString()).To(Equal("10000")) } else { - result := podmanTest.Podman([]string{"run", "--rm", "--cpu-shares=2", ALPINE, "cat", "/sys/fs/cgroup/cpu/cpu.shares"}) + result := podmanTest.Podman([]string{"run", "--rm", "-c", "2", ALPINE, "cat", "/sys/fs/cgroup/cpu/cpu.shares"}) result.WaitWithDefaultTimeout() Expect(result).Should(Exit(0)) Expect(result.OutputToString()).To(Equal("2")) diff --git a/test/e2e/stop_test.go b/test/e2e/stop_test.go index 97d8ba701..7a258466a 100644 --- a/test/e2e/stop_test.go +++ b/test/e2e/stop_test.go @@ -1,6 +1,7 @@ package integration import ( + "fmt" "io/ioutil" "os" "strings" @@ -363,4 +364,45 @@ var _ = Describe("Podman stop", func() { Expect(session).Should(Exit(0)) Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) }) + + It("podman stop --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", "--all"}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + + session1 = podmanTest.Podman([]string{"stop", cid1, "-f", "status=running"}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(125)) + + session1 = podmanTest.Podman([]string{"stop", "-a", "--filter", fmt.Sprintf("id=%swrongid", shortCid3)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(HaveLen(0)) + + session1 = podmanTest.Podman([]string{"stop", "-a", "--filter", fmt.Sprintf("id=%s", shortCid3)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(BeEquivalentTo(cid3)) + + session1 = podmanTest.Podman([]string{"stop", "-f", fmt.Sprintf("id=%s", cid2)}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + Expect(session1.OutputToString()).To(BeEquivalentTo(cid2)) + }) }) diff --git a/test/system/090-events.bats b/test/system/090-events.bats index 128802360..ceb53ae73 100644 --- a/test/system/090-events.bats +++ b/test/system/090-events.bats @@ -13,11 +13,11 @@ load helpers run_podman run --label $labelname=$labelvalue --name $cname --rm $IMAGE ls expect=".* container start [0-9a-f]\+ (image=$IMAGE, name=$cname,.* ${labelname}=${labelvalue}" - run_podman events --filter type=container --filter container=$cname --filter label=${labelname}=${labelvalue} --filter event=start --stream=false + run_podman events --filter type=container -f container=$cname --filter label=${labelname}=${labelvalue} --filter event=start --stream=false is "$output" "$expect" "filtering by container name and label" # Same thing, but without the container-name filter - run_podman events --filter type=container --filter label=${labelname}=${labelvalue} --filter event=start --stream=false + run_podman events -f type=container --filter label=${labelname}=${labelvalue} --filter event=start --stream=false is "$output" "$expect" "filtering just by label" # Now filter just by container name, no label diff --git a/test/system/700-play.bats b/test/system/700-play.bats index 6c2a8c8b1..72602a9e6 100644 --- a/test/system/700-play.bats +++ b/test/system/700-play.bats @@ -65,12 +65,12 @@ status: {} RELABEL="system_u:object_r:container_file_t:s0" -@test "podman play with stdin" { +@test "podman kube with stdin" { TESTDIR=$PODMAN_TMPDIR/testdir mkdir -p $TESTDIR echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml - run_podman play kube - < $PODMAN_TMPDIR/test.yaml + run_podman kube play - < $PODMAN_TMPDIR/test.yaml if [ -e /usr/sbin/selinuxenabled -a /usr/sbin/selinuxenabled ]; then run ls -Zd $TESTDIR is "$output" "${RELABEL} $TESTDIR" "selinux relabel should have happened" @@ -87,6 +87,8 @@ RELABEL="system_u:object_r:container_file_t:s0" } @test "podman play" { + # Testing that the "podman play" cmd still works now that + # "podman kube" is an option. TESTDIR=$PODMAN_TMPDIR/testdir mkdir -p $TESTDIR echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml @@ -159,13 +161,13 @@ EOF run_podman 1 container exists $service_container } -@test "podman play --network" { +@test "podman kube --network" { TESTDIR=$PODMAN_TMPDIR/testdir mkdir -p $TESTDIR echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml - run_podman 125 play kube --network host $PODMAN_TMPDIR/test.yaml + run_podman 125 kube play --network host $PODMAN_TMPDIR/test.yaml is "$output" ".*invalid value passed to --network: bridge or host networking must be configured in YAML" "podman plan-network should fail with --network host" - run_podman play kube --network slirp4netns:port_handler=slirp4netns $PODMAN_TMPDIR/test.yaml + run_podman kube play --network slirp4netns:port_handler=slirp4netns $PODMAN_TMPDIR/test.yaml run_podman pod inspect --format {{.InfraContainerID}} "${lines[1]}" infraID="$output" run_podman container inspect --format "{{.HostConfig.NetworkMode}}" $infraID @@ -174,7 +176,7 @@ EOF run_podman stop -a -t 0 run_podman pod rm -t 0 -f test_pod - run_podman play kube --network none $PODMAN_TMPDIR/test.yaml + run_podman kube play --network none $PODMAN_TMPDIR/test.yaml run_podman pod inspect --format {{.InfraContainerID}} "${lines[1]}" infraID="$output" run_podman container inspect --format "{{.HostConfig.NetworkMode}}" $infraID @@ -280,12 +282,12 @@ _EOF run_podman rmi -f userimage:latest } -@test "podman play --annotation" { +@test "podman kube --annotation" { TESTDIR=$PODMAN_TMPDIR/testdir RANDOMSTRING=$(random_string 15) mkdir -p $TESTDIR echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml - run_podman play kube --annotation "name=$RANDOMSTRING" $PODMAN_TMPDIR/test.yaml + run_podman kube play --annotation "name=$RANDOMSTRING" $PODMAN_TMPDIR/test.yaml run_podman inspect --format "{{ .Config.Annotations }}" test_pod-test is "$output" ".*name:$RANDOMSTRING" "Annotation should be added to pod" @@ -338,7 +340,7 @@ status: {} assert "$output" =~ "invalid annotation \"test\"=\"$RANDOMSTRING\"" "Expected to fail with annotation length greater than 63" } -@test "podman play kube - default log driver" { +@test "podman kube play - default log driver" { TESTDIR=$PODMAN_TMPDIR/testdir mkdir -p $TESTDIR echo "$testYaml" | sed "s|TESTDIR|${TESTDIR}|g" > $PODMAN_TMPDIR/test.yaml @@ -347,7 +349,7 @@ status: {} default_driver=$output # Make sure that the default log driver is used - run_podman play kube $PODMAN_TMPDIR/test.yaml + run_podman kube play $PODMAN_TMPDIR/test.yaml run_podman inspect --format "{{.HostConfig.LogConfig.Type}}" test_pod-test is "$output" "$default_driver" "play kube uses default log driver" diff --git a/utils/utils.go b/utils/utils.go index 997de150d..aa1c6a958 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -190,3 +190,33 @@ func MovePauseProcessToScope(pausePidPath string) { } } } + +var ( + maybeMoveToSubCgroupSync sync.Once + maybeMoveToSubCgroupSyncErr error +) + +// MaybeMoveToSubCgroup moves the current process in a sub cgroup when +// it is running in the root cgroup on a system that uses cgroupv2. +func MaybeMoveToSubCgroup() error { + maybeMoveToSubCgroupSync.Do(func() { + unifiedMode, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + maybeMoveToSubCgroupSyncErr = err + return + } + if !unifiedMode { + maybeMoveToSubCgroupSyncErr = nil + return + } + cgroup, err := GetOwnCgroup() + if err != nil { + maybeMoveToSubCgroupSyncErr = err + return + } + if cgroup == "/" { + maybeMoveToSubCgroupSyncErr = MoveUnderCgroupSubtree("init") + } + }) + return maybeMoveToSubCgroupSyncErr +} diff --git a/vendor/github.com/blang/semver/v4/LICENSE b/vendor/github.com/blang/semver/v4/LICENSE new file mode 100644 index 000000000..5ba5c86fc --- /dev/null +++ b/vendor/github.com/blang/semver/v4/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Benedikt Lang <github at benediktlang.de> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/github.com/blang/semver/v4/go.mod b/vendor/github.com/blang/semver/v4/go.mod new file mode 100644 index 000000000..06d262218 --- /dev/null +++ b/vendor/github.com/blang/semver/v4/go.mod @@ -0,0 +1,3 @@ +module github.com/blang/semver/v4 + +go 1.14 diff --git a/vendor/github.com/blang/semver/v4/json.go b/vendor/github.com/blang/semver/v4/json.go new file mode 100644 index 000000000..a74bf7c44 --- /dev/null +++ b/vendor/github.com/blang/semver/v4/json.go @@ -0,0 +1,23 @@ +package semver + +import ( + "encoding/json" +) + +// MarshalJSON implements the encoding/json.Marshaler interface. +func (v Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +// UnmarshalJSON implements the encoding/json.Unmarshaler interface. +func (v *Version) UnmarshalJSON(data []byte) (err error) { + var versionString string + + if err = json.Unmarshal(data, &versionString); err != nil { + return + } + + *v, err = Parse(versionString) + + return +} diff --git a/vendor/github.com/blang/semver/v4/range.go b/vendor/github.com/blang/semver/v4/range.go new file mode 100644 index 000000000..95f7139b9 --- /dev/null +++ b/vendor/github.com/blang/semver/v4/range.go @@ -0,0 +1,416 @@ +package semver + +import ( + "fmt" + "strconv" + "strings" + "unicode" +) + +type wildcardType int + +const ( + noneWildcard wildcardType = iota + majorWildcard wildcardType = 1 + minorWildcard wildcardType = 2 + patchWildcard wildcardType = 3 +) + +func wildcardTypefromInt(i int) wildcardType { + switch i { + case 1: + return majorWildcard + case 2: + return minorWildcard + case 3: + return patchWildcard + default: + return noneWildcard + } +} + +type comparator func(Version, Version) bool + +var ( + compEQ comparator = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 0 + } + compNE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) != 0 + } + compGT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == 1 + } + compGE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) >= 0 + } + compLT = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) == -1 + } + compLE = func(v1 Version, v2 Version) bool { + return v1.Compare(v2) <= 0 + } +) + +type versionRange struct { + v Version + c comparator +} + +// rangeFunc creates a Range from the given versionRange. +func (vr *versionRange) rangeFunc() Range { + return Range(func(v Version) bool { + return vr.c(v, vr.v) + }) +} + +// Range represents a range of versions. +// A Range can be used to check if a Version satisfies it: +// +// range, err := semver.ParseRange(">1.0.0 <2.0.0") +// range(semver.MustParse("1.1.1") // returns true +type Range func(Version) bool + +// OR combines the existing Range with another Range using logical OR. +func (rf Range) OR(f Range) Range { + return Range(func(v Version) bool { + return rf(v) || f(v) + }) +} + +// AND combines the existing Range with another Range using logical AND. +func (rf Range) AND(f Range) Range { + return Range(func(v Version) bool { + return rf(v) && f(v) + }) +} + +// ParseRange parses a range and returns a Range. +// If the range could not be parsed an error is returned. +// +// Valid ranges are: +// - "<1.0.0" +// - "<=1.0.0" +// - ">1.0.0" +// - ">=1.0.0" +// - "1.0.0", "=1.0.0", "==1.0.0" +// - "!1.0.0", "!=1.0.0" +// +// A Range can consist of multiple ranges separated by space: +// Ranges can be linked by logical AND: +// - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0" +// - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2 +// +// Ranges can also be linked by logical OR: +// - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x" +// +// AND has a higher precedence than OR. It's not possible to use brackets. +// +// Ranges can be combined by both AND and OR +// +// - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1` +func ParseRange(s string) (Range, error) { + parts := splitAndTrim(s) + orParts, err := splitORParts(parts) + if err != nil { + return nil, err + } + expandedParts, err := expandWildcardVersion(orParts) + if err != nil { + return nil, err + } + var orFn Range + for _, p := range expandedParts { + var andFn Range + for _, ap := range p { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + vr, err := buildVersionRange(opStr, vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err) + } + rf := vr.rangeFunc() + + // Set function + if andFn == nil { + andFn = rf + } else { // Combine with existing function + andFn = andFn.AND(rf) + } + } + if orFn == nil { + orFn = andFn + } else { + orFn = orFn.OR(andFn) + } + + } + return orFn, nil +} + +// splitORParts splits the already cleaned parts by '||'. +// Checks for invalid positions of the operator and returns an +// error if found. +func splitORParts(parts []string) ([][]string, error) { + var ORparts [][]string + last := 0 + for i, p := range parts { + if p == "||" { + if i == 0 { + return nil, fmt.Errorf("First element in range is '||'") + } + ORparts = append(ORparts, parts[last:i]) + last = i + 1 + } + } + if last == len(parts) { + return nil, fmt.Errorf("Last element in range is '||'") + } + ORparts = append(ORparts, parts[last:]) + return ORparts, nil +} + +// buildVersionRange takes a slice of 2: operator and version +// and builds a versionRange, otherwise an error. +func buildVersionRange(opStr, vStr string) (*versionRange, error) { + c := parseComparator(opStr) + if c == nil { + return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, "")) + } + v, err := Parse(vStr) + if err != nil { + return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err) + } + + return &versionRange{ + v: v, + c: c, + }, nil + +} + +// inArray checks if a byte is contained in an array of bytes +func inArray(s byte, list []byte) bool { + for _, el := range list { + if el == s { + return true + } + } + return false +} + +// splitAndTrim splits a range string by spaces and cleans whitespaces +func splitAndTrim(s string) (result []string) { + last := 0 + var lastChar byte + excludeFromSplit := []byte{'>', '<', '='} + for i := 0; i < len(s); i++ { + if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) { + if last < i-1 { + result = append(result, s[last:i]) + } + last = i + 1 + } else if s[i] != ' ' { + lastChar = s[i] + } + } + if last < len(s)-1 { + result = append(result, s[last:]) + } + + for i, v := range result { + result[i] = strings.Replace(v, " ", "", -1) + } + + // parts := strings.Split(s, " ") + // for _, x := range parts { + // if s := strings.TrimSpace(x); len(s) != 0 { + // result = append(result, s) + // } + // } + return +} + +// splitComparatorVersion splits the comparator from the version. +// Input must be free of leading or trailing spaces. +func splitComparatorVersion(s string) (string, string, error) { + i := strings.IndexFunc(s, unicode.IsDigit) + if i == -1 { + return "", "", fmt.Errorf("Could not get version from string: %q", s) + } + return strings.TrimSpace(s[0:i]), s[i:], nil +} + +// getWildcardType will return the type of wildcard that the +// passed version contains +func getWildcardType(vStr string) wildcardType { + parts := strings.Split(vStr, ".") + nparts := len(parts) + wildcard := parts[nparts-1] + + possibleWildcardType := wildcardTypefromInt(nparts) + if wildcard == "x" { + return possibleWildcardType + } + + return noneWildcard +} + +// createVersionFromWildcard will convert a wildcard version +// into a regular version, replacing 'x's with '0's, handling +// special cases like '1.x.x' and '1.x' +func createVersionFromWildcard(vStr string) string { + // handle 1.x.x + vStr2 := strings.Replace(vStr, ".x.x", ".x", 1) + vStr2 = strings.Replace(vStr2, ".x", ".0", 1) + parts := strings.Split(vStr2, ".") + + // handle 1.x + if len(parts) == 2 { + return vStr2 + ".0" + } + + return vStr2 +} + +// incrementMajorVersion will increment the major version +// of the passed version +func incrementMajorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[0]) + if err != nil { + return "", err + } + parts[0] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// incrementMajorVersion will increment the minor version +// of the passed version +func incrementMinorVersion(vStr string) (string, error) { + parts := strings.Split(vStr, ".") + i, err := strconv.Atoi(parts[1]) + if err != nil { + return "", err + } + parts[1] = strconv.Itoa(i + 1) + + return strings.Join(parts, "."), nil +} + +// expandWildcardVersion will expand wildcards inside versions +// following these rules: +// +// * when dealing with patch wildcards: +// >= 1.2.x will become >= 1.2.0 +// <= 1.2.x will become < 1.3.0 +// > 1.2.x will become >= 1.3.0 +// < 1.2.x will become < 1.2.0 +// != 1.2.x will become < 1.2.0 >= 1.3.0 +// +// * when dealing with minor wildcards: +// >= 1.x will become >= 1.0.0 +// <= 1.x will become < 2.0.0 +// > 1.x will become >= 2.0.0 +// < 1.0 will become < 1.0.0 +// != 1.x will become < 1.0.0 >= 2.0.0 +// +// * when dealing with wildcards without +// version operator: +// 1.2.x will become >= 1.2.0 < 1.3.0 +// 1.x will become >= 1.0.0 < 2.0.0 +func expandWildcardVersion(parts [][]string) ([][]string, error) { + var expandedParts [][]string + for _, p := range parts { + var newParts []string + for _, ap := range p { + if strings.Contains(ap, "x") { + opStr, vStr, err := splitComparatorVersion(ap) + if err != nil { + return nil, err + } + + versionWildcardType := getWildcardType(vStr) + flatVersion := createVersionFromWildcard(vStr) + + var resultOperator string + var shouldIncrementVersion bool + switch opStr { + case ">": + resultOperator = ">=" + shouldIncrementVersion = true + case ">=": + resultOperator = ">=" + case "<": + resultOperator = "<" + case "<=": + resultOperator = "<" + shouldIncrementVersion = true + case "", "=", "==": + newParts = append(newParts, ">="+flatVersion) + resultOperator = "<" + shouldIncrementVersion = true + case "!=", "!": + newParts = append(newParts, "<"+flatVersion) + resultOperator = ">=" + shouldIncrementVersion = true + } + + var resultVersion string + if shouldIncrementVersion { + switch versionWildcardType { + case patchWildcard: + resultVersion, _ = incrementMinorVersion(flatVersion) + case minorWildcard: + resultVersion, _ = incrementMajorVersion(flatVersion) + } + } else { + resultVersion = flatVersion + } + + ap = resultOperator + resultVersion + } + newParts = append(newParts, ap) + } + expandedParts = append(expandedParts, newParts) + } + + return expandedParts, nil +} + +func parseComparator(s string) comparator { + switch s { + case "==": + fallthrough + case "": + fallthrough + case "=": + return compEQ + case ">": + return compGT + case ">=": + return compGE + case "<": + return compLT + case "<=": + return compLE + case "!": + fallthrough + case "!=": + return compNE + } + + return nil +} + +// MustParseRange is like ParseRange but panics if the range cannot be parsed. +func MustParseRange(s string) Range { + r, err := ParseRange(s) + if err != nil { + panic(`semver: ParseRange(` + s + `): ` + err.Error()) + } + return r +} diff --git a/vendor/github.com/blang/semver/v4/semver.go b/vendor/github.com/blang/semver/v4/semver.go new file mode 100644 index 000000000..307de610f --- /dev/null +++ b/vendor/github.com/blang/semver/v4/semver.go @@ -0,0 +1,476 @@ +package semver + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +const ( + numbers string = "0123456789" + alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + alphanum = alphas + numbers +) + +// SpecVersion is the latest fully supported spec version of semver +var SpecVersion = Version{ + Major: 2, + Minor: 0, + Patch: 0, +} + +// Version represents a semver compatible version +type Version struct { + Major uint64 + Minor uint64 + Patch uint64 + Pre []PRVersion + Build []string //No Precedence +} + +// Version to string +func (v Version) String() string { + b := make([]byte, 0, 5) + b = strconv.AppendUint(b, v.Major, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Minor, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Patch, 10) + + if len(v.Pre) > 0 { + b = append(b, '-') + b = append(b, v.Pre[0].String()...) + + for _, pre := range v.Pre[1:] { + b = append(b, '.') + b = append(b, pre.String()...) + } + } + + if len(v.Build) > 0 { + b = append(b, '+') + b = append(b, v.Build[0]...) + + for _, build := range v.Build[1:] { + b = append(b, '.') + b = append(b, build...) + } + } + + return string(b) +} + +// FinalizeVersion discards prerelease and build number and only returns +// major, minor and patch number. +func (v Version) FinalizeVersion() string { + b := make([]byte, 0, 5) + b = strconv.AppendUint(b, v.Major, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Minor, 10) + b = append(b, '.') + b = strconv.AppendUint(b, v.Patch, 10) + return string(b) +} + +// Equals checks if v is equal to o. +func (v Version) Equals(o Version) bool { + return (v.Compare(o) == 0) +} + +// EQ checks if v is equal to o. +func (v Version) EQ(o Version) bool { + return (v.Compare(o) == 0) +} + +// NE checks if v is not equal to o. +func (v Version) NE(o Version) bool { + return (v.Compare(o) != 0) +} + +// GT checks if v is greater than o. +func (v Version) GT(o Version) bool { + return (v.Compare(o) == 1) +} + +// GTE checks if v is greater than or equal to o. +func (v Version) GTE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// GE checks if v is greater than or equal to o. +func (v Version) GE(o Version) bool { + return (v.Compare(o) >= 0) +} + +// LT checks if v is less than o. +func (v Version) LT(o Version) bool { + return (v.Compare(o) == -1) +} + +// LTE checks if v is less than or equal to o. +func (v Version) LTE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// LE checks if v is less than or equal to o. +func (v Version) LE(o Version) bool { + return (v.Compare(o) <= 0) +} + +// Compare compares Versions v to o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v Version) Compare(o Version) int { + if v.Major != o.Major { + if v.Major > o.Major { + return 1 + } + return -1 + } + if v.Minor != o.Minor { + if v.Minor > o.Minor { + return 1 + } + return -1 + } + if v.Patch != o.Patch { + if v.Patch > o.Patch { + return 1 + } + return -1 + } + + // Quick comparison if a version has no prerelease versions + if len(v.Pre) == 0 && len(o.Pre) == 0 { + return 0 + } else if len(v.Pre) == 0 && len(o.Pre) > 0 { + return 1 + } else if len(v.Pre) > 0 && len(o.Pre) == 0 { + return -1 + } + + i := 0 + for ; i < len(v.Pre) && i < len(o.Pre); i++ { + if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 { + continue + } else if comp == 1 { + return 1 + } else { + return -1 + } + } + + // If all pr versions are the equal but one has further prversion, this one greater + if i == len(v.Pre) && i == len(o.Pre) { + return 0 + } else if i == len(v.Pre) && i < len(o.Pre) { + return -1 + } else { + return 1 + } + +} + +// IncrementPatch increments the patch version +func (v *Version) IncrementPatch() error { + v.Patch++ + return nil +} + +// IncrementMinor increments the minor version +func (v *Version) IncrementMinor() error { + v.Minor++ + v.Patch = 0 + return nil +} + +// IncrementMajor increments the major version +func (v *Version) IncrementMajor() error { + v.Major++ + v.Minor = 0 + v.Patch = 0 + return nil +} + +// Validate validates v and returns error in case +func (v Version) Validate() error { + // Major, Minor, Patch already validated using uint64 + + for _, pre := range v.Pre { + if !pre.IsNum { //Numeric prerelease versions already uint64 + if len(pre.VersionStr) == 0 { + return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr) + } + if !containsOnly(pre.VersionStr, alphanum) { + return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr) + } + } + } + + for _, build := range v.Build { + if len(build) == 0 { + return fmt.Errorf("Build meta data can not be empty %q", build) + } + if !containsOnly(build, alphanum) { + return fmt.Errorf("Invalid character(s) found in build meta data %q", build) + } + } + + return nil +} + +// New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error +func New(s string) (*Version, error) { + v, err := Parse(s) + vp := &v + return vp, err +} + +// Make is an alias for Parse, parses version string and returns a validated Version or error +func Make(s string) (Version, error) { + return Parse(s) +} + +// ParseTolerant allows for certain version specifications that do not strictly adhere to semver +// specs to be parsed by this library. It does so by normalizing versions before passing them to +// Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions +// with only major and minor components specified, and removes leading 0s. +func ParseTolerant(s string) (Version, error) { + s = strings.TrimSpace(s) + s = strings.TrimPrefix(s, "v") + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + // Remove leading zeros. + for i, p := range parts { + if len(p) > 1 { + p = strings.TrimLeft(p, "0") + if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") { + p = "0" + p + } + parts[i] = p + } + } + // Fill up shortened versions. + if len(parts) < 3 { + if strings.ContainsAny(parts[len(parts)-1], "+-") { + return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data") + } + for len(parts) < 3 { + parts = append(parts, "0") + } + } + s = strings.Join(parts, ".") + + return Parse(s) +} + +// Parse parses version string and returns a validated Version or error +func Parse(s string) (Version, error) { + if len(s) == 0 { + return Version{}, errors.New("Version string empty") + } + + // Split into major.minor.(patch+pr+meta) + parts := strings.SplitN(s, ".", 3) + if len(parts) != 3 { + return Version{}, errors.New("No Major.Minor.Patch elements found") + } + + // Major + if !containsOnly(parts[0], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0]) + } + if hasLeadingZeroes(parts[0]) { + return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0]) + } + major, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return Version{}, err + } + + // Minor + if !containsOnly(parts[1], numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1]) + } + if hasLeadingZeroes(parts[1]) { + return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1]) + } + minor, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return Version{}, err + } + + v := Version{} + v.Major = major + v.Minor = minor + + var build, prerelease []string + patchStr := parts[2] + + if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 { + build = strings.Split(patchStr[buildIndex+1:], ".") + patchStr = patchStr[:buildIndex] + } + + if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 { + prerelease = strings.Split(patchStr[preIndex+1:], ".") + patchStr = patchStr[:preIndex] + } + + if !containsOnly(patchStr, numbers) { + return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr) + } + if hasLeadingZeroes(patchStr) { + return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr) + } + patch, err := strconv.ParseUint(patchStr, 10, 64) + if err != nil { + return Version{}, err + } + + v.Patch = patch + + // Prerelease + for _, prstr := range prerelease { + parsedPR, err := NewPRVersion(prstr) + if err != nil { + return Version{}, err + } + v.Pre = append(v.Pre, parsedPR) + } + + // Build meta data + for _, str := range build { + if len(str) == 0 { + return Version{}, errors.New("Build meta data is empty") + } + if !containsOnly(str, alphanum) { + return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str) + } + v.Build = append(v.Build, str) + } + + return v, nil +} + +// MustParse is like Parse but panics if the version cannot be parsed. +func MustParse(s string) Version { + v, err := Parse(s) + if err != nil { + panic(`semver: Parse(` + s + `): ` + err.Error()) + } + return v +} + +// PRVersion represents a PreRelease Version +type PRVersion struct { + VersionStr string + VersionNum uint64 + IsNum bool +} + +// NewPRVersion creates a new valid prerelease version +func NewPRVersion(s string) (PRVersion, error) { + if len(s) == 0 { + return PRVersion{}, errors.New("Prerelease is empty") + } + v := PRVersion{} + if containsOnly(s, numbers) { + if hasLeadingZeroes(s) { + return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s) + } + num, err := strconv.ParseUint(s, 10, 64) + + // Might never be hit, but just in case + if err != nil { + return PRVersion{}, err + } + v.VersionNum = num + v.IsNum = true + } else if containsOnly(s, alphanum) { + v.VersionStr = s + v.IsNum = false + } else { + return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s) + } + return v, nil +} + +// IsNumeric checks if prerelease-version is numeric +func (v PRVersion) IsNumeric() bool { + return v.IsNum +} + +// Compare compares two PreRelease Versions v and o: +// -1 == v is less than o +// 0 == v is equal to o +// 1 == v is greater than o +func (v PRVersion) Compare(o PRVersion) int { + if v.IsNum && !o.IsNum { + return -1 + } else if !v.IsNum && o.IsNum { + return 1 + } else if v.IsNum && o.IsNum { + if v.VersionNum == o.VersionNum { + return 0 + } else if v.VersionNum > o.VersionNum { + return 1 + } else { + return -1 + } + } else { // both are Alphas + if v.VersionStr == o.VersionStr { + return 0 + } else if v.VersionStr > o.VersionStr { + return 1 + } else { + return -1 + } + } +} + +// PreRelease version to string +func (v PRVersion) String() string { + if v.IsNum { + return strconv.FormatUint(v.VersionNum, 10) + } + return v.VersionStr +} + +func containsOnly(s string, set string) bool { + return strings.IndexFunc(s, func(r rune) bool { + return !strings.ContainsRune(set, r) + }) == -1 +} + +func hasLeadingZeroes(s string) bool { + return len(s) > 1 && s[0] == '0' +} + +// NewBuildVersion creates a new valid build version +func NewBuildVersion(s string) (string, error) { + if len(s) == 0 { + return "", errors.New("Buildversion is empty") + } + if !containsOnly(s, alphanum) { + return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s) + } + return s, nil +} + +// FinalizeVersion returns the major, minor and patch number only and discards +// prerelease and build number. +func FinalizeVersion(s string) (string, error) { + v, err := Parse(s) + if err != nil { + return "", err + } + v.Pre = nil + v.Build = nil + + finalVer := v.String() + return finalVer, nil +} diff --git a/vendor/github.com/blang/semver/v4/sort.go b/vendor/github.com/blang/semver/v4/sort.go new file mode 100644 index 000000000..e18f88082 --- /dev/null +++ b/vendor/github.com/blang/semver/v4/sort.go @@ -0,0 +1,28 @@ +package semver + +import ( + "sort" +) + +// Versions represents multiple versions. +type Versions []Version + +// Len returns length of version collection +func (s Versions) Len() int { + return len(s) +} + +// Swap swaps two versions inside the collection by its indices +func (s Versions) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Less checks if version at index i is less than version at index j +func (s Versions) Less(i, j int) bool { + return s[i].LT(s[j]) +} + +// Sort sorts a slice of versions +func Sort(versions []Version) { + sort.Sort(Versions(versions)) +} diff --git a/vendor/github.com/blang/semver/v4/sql.go b/vendor/github.com/blang/semver/v4/sql.go new file mode 100644 index 000000000..db958134f --- /dev/null +++ b/vendor/github.com/blang/semver/v4/sql.go @@ -0,0 +1,30 @@ +package semver + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements the database/sql.Scanner interface. +func (v *Version) Scan(src interface{}) (err error) { + var str string + switch src := src.(type) { + case string: + str = src + case []byte: + str = string(src) + default: + return fmt.Errorf("version.Scan: cannot convert %T to string", src) + } + + if t, err := Parse(str); err == nil { + *v = t + } + + return +} + +// Value implements the database/sql/driver.Valuer interface. +func (v Version) Value() (driver.Value, error) { + return v.String(), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 195c40405..bd6f81903 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -41,8 +41,10 @@ github.com/acarl005/stripansi # github.com/beorn7/perks v1.0.1 github.com/beorn7/perks/quantile # github.com/blang/semver v3.5.1+incompatible -## explicit github.com/blang/semver +# github.com/blang/semver/v4 v4.0.0 +## explicit +github.com/blang/semver/v4 # github.com/buger/goterm v1.0.4 ## explicit github.com/buger/goterm diff --git a/version/version.go b/version/version.go index 57403b42c..0a84bb235 100644 --- a/version/version.go +++ b/version/version.go @@ -1,7 +1,7 @@ package version import ( - "github.com/blang/semver" + "github.com/blang/semver/v4" ) type ( |