diff options
Diffstat (limited to 'cmd/podman')
25 files changed, 362 insertions, 260 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index a3ff37c19..4598e535d 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -5,6 +5,7 @@ import ( "github.com/containers/common/pkg/auth" "github.com/containers/common/pkg/completion" + commonFlag "github.com/containers/common/pkg/flag" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities" @@ -319,6 +320,9 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, "Kernel memory limit "+sizeWithUnitFormat, ) _ = cmd.RegisterFlagCompletionFunc(kernelMemoryFlagName, completion.AutocompleteNone) + // kernel-memory is deprecated in the runtime spec. + _ = createFlags.MarkHidden("kernel-memory") + logDriverFlagName := "log-driver" createFlags.StringVar( &cf.LogDriver, @@ -406,7 +410,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, createFlags.StringVar( &cf.Variant, variantFlagName, "", - "Use _VARIANT_ instead of the running architecture variant for choosing images", + "Use `VARIANT` instead of the running architecture variant for choosing images", ) _ = cmd.RegisterFlagCompletionFunc(variantFlagName, completion.AutocompleteNone) @@ -586,12 +590,9 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(timeoutFlagName, completion.AutocompleteNone) - // Flag for TLS verification, so that `run` and `create` commands can make use of it. - // Make sure to use `=` while using this flag i.e `--tls-verify=false/true` - tlsVerifyFlagName := "tls-verify" - createFlags.BoolVar( + commonFlag.OptionalBoolFlag(createFlags, &cf.TLSVerify, - tlsVerifyFlagName, true, + "tls-verify", "Require HTTPS and verify certificates when contacting registries for pulling images", ) diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index 09ac61f2e..6283eb28e 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -18,6 +18,7 @@ import ( "github.com/containers/podman/v3/pkg/specgen" "github.com/docker/docker/api/types/mount" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) func stringMaptoArray(m map[string]string) []string { @@ -104,15 +105,18 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c addField(&builder, "target", m.Target) addField(&builder, "ro", strconv.FormatBool(m.ReadOnly)) addField(&builder, "consistency", string(m.Consistency)) - // Map any specialized mount options that intersect between *Options and cli options switch m.Type { case mount.TypeBind: - addField(&builder, "bind-propagation", string(m.BindOptions.Propagation)) - addField(&builder, "bind-nonrecursive", strconv.FormatBool(m.BindOptions.NonRecursive)) + if m.BindOptions != nil { + addField(&builder, "bind-propagation", string(m.BindOptions.Propagation)) + addField(&builder, "bind-nonrecursive", strconv.FormatBool(m.BindOptions.NonRecursive)) + } case mount.TypeTmpfs: - addField(&builder, "tmpfs-size", strconv.FormatInt(m.TmpfsOptions.SizeBytes, 10)) - addField(&builder, "tmpfs-mode", strconv.FormatUint(uint64(m.TmpfsOptions.Mode), 10)) + if m.TmpfsOptions != nil { + addField(&builder, "tmpfs-size", strconv.FormatInt(m.TmpfsOptions.SizeBytes, 10)) + addField(&builder, "tmpfs-mode", strconv.FormatUint(uint64(m.TmpfsOptions.Mode), 10)) + } case mount.TypeVolume: // All current VolumeOpts are handled above // See vendor/github.com/containers/common/pkg/parse/parse.go:ValidateVolumeOpts() @@ -288,7 +292,7 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c Rm: cc.HostConfig.AutoRemove, SecurityOpt: cc.HostConfig.SecurityOpt, StopSignal: cc.Config.StopSignal, - StorageOpt: stringMaptoArray(cc.HostConfig.StorageOpt), + StorageOpts: stringMaptoArray(cc.HostConfig.StorageOpt), Sysctl: stringMaptoArray(cc.HostConfig.Sysctls), Systemd: "true", // podman default TmpFS: parsedTmp, @@ -380,6 +384,9 @@ func ContainerCreateToContainerCLIOpts(cc handlers.CreateContainerConfig, rtc *c if cc.HostConfig.Memory > 0 { cliOpts.Memory = strconv.Itoa(int(cc.HostConfig.Memory)) } + if cc.HostConfig.KernelMemory > 0 { + logrus.Warnf("The --kernel-memory flag has been deprecated. May not work properly on your system.") + } if cc.HostConfig.MemoryReservation > 0 { cliOpts.MemoryReservation = strconv.Itoa(int(cc.HostConfig.MemoryReservation)) diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index bfeeb7ebe..d35c1a192 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -303,6 +303,11 @@ func PullImage(imageName string, cliVals entities.ContainerCreateOptions) (strin } } + skipTLSVerify := types.OptionalBoolUndefined + if cliVals.TLSVerify.Present() { + skipTLSVerify = types.NewOptionalBool(!cliVals.TLSVerify.Value()) + } + pullReport, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{ Authfile: cliVals.Authfile, Quiet: cliVals.Quiet, @@ -311,7 +316,7 @@ func PullImage(imageName string, cliVals entities.ContainerCreateOptions) (strin Variant: cliVals.Variant, SignaturePolicy: cliVals.SignaturePolicy, PullPolicy: pullPolicy, - SkipTLSVerify: types.NewOptionalBool(!cliVals.TLSVerify), // If Flag changed for TLS Verification + SkipTLSVerify: skipTLSVerify, }) if pullErr != nil { return "", pullErr @@ -372,15 +377,10 @@ func createPodIfNecessary(s *specgen.SpecGenerator, netOpts *entities.NetOptions } infraOpts := entities.ContainerCreateOptions{ImageVolume: "bind", Net: netOpts, Quiet: true} - rawImageName := config.DefaultInfraImage - name, err := PullImage(rawImageName, infraOpts) - if err != nil { - fmt.Println(err) - } - imageName := name + imageName := config.DefaultInfraImage podGen.InfraImage = imageName podGen.InfraContainerSpec = specgen.NewSpecGenerator(imageName, false) - podGen.InfraContainerSpec.RawImageName = rawImageName + podGen.InfraContainerSpec.RawImageName = imageName podGen.InfraContainerSpec.NetworkOptions = podGen.NetworkOptions err = specgenutil.FillOutSpecGen(podGen.InfraContainerSpec, &infraOpts, []string{}) if err != nil { diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index f309390c3..0e582ae52 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -8,7 +8,6 @@ import ( "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" - "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -73,7 +72,8 @@ func port(_ *cobra.Command, args []string) error { var ( container string err error - userPort types.OCICNIPortMapping + userPort uint16 + userProto string ) if len(args) == 0 && !portOpts.Latest && !portOpts.All { @@ -101,16 +101,12 @@ func port(_ *cobra.Command, args []string) error { fields = append(fields, "tcp") } - portNum, err := strconv.Atoi(fields[0]) + portNum, err := strconv.ParseUint(fields[0], 10, 16) if err != nil { return err } - userPort = types.OCICNIPortMapping{ - HostPort: 0, - ContainerPort: int32(portNum), - Protocol: fields[1], - HostIP: "", - } + userPort = uint16(portNum) + userProto = fields[1] } reports, err := registry.ContainerEngine().ContainerPort(registry.GetContext(), container, portOpts) @@ -120,24 +116,36 @@ func port(_ *cobra.Command, args []string) error { var found bool // Iterate mappings for _, report := range reports { + allPrefix := "" + if portOpts.All { + allPrefix = report.Id[:12] + "\t" + } for _, v := range report.Ports { hostIP := v.HostIP // Set host IP to 0.0.0.0 if blank if hostIP == "" { hostIP = "0.0.0.0" } - if portOpts.All { - fmt.Printf("%s\t", report.Id[:12]) - } - // If not searching by port or port/proto, then dump what we see - if port == "" { - fmt.Printf("%d/%s -> %s:%d\n", v.ContainerPort, v.Protocol, hostIP, v.HostPort) - continue - } - if v.ContainerPort == userPort.ContainerPort { - fmt.Printf("%s:%d\n", hostIP, v.HostPort) - found = true - break + protocols := strings.Split(v.Protocol, ",") + for _, protocol := range protocols { + // If not searching by port or port/proto, then dump what we see + if port == "" { + for i := uint16(0); i < v.Range; i++ { + fmt.Printf("%s%d/%s -> %s:%d\n", allPrefix, v.ContainerPort+i, protocol, hostIP, v.HostPort+i) + } + continue + } + // check if the proto matches and if the port is in the range + // this is faster than looping over the range for no reason + if v.Protocol == userProto && + v.ContainerPort <= userPort && + v.ContainerPort+v.Range > userPort { + // we have to add the current range to the host port + hostPort := v.HostPort + userPort - v.ContainerPort + fmt.Printf("%s%s:%d\n", allPrefix, hostIP, hostPort) + found = true + break + } } } if !found && port != "" { diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index 9687cd5bd..712de327c 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -3,8 +3,6 @@ package containers import ( "fmt" "os" - "sort" - "strconv" "strings" "time" @@ -477,174 +475,31 @@ func (l psReporter) UTS() string { // portsToString converts the ports used to a string of the from "port1, port2" // and also groups a continuous list of ports into a readable format. -func portsToString(ports []types.OCICNIPortMapping) string { +// The format is IP:HostPort(-Range)->ContainerPort(-Range)/Proto +func portsToString(ports []types.PortMapping) string { if len(ports) == 0 { return "" } - // Sort the ports, so grouping continuous ports become easy. - sort.Slice(ports, func(i, j int) bool { - return comparePorts(ports[i], ports[j]) - }) - - portGroups := [][]types.OCICNIPortMapping{} - currentGroup := []types.OCICNIPortMapping{} - for i, v := range ports { - var prevPort, nextPort *int32 - if i > 0 { - prevPort = &ports[i-1].ContainerPort - } - if i+1 < len(ports) { - nextPort = &ports[i+1].ContainerPort - } - - port := v.ContainerPort - - // Helper functions - addToCurrentGroup := func(x types.OCICNIPortMapping) { - currentGroup = append(currentGroup, x) - } - - addToPortGroup := func(x types.OCICNIPortMapping) { - portGroups = append(portGroups, []types.OCICNIPortMapping{x}) - } - - finishCurrentGroup := func() { - portGroups = append(portGroups, currentGroup) - currentGroup = []types.OCICNIPortMapping{} - } - - // Single entry slice - if prevPort == nil && nextPort == nil { - addToPortGroup(v) - } - - // Start of the slice with len > 0 - if prevPort == nil && nextPort != nil { - isGroup := *nextPort-1 == port - - if isGroup { - // Start with a group - addToCurrentGroup(v) - } else { - // Start with single item - addToPortGroup(v) - } - - continue - } - - // Middle of the slice with len > 0 - if prevPort != nil && nextPort != nil { - currentIsGroup := *prevPort+1 == port - nextIsGroup := *nextPort-1 == port - - if currentIsGroup { - // Maybe in the middle of a group - addToCurrentGroup(v) - - if !nextIsGroup { - // End of a group - finishCurrentGroup() - } - } else if nextIsGroup { - // Start of a new group - addToCurrentGroup(v) - } else { - // No group at all - addToPortGroup(v) - } - - continue - } - - // End of the slice with len > 0 - if prevPort != nil && nextPort == nil { - isGroup := *prevPort+1 == port - - if isGroup { - // End group - addToCurrentGroup(v) - finishCurrentGroup() - } else { - // End single item - addToPortGroup(v) - } - } - } - - portDisplay := []string{} - for _, group := range portGroups { - if len(group) == 0 { - // Usually should not happen, but better do not crash. - continue - } - - first := group[0] - - hostIP := first.HostIP + sb := &strings.Builder{} + for _, port := range ports { + hostIP := port.HostIP if hostIP == "" { hostIP = "0.0.0.0" } - - // Single mappings - if len(group) == 1 { - portDisplay = append(portDisplay, - fmt.Sprintf( - "%s:%d->%d/%s", - hostIP, first.HostPort, first.ContainerPort, first.Protocol, - ), - ) - continue - } - - // Group mappings - last := group[len(group)-1] - portDisplay = append(portDisplay, formatGroup( - fmt.Sprintf("%s/%s", hostIP, first.Protocol), - first.HostPort, last.HostPort, - first.ContainerPort, last.ContainerPort, - )) - } - return strings.Join(portDisplay, ", ") -} - -func comparePorts(i, j types.OCICNIPortMapping) bool { - if i.ContainerPort != j.ContainerPort { - return i.ContainerPort < j.ContainerPort - } - - if i.HostIP != j.HostIP { - return i.HostIP < j.HostIP - } - - if i.HostPort != j.HostPort { - return i.HostPort < j.HostPort - } - - return i.Protocol < j.Protocol -} - -// formatGroup returns the group in the format: -// <IP:firstHost:lastHost->firstCtr:lastCtr/Proto> -// e.g 0.0.0.0:1000-1006->2000-2006/tcp. -func formatGroup(key string, firstHost, lastHost, firstCtr, lastCtr int32) string { - parts := strings.Split(key, "/") - groupType := parts[0] - var ip string - if len(parts) > 1 { - ip = parts[0] - groupType = parts[1] - } - - group := func(first, last int32) string { - group := strconv.Itoa(int(first)) - if first != last { - group = fmt.Sprintf("%s-%d", group, last) + protocols := strings.Split(port.Protocol, ",") + for _, protocol := range protocols { + if port.Range > 1 { + fmt.Fprintf(sb, "%s:%d-%d->%d-%d/%s, ", + hostIP, port.HostPort, port.HostPort+port.Range-1, + port.ContainerPort, port.ContainerPort+port.Range-1, protocol) + } else { + fmt.Fprintf(sb, "%s:%d->%d/%s, ", + hostIP, port.HostPort, + port.ContainerPort, protocol) + } } - return group } - hostGroup := group(firstHost, lastHost) - ctrGroup := group(firstCtr, lastCtr) - - return fmt.Sprintf("%s:%s->%s/%s", ip, hostGroup, ctrGroup, groupType) + display := sb.String() + // make sure to trim the last ", " of the string + return display[:len(display)-2] } diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go index 1163b9093..8813fc273 100644 --- a/cmd/podman/containers/start.go +++ b/cmd/podman/containers/start.go @@ -87,6 +87,9 @@ func validateStart(cmd *cobra.Command, args []string) error { if len(args) == 0 && !startOptions.Latest && !startOptions.All { return errors.New("start requires at least one argument") } + if startOptions.All && startOptions.Latest { + return errors.Errorf("--all and --latest cannot be used together") + } if len(args) > 0 && startOptions.Latest { return errors.Errorf("--latest and containers cannot be used together") } diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go index 11e8f6870..d21feaabc 100644 --- a/cmd/podman/containers/stats.go +++ b/cmd/podman/containers/stats.go @@ -11,9 +11,7 @@ import ( "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/validate" "github.com/containers/podman/v3/libpod/define" - "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/podman/v3/pkg/rootless" "github.com/containers/podman/v3/utils" "github.com/docker/go-units" "github.com/pkg/errors" @@ -113,16 +111,6 @@ func checkStatOptions(cmd *cobra.Command, args []string) error { } func stats(cmd *cobra.Command, args []string) error { - if rootless.IsRootless() { - unified, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return err - } - if !unified { - return errors.New("stats is not supported in rootless mode without cgroups v2") - } - } - // Convert to the entities options. We should not leak CLI-only // options into the backend and separate concerns. opts := entities.ContainerStatsOptions{ diff --git a/cmd/podman/diff/diff.go b/cmd/podman/diff/diff.go index 81bbb6c43..fba4ea540 100644 --- a/cmd/podman/diff/diff.go +++ b/cmd/podman/diff/diff.go @@ -8,7 +8,7 @@ import ( "github.com/containers/common/pkg/report" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/docker/docker/pkg/archive" + "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" "github.com/spf13/cobra" ) diff --git a/cmd/podman/images/buildx.go b/cmd/podman/images/buildx.go index 5c8e5aaa0..2577a3a74 100644 --- a/cmd/podman/images/buildx.go +++ b/cmd/podman/images/buildx.go @@ -14,11 +14,12 @@ var ( // If we are adding new buildx features, we will add them by default // to podman build. buildxCmd = &cobra.Command{ - Use: "buildx", - Short: "Build images", - Long: "Build images", - RunE: validate.SubCommandExists, - Hidden: true, + Use: "buildx", + Aliases: []string{"builder"}, + Short: "Build images", + Long: "Build images", + RunE: validate.SubCommandExists, + Hidden: true, } ) diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go index 6c39e5c69..fc7451c41 100644 --- a/cmd/podman/images/prune.go +++ b/cmd/podman/images/prune.go @@ -36,6 +36,11 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: pruneCmd, + Parent: buildxCmd, + }) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: pruneCmd, Parent: imageCmd, }) diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go index a4e3515db..a990d1626 100644 --- a/cmd/podman/images/pull.go +++ b/cmd/podman/images/pull.go @@ -92,7 +92,7 @@ func pullFlags(cmd *cobra.Command) { _ = cmd.RegisterFlagCompletionFunc(osFlagName, completion.AutocompleteOS) variantFlagName := "variant" - flags.StringVar(&pullOptions.Variant, variantFlagName, "", " use VARIANT instead of the running architecture variant for choosing images") + flags.StringVar(&pullOptions.Variant, variantFlagName, "", "Use VARIANT instead of the running architecture variant for choosing images") _ = cmd.RegisterFlagCompletionFunc(variantFlagName, completion.AutocompleteNone) platformFlagName := "platform" diff --git a/cmd/podman/images/scp.go b/cmd/podman/images/scp.go index 176563440..8402d9a10 100644 --- a/cmd/podman/images/scp.go +++ b/cmd/podman/images/scp.go @@ -16,6 +16,7 @@ import ( "github.com/containers/podman/v3/cmd/podman/system/connection" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/pkg/rootless" "github.com/docker/distribution/reference" scpD "github.com/dtylman/scp" "github.com/pkg/errors" @@ -125,6 +126,11 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) { fmt.Println(rep) // TODO: Add podman remote support default: // else native load + scpOpts.Save.Format = "oci-archive" + _, err := os.Open(scpOpts.Save.Output) + if err != nil { + return err + } if scpOpts.Tag != "" { return errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported") } @@ -133,12 +139,20 @@ func scp(cmd *cobra.Command, args []string) (finalErr error) { if abiErr != nil { errors.Wrapf(abiErr, "could not save image as specified") } - rep, err := abiEng.Load(context.Background(), scpOpts.Load) - if err != nil { - return err + if !rootless.IsRootless() && scpOpts.Rootless { + err := abiEng.Transfer(context.Background(), scpOpts) + if err != nil { + return err + } + } else { + rep, err := abiEng.Load(context.Background(), scpOpts.Load) + if err != nil { + return err + } + fmt.Println("Loaded image(s): " + strings.Join(rep.Names, ",")) } - fmt.Println("Loaded image(s): " + strings.Join(rep.Names, ",")) } + return nil } @@ -185,7 +199,7 @@ func saveToRemote(image, localFile string, tag string, uri *urlP.URL, iden strin return errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported") } podman := os.Args[0] - run := podman + " image save " + image + " --format=oci-archive --output=" + remoteFile // run ssh image load of the file copied via scp. Files are reverse in thie case... + run := podman + " image save " + image + " --format=oci-archive --output=" + remoteFile // run ssh image load of the file copied via scp. Files are reverse in this case... _, err = connection.ExecRemoteCommand(dial, run) if err != nil { return nil @@ -271,7 +285,14 @@ func parseArgs(args []string, cfg *config.Config) (map[string]config.Destination scpOpts.SourceImageName = args[0] } case 2: - if strings.Contains(args[0], "::") { + if strings.Contains(args[0], "localhost") || strings.Contains(args[1], "localhost") { // only supporting root to local using sudo at the moment + scpOpts.Rootless = true + scpOpts.User = strings.Split(args[1], "@")[0] + scpOpts.SourceImageName = strings.Split(args[0], "::")[1] + if strings.Split(args[0], "@")[0] != "root" { + return nil, errors.Wrapf(define.ErrInvalidArg, "cannot transfer images from any user besides root using sudo") + } + } else if strings.Contains(args[0], "::") { if !(strings.Contains(args[1], "::")) && remoteArgLength(args[0], 1) == 0 { // if an image is specified, this mean we are loading to our client cliConnections = append(cliConnections, args[0]) scpOpts.ToRemote = true diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go index 11e54578a..c9a4793aa 100644 --- a/cmd/podman/images/search.go +++ b/cmd/podman/images/search.go @@ -3,6 +3,7 @@ package images import ( "fmt" "os" + "strings" "github.com/containers/common/pkg/auth" "github.com/containers/common/pkg/completion" @@ -19,6 +20,7 @@ import ( type searchOptionsWrapper struct { entities.ImageSearchOptions // CLI only flags + Compatible bool // Docker compat TLSVerifyCLI bool // Used to convert to an optional bool later Format string // For go templating } @@ -79,7 +81,7 @@ func searchFlags(cmd *cobra.Command) { filterFlagName := "filter" flags.StringSliceVarP(&searchOptions.Filters, filterFlagName, "f", []string{}, "Filter output based on conditions provided (default [])") - //TODO add custom filter function + // TODO add custom filter function _ = cmd.RegisterFlagCompletionFunc(filterFlagName, completion.AutocompleteNone) formatFlagName := "format" @@ -90,7 +92,8 @@ func searchFlags(cmd *cobra.Command) { flags.IntVar(&searchOptions.Limit, limitFlagName, 0, "Limit the number of results") _ = cmd.RegisterFlagCompletionFunc(limitFlagName, completion.AutocompleteNone) - flags.BoolVar(&searchOptions.NoTrunc, "no-trunc", false, "Do not truncate the output") + flags.Bool("no-trunc", true, "Do not truncate the output. Default: true") + flags.BoolVar(&searchOptions.Compatible, "compatible", false, "List stars, official and automated columns (Docker compatibility)") authfileFlagName := "authfile" flags.StringVar(&searchOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") @@ -132,11 +135,20 @@ func imageSearch(cmd *cobra.Command, args []string) error { if err != nil { return err } - if len(searchReport) == 0 { return nil } + noTrunc, _ := cmd.Flags().GetBool("no-trunc") + isJSON := report.IsJSON(searchOptions.Format) + for i, element := range searchReport { + d := strings.ReplaceAll(element.Description, "\n", " ") + if len(d) > 44 && !(noTrunc || isJSON) { + d = strings.TrimSpace(d[:44]) + "..." + } + searchReport[i].Description = d + } + hdrs := report.Headers(entities.ImageSearchReport{}, nil) renderHeaders := true var row string @@ -145,18 +157,22 @@ func imageSearch(cmd *cobra.Command, args []string) error { if len(searchOptions.Filters) != 0 { return errors.Errorf("filters are not applicable to list tags result") } - if report.IsJSON(searchOptions.Format) { + if isJSON { listTagsEntries := buildListTagsJSON(searchReport) return printArbitraryJSON(listTagsEntries) } row = "{{.Name}}\t{{.Tag}}\n" - case report.IsJSON(searchOptions.Format): + case isJSON: return printArbitraryJSON(searchReport) case cmd.Flags().Changed("format"): renderHeaders = report.HasTable(searchOptions.Format) row = report.NormalizeFormat(searchOptions.Format) default: - row = "{{.Index}}\t{{.Name}}\t{{.Description}}\t{{.Stars}}\t{{.Official}}\t{{.Automated}}\n" + row = "{{.Name}}\t{{.Description}}" + if searchOptions.Compatible { + row += "\t{{.Stars}}\t{{.Official}}\t{{.Automated}}" + } + row += "\n" } format := report.EnforceRange(row) @@ -190,7 +206,7 @@ func printArbitraryJSON(v interface{}) error { } func buildListTagsJSON(searchReport []entities.ImageSearchReport) []listEntryTag { - entries := []listEntryTag{} + entries := make([]listEntryTag, 0) ReportLoop: for _, report := range searchReport { diff --git a/cmd/podman/machine/list.go b/cmd/podman/machine/list.go index 7e5459e08..d569f4db0 100644 --- a/cmd/podman/machine/list.go +++ b/cmd/podman/machine/list.go @@ -48,6 +48,7 @@ type machineReporter struct { Created string Running bool LastUp string + Stream string VMType string CPUs uint64 Memory string @@ -153,6 +154,13 @@ func strUint(u uint64) string { return strconv.FormatUint(u, 10) } +func streamName(imageStream string) string { + if imageStream == "" { + return "default" + } + return imageStream +} + func toMachineFormat(vms []*machine.ListResponse) ([]*machineReporter, error) { cfg, err := config.ReadCustomConfig() if err != nil { @@ -167,6 +175,7 @@ func toMachineFormat(vms []*machine.ListResponse) ([]*machineReporter, error) { response.Running = vm.Running response.LastUp = strTime(vm.LastUp) response.Created = strTime(vm.CreatedAt) + response.Stream = streamName(vm.Stream) response.VMType = vm.VMType response.CPUs = vm.CPUs response.Memory = strUint(vm.Memory * units.MiB) diff --git a/cmd/podman/machine/stop.go b/cmd/podman/machine/stop.go index 76ba85601..75666f734 100644 --- a/cmd/podman/machine/stop.go +++ b/cmd/podman/machine/stop.go @@ -3,6 +3,8 @@ package machine import ( + "fmt" + "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/pkg/machine" "github.com/containers/podman/v3/pkg/machine/qemu" @@ -46,5 +48,9 @@ func stop(cmd *cobra.Command, args []string) error { if err != nil { return err } - return vm.Stop(vmName, machine.StopOptions{}) + if err := vm.Stop(vmName, machine.StopOptions{}); err != nil { + return err + } + fmt.Printf("Machine %q stopped successfully\n", vmName) + return nil } diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index e6869efd3..581b29113 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -80,6 +80,14 @@ func init() { flags.StringVar(&kubeOptions.LogDriver, logDriverFlagName, "", "Logging driver for the container") _ = kubeCmd.RegisterFlagCompletionFunc(logDriverFlagName, common.AutocompleteLogDriver) + logOptFlagName := "log-opt" + flags.StringSliceVar( + &kubeOptions.LogOptions, + logOptFlagName, []string{}, + "Logging driver options", + ) + _ = kubeCmd.RegisterFlagCompletionFunc(logOptFlagName, common.AutocompleteLogOpt) + 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") diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index d5aaf09ce..0a0d43b53 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -132,7 +132,7 @@ func create(cmd *cobra.Command, args []string) error { } createOptions.Share = nil } else { - // reassign certain optios for lbpod api, these need to be populated in spec + // reassign certain options for lbpod api, these need to be populated in spec flags := cmd.Flags() infraOptions.Net, err = common.NetFlagsToNetOptions(nil, *flags, false) if err != nil { @@ -242,16 +242,6 @@ func create(cmd *cobra.Command, args []string) error { } if createOptions.Infra { rawImageName = img - if !infraOptions.RootFS { - curr := infraOptions.Quiet - infraOptions.Quiet = true - name, err := containers.PullImage(imageName, infraOptions) - if err != nil { - fmt.Println(err) - } - imageName = name - infraOptions.Quiet = curr - } podSpec.InfraImage = imageName if infraOptions.Entrypoint != nil { createOptions.InfraCommand = infraOptions.Entrypoint diff --git a/cmd/podman/registry/remote.go b/cmd/podman/registry/remote.go index b5da98bd4..c78930574 100644 --- a/cmd/podman/registry/remote.go +++ b/cmd/podman/registry/remote.go @@ -19,11 +19,17 @@ var remoteFromCLI = struct { // Use in init() functions as an initialization check func IsRemote() bool { remoteFromCLI.sync.Do(func() { + remote := false + if _, ok := os.LookupEnv("CONTAINER_HOST"); ok { + remote = true + } else if _, ok := os.LookupEnv("CONTAINER_CONNECTION"); ok { + remote = true + } fs := pflag.NewFlagSet("remote", pflag.ContinueOnError) fs.ParseErrorsWhitelist.UnknownFlags = true fs.Usage = func() {} fs.SetInterspersed(false) - fs.BoolVarP(&remoteFromCLI.Value, "remote", "r", false, "") + fs.BoolVarP(&remoteFromCLI.Value, "remote", "r", remote, "") // The shell completion logic will call a command called "__complete" or "__completeNoDesc" // This command will always be the second argument diff --git a/cmd/podman/root.go b/cmd/podman/root.go index eb30f1ef6..6da34050e 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -314,7 +314,7 @@ func rootFlags(cmd *cobra.Command, opts *entities.PodmanConfig) { lFlags.StringVar(&opts.Identity, identityFlagName, ident, "path to SSH identity file, (CONTAINER_SSHKEY)") _ = cmd.RegisterFlagCompletionFunc(identityFlagName, completion.AutocompleteDefault) - lFlags.BoolVarP(&opts.Remote, "remote", "r", false, "Access remote Podman service (default false)") + lFlags.BoolVarP(&opts.Remote, "remote", "r", registry.IsRemote(), "Access remote Podman service") pFlags := cmd.PersistentFlags() if registry.IsRemote() { if err := lFlags.MarkHidden("remote"); err != nil { diff --git a/cmd/podman/system/connection/list.go b/cmd/podman/system/connection/list.go index de85ce3fa..a3290e3d6 100644 --- a/cmd/podman/system/connection/list.go +++ b/cmd/podman/system/connection/list.go @@ -44,6 +44,7 @@ func init() { type namedDestination struct { Name string config.Destination + Default bool } func list(cmd *cobra.Command, _ []string) error { @@ -60,12 +61,14 @@ func list(cmd *cobra.Command, _ []string) error { "Identity": "Identity", "Name": "Name", "URI": "URI", + "Default": "Default", }} rows := make([]namedDestination, 0) for k, v := range cfg.Engine.ServiceDestinations { + def := false if k == cfg.Engine.ActiveService { - k += "*" + def = true } r := namedDestination{ @@ -74,6 +77,7 @@ func list(cmd *cobra.Command, _ []string) error { Identity: v.Identity, URI: v.URI, }, + Default: def, } rows = append(rows, r) } @@ -82,7 +86,7 @@ func list(cmd *cobra.Command, _ []string) error { return rows[i].Name < rows[j].Name }) - format := "{{.Name}}\t{{.Identity}}\t{{.URI}}\n" + format := "{{.Name}}\t{{.URI}}\t{{.Identity}}\t{{.Default}}\n" switch { case report.IsJSON(cmd.Flag("format").Value.String()): buf, err := registry.JSONLibrary().MarshalIndent(rows, "", " ") diff --git a/cmd/podman/system/connection/remove.go b/cmd/podman/system/connection/remove.go index 73bae4994..ffbea76c5 100644 --- a/cmd/podman/system/connection/remove.go +++ b/cmd/podman/system/connection/remove.go @@ -5,14 +5,14 @@ import ( "github.com/containers/podman/v3/cmd/podman/common" "github.com/containers/podman/v3/cmd/podman/registry" "github.com/containers/podman/v3/cmd/podman/system" + "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( // Skip creating engines since this command will obtain connection information to said engines rmCmd = &cobra.Command{ - Use: "remove NAME", - Args: cobra.ExactArgs(1), + Use: "remove [options] NAME", Aliases: []string{"rm"}, Long: `Delete named destination from podman configuration`, Short: "Delete named destination", @@ -21,6 +21,10 @@ var ( Example: `podman system connection remove devl podman system connection rm devl`, } + + rmOpts = struct { + All bool + }{} ) func init() { @@ -28,14 +32,31 @@ func init() { Command: rmCmd, Parent: system.ConnectionCmd, }) + + flags := rmCmd.Flags() + flags.BoolVarP(&rmOpts.All, "all", "a", false, "Remove all connections") } -func rm(_ *cobra.Command, args []string) error { +func rm(cmd *cobra.Command, args []string) error { cfg, err := config.ReadCustomConfig() if err != nil { return err } + if rmOpts.All { + if cfg.Engine.ServiceDestinations != nil { + for k := range cfg.Engine.ServiceDestinations { + delete(cfg.Engine.ServiceDestinations, k) + } + } + cfg.Engine.ActiveService = "" + return cfg.Write() + } + + if len(args) != 1 { + return errors.New("accepts 1 arg(s), received 0") + } + if cfg.Engine.ServiceDestinations != nil { delete(cfg.Engine.ServiceDestinations, args[0]) } diff --git a/cmd/podman/system/dial_stdio.go b/cmd/podman/system/dial_stdio.go new file mode 100644 index 000000000..eae89f38e --- /dev/null +++ b/cmd/podman/system/dial_stdio.go @@ -0,0 +1,145 @@ +package system + +import ( + "context" + "io" + "os" + + "github.com/containers/podman/v3/cmd/podman/registry" + "github.com/containers/podman/v3/cmd/podman/validate" + "github.com/containers/podman/v3/pkg/bindings" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + dialStdioCommand = &cobra.Command{ + Use: "dial-stdio", + Short: "Proxy the stdio stream to the daemon connection. Should not be invoked manually.", + Args: validate.NoArgs, + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + return runDialStdio() + }, + Example: "podman system dial-stdio", + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: dialStdioCommand, + Parent: systemCmd, + }) +} + +func runDialStdio() error { + ctx := registry.Context() + cfg := registry.PodmanConfig() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + bindCtx, err := bindings.NewConnection(ctx, cfg.URI) + if err != nil { + return errors.Wrap(err, "failed to open connection to podman") + } + conn, err := bindings.GetClient(bindCtx) + if err != nil { + return errors.Wrap(err, "failed to get connection after initialization") + } + netConn, err := conn.GetDialer(bindCtx) + if err != nil { + return errors.Wrap(err, "failed to open the raw stream connection") + } + defer netConn.Close() + + var connHalfCloser halfCloser + switch t := netConn.(type) { + case halfCloser: + connHalfCloser = t + case halfReadWriteCloser: + connHalfCloser = &nopCloseReader{t} + default: + return errors.New("the raw stream connection does not implement halfCloser") + } + + stdin2conn := make(chan error, 1) + conn2stdout := make(chan error, 1) + go func() { + stdin2conn <- copier(connHalfCloser, &halfReadCloserWrapper{os.Stdin}, "stdin to stream") + }() + go func() { + conn2stdout <- copier(&halfWriteCloserWrapper{os.Stdout}, connHalfCloser, "stream to stdout") + }() + select { + case err = <-stdin2conn: + if err != nil { + return err + } + // wait for stdout + err = <-conn2stdout + case err = <-conn2stdout: + // return immediately + } + return err +} + +// Below portion taken from original docker CLI +// https://github.com/docker/cli/blob/v20.10.9/cli/command/system/dial_stdio.go +func copier(to halfWriteCloser, from halfReadCloser, debugDescription string) error { + defer func() { + if err := from.CloseRead(); err != nil { + logrus.Errorf("error while CloseRead (%s): %v", debugDescription, err) + } + if err := to.CloseWrite(); err != nil { + logrus.Errorf("error while CloseWrite (%s): %v", debugDescription, err) + } + }() + if _, err := io.Copy(to, from); err != nil { + return errors.Wrapf(err, "error while Copy (%s)", debugDescription) + } + return nil +} + +type halfReadCloser interface { + io.Reader + CloseRead() error +} + +type halfWriteCloser interface { + io.Writer + CloseWrite() error +} + +type halfCloser interface { + halfReadCloser + halfWriteCloser +} + +type halfReadWriteCloser interface { + io.Reader + halfWriteCloser +} + +type nopCloseReader struct { + halfReadWriteCloser +} + +func (x *nopCloseReader) CloseRead() error { + return nil +} + +type halfReadCloserWrapper struct { + io.ReadCloser +} + +func (x *halfReadCloserWrapper) CloseRead() error { + return x.Close() +} + +type halfWriteCloserWrapper struct { + io.WriteCloser +} + +func (x *halfWriteCloserWrapper) CloseWrite() error { + return x.Close() +} diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go index 0a4be6aea..b9bd7538f 100644 --- a/cmd/podman/system/service_abi.go +++ b/cmd/podman/system/service_abi.go @@ -93,7 +93,7 @@ func restService(flags *pflag.FlagSet, cfg *entities.PodmanConfig, opts entities return err } defer func() { - if err := server.Shutdown(false); err != nil { + if err := server.Shutdown(true); err != nil { logrus.Warnf("Error when stopping API service: %s", err) } }() diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go index 50230609e..9b777dd8f 100644 --- a/cmd/podman/system/unshare.go +++ b/cmd/podman/system/unshare.go @@ -10,6 +10,7 @@ import ( "github.com/containers/podman/v3/pkg/rootless" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -34,7 +35,14 @@ func init() { }) flags := unshareCommand.Flags() flags.SetInterspersed(false) - flags.BoolVar(&unshareOptions.RootlessCNI, "rootless-cni", false, "Join the rootless network namespace used for CNI networking") + flags.BoolVar(&unshareOptions.RootlessNetNS, "rootless-netns", false, "Join the rootless network namespace used for CNI and netavark networking") + // backwards compat still allow --rootless-cni + flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName { + if name == "rootless-cni" { + name = "rootless-netns" + } + return pflag.NormalizedName(name) + }) } func unshare(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go index 4502b156c..3443978d6 100644 --- a/cmd/podman/system/version.go +++ b/cmd/podman/system/version.go @@ -20,7 +20,7 @@ var ( versionCommand = &cobra.Command{ Use: "version [options]", Args: validate.NoArgs, - Short: "Display the Podman Version Information", + Short: "Display the Podman version information", RunE: version, ValidArgsFunction: completion.AutocompleteNone, } @@ -67,7 +67,7 @@ func version(cmd *cobra.Command, args []string) error { } if err := tmpl.Execute(w, versions); err != nil { // On Failure, assume user is using older version of podman version --format and check client - row = strings.Replace(row, ".Server.", ".", 1) + row = strings.ReplaceAll(row, ".Server.", ".") tmpl, err := report.NewTemplate("version 1.0.0").Parse(row) if err != nil { return err |