diff options
Diffstat (limited to 'cmd/podman')
51 files changed, 1757 insertions, 101 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 53f4a8fa2..7086dc839 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -156,8 +156,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet { createFlags.String("entrypoint", "", "Overwrite the default ENTRYPOINT of the image", ) - createFlags.StringArrayVarP( - &cf.env, + createFlags.StringArrayP( "env", "e", containerConfig.Env(), "Set environment variables in container", ) diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go index c275b1674..8b38e3b47 100644 --- a/cmd/podman/common/create_opts.go +++ b/cmd/podman/common/create_opts.go @@ -32,7 +32,7 @@ type ContainerCLIOpts struct { DeviceWriteBPs []string DeviceWriteIOPs []string Entrypoint *string - env []string + Env []string EnvHost bool EnvFile []string Expose []string diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go index 33cba30cd..9a2345064 100644 --- a/cmd/podman/common/specgen.go +++ b/cmd/podman/common/specgen.go @@ -26,6 +26,16 @@ func getCPULimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) cpu := &specs.LinuxCPU{} hasLimits := false + const cpuPeriod = 100000 + + if c.CPUS > 0 { + quota := int64(c.CPUS * cpuPeriod) + period := uint64(cpuPeriod) + + cpu.Period = &period + cpu.Quota = "a + hasLimits = true + } if c.CPUShares > 0 { cpu.Shares = &c.CPUShares hasLimits = true @@ -142,6 +152,10 @@ func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []strin return nil, errors.Wrapf(err, "invalid value for memory") } memory.Limit = &ml + if c.MemorySwap == "" { + limit := 2 * ml + memory.Swap = &(limit) + } hasLimits = true } if m := c.MemoryReservation; len(m) > 0 { @@ -192,7 +206,6 @@ func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []strin func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error { var ( err error - // namespaces map[string]string ) // validate flags as needed @@ -234,9 +247,15 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string // We are not handling the Expose flag yet. // s.PortsExpose = c.Expose s.PortMappings = c.Net.PublishPorts - s.PublishImagePorts = c.PublishAll + s.PublishExposedPorts = c.PublishAll s.Pod = c.Pod + expose, err := createExpose(c.Expose) + if err != nil { + return err + } + s.Expose = expose + for k, v := range map[string]*specgen.Namespace{ c.IPC: &s.IpcNS, c.PID: &s.PidNS, @@ -316,15 +335,12 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string env = envLib.Join(env, fileEnv) } - // env overrides any previous variables - if cmdLineEnv := c.env; len(cmdLineEnv) > 0 { - parsedEnv, err := envLib.ParseSlice(cmdLineEnv) - if err != nil { - return err - } - env = envLib.Join(env, parsedEnv) + parsedEnv, err := envLib.ParseSlice(c.Env) + if err != nil { + return err } - s.Env = env + + s.Env = envLib.Join(env, parsedEnv) // LABEL VARIABLES labels, err := parse.GetAllLabels(c.LabelFile, c.Label) @@ -515,10 +531,13 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string case "label": // TODO selinux opts and label opts are the same thing s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1]) + s.Annotations[define.InspectAnnotationLabel] = con[1] case "apparmor": s.ContainerSecurityConfig.ApparmorProfile = con[1] + s.Annotations[define.InspectAnnotationApparmor] = con[1] case "seccomp": s.SeccompProfilePath = con[1] + s.Annotations[define.InspectAnnotationSeccomp] = con[1] default: return fmt.Errorf("invalid --security-opt 2: %q", opt) } @@ -601,7 +620,29 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string s.Name = c.Name s.OOMScoreAdj = &c.OOMScoreAdj - s.RestartPolicy = c.Restart + if c.Restart != "" { + splitRestart := strings.Split(c.Restart, ":") + switch len(splitRestart) { + case 1: + // No retries specified + case 2: + if strings.ToLower(splitRestart[0]) != "on-failure" { + return errors.Errorf("restart policy retries can only be specified with on-failure restart policy") + } + retries, err := strconv.Atoi(splitRestart[1]) + if err != nil { + return errors.Wrapf(err, "error parsing restart policy retry count") + } + if retries < 0 { + return errors.Errorf("must specify restart policy retry count as a number greater than 0") + } + var retriesUint uint = uint(retries) + s.RestartRetries = &retriesUint + default: + return errors.Errorf("invalid restart policy: may specify retries at most once") + } + s.RestartPolicy = splitRestart[0] + } s.Remove = c.Rm s.StopTimeout = &c.StopTimeout diff --git a/cmd/podman/common/types.go b/cmd/podman/common/types.go deleted file mode 100644 index 2427ae975..000000000 --- a/cmd/podman/common/types.go +++ /dev/null @@ -1,3 +0,0 @@ -package common - -var DefaultKernelNamespaces = "cgroup,ipc,net,uts" diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go index 47bbe12fa..a3626b4e4 100644 --- a/cmd/podman/common/util.go +++ b/cmd/podman/common/util.go @@ -1,43 +1,201 @@ package common import ( + "net" "strconv" + "strings" - "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/docker/go-connections/nat" + "github.com/containers/libpod/pkg/specgen" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) -// createPortBindings iterates ports mappings and exposed ports into a format CNI understands -func createPortBindings(ports []string) ([]ocicni.PortMapping, error) { - // TODO wants someone to rewrite this code in the future - var portBindings []ocicni.PortMapping - // The conversion from []string to natBindings is temporary while mheon reworks the port - // deduplication code. Eventually that step will not be required. - _, natBindings, err := nat.ParsePortSpecs(ports) - if err != nil { - return nil, err - } - for containerPb, hostPb := range natBindings { - var pm ocicni.PortMapping - pm.ContainerPort = int32(containerPb.Int()) - for _, i := range hostPb { - var hostPort int - var err error - pm.HostIP = i.HostIP - if i.HostPort == "" { - hostPort = containerPb.Int() +// createExpose parses user-provided exposed port definitions and converts them +// into SpecGen format. +// TODO: The SpecGen format should really handle ranges more sanely - we could +// be massively inflating what is sent over the wire with a large range. +func createExpose(expose []string) (map[uint16]string, error) { + toReturn := make(map[uint16]string) + + for _, e := range expose { + // Check for protocol + proto := "tcp" + splitProto := strings.Split(e, "/") + if len(splitProto) > 2 { + return nil, errors.Errorf("invalid expose format - protocol can only be specified once") + } else if len(splitProto) == 2 { + proto = splitProto[1] + } + + // Check for a range + start, len, err := parseAndValidateRange(splitProto[0]) + if err != nil { + return nil, err + } + + var index uint16 + for index = 0; index < len; index++ { + portNum := start + index + protocols, ok := toReturn[portNum] + if !ok { + toReturn[portNum] = proto } else { - hostPort, err = strconv.Atoi(i.HostPort) - if err != nil { - return nil, errors.Wrapf(err, "unable to convert host port to integer") - } + newProto := strings.Join(append(strings.Split(protocols, ","), strings.Split(proto, ",")...), ",") + toReturn[portNum] = newProto } + } + } + + return toReturn, nil +} + +// createPortBindings iterates ports mappings into SpecGen format. +func createPortBindings(ports []string) ([]specgen.PortMapping, error) { + // --publish is formatted as follows: + // [[hostip:]hostport[-endPort]:]containerport[-endPort][/protocol] + toReturn := make([]specgen.PortMapping, 0, len(ports)) + + for _, p := range ports { + var ( + ctrPort string + proto, hostIP, hostPort *string + ) + + splitProto := strings.Split(p, "/") + switch len(splitProto) { + case 1: + // No protocol was provided + case 2: + proto = &(splitProto[1]) + default: + return nil, errors.Errorf("invalid port format - protocol can only be specified once") + } - pm.HostPort = int32(hostPort) - pm.Protocol = containerPb.Proto() - portBindings = append(portBindings, pm) + splitPort := strings.Split(splitProto[0], ":") + switch len(splitPort) { + case 1: + ctrPort = splitPort[0] + case 2: + hostPort = &(splitPort[0]) + ctrPort = splitPort[1] + case 3: + hostIP = &(splitPort[0]) + hostPort = &(splitPort[1]) + ctrPort = splitPort[2] + default: + return nil, errors.Errorf("invalid port format - format is [[hostIP:]hostPort:]containerPort") + } + + newPort, err := parseSplitPort(hostIP, hostPort, ctrPort, proto) + if err != nil { + return nil, err + } + + toReturn = append(toReturn, newPort) + } + + return toReturn, nil +} + +// parseSplitPort parses individual components of the --publish flag to produce +// a single port mapping in SpecGen format. +func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) (specgen.PortMapping, error) { + newPort := specgen.PortMapping{} + if ctrPort == "" { + return newPort, errors.Errorf("must provide a non-empty container port to publish") + } + ctrStart, ctrLen, err := parseAndValidateRange(ctrPort) + if err != nil { + return newPort, errors.Wrapf(err, "error parsing container port") + } + newPort.ContainerPort = ctrStart + newPort.Range = ctrLen + + if protocol != nil { + if *protocol == "" { + return newPort, errors.Errorf("must provide a non-empty protocol to publish") + } + newPort.Protocol = *protocol + } + if hostIP != nil { + if *hostIP == "" { + return newPort, errors.Errorf("must provide a non-empty container host IP to publish") } + testIP := net.ParseIP(*hostIP) + if testIP == nil { + return newPort, errors.Errorf("cannot parse %q as an IP address", *hostIP) + } + newPort.HostIP = testIP.String() + } + if hostPort != nil { + if *hostPort == "" { + return newPort, errors.Errorf("must provide a non-empty container host port to publish") + } + hostStart, hostLen, err := parseAndValidateRange(*hostPort) + if err != nil { + return newPort, errors.Wrapf(err, "error parsing host port") + } + if hostLen != ctrLen { + return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen) + } + newPort.HostPort = hostStart + } + + hport := newPort.HostPort + if hport == 0 { + hport = newPort.ContainerPort + } + logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol) + + return newPort, nil +} + +// Parse and validate a port range. +// Returns start port, length of range, error. +func parseAndValidateRange(portRange string) (uint16, uint16, error) { + splitRange := strings.Split(portRange, "-") + if len(splitRange) > 2 { + return 0, 0, errors.Errorf("invalid port format - port ranges are formatted as startPort-stopPort") + } + + if splitRange[0] == "" { + return 0, 0, errors.Errorf("port numbers cannot be negative") + } + + startPort, err := parseAndValidatePort(splitRange[0]) + if err != nil { + return 0, 0, err + } + + var rangeLen uint16 = 1 + if len(splitRange) == 2 { + if splitRange[1] == "" { + return 0, 0, errors.Errorf("must provide ending number for port range") + } + endPort, err := parseAndValidatePort(splitRange[1]) + if err != nil { + return 0, 0, err + } + if endPort <= startPort { + return 0, 0, errors.Errorf("the end port of a range must be higher than the start port - %d is not higher than %d", endPort, startPort) + } + // Our range is the total number of ports + // involved, so we need to add 1 (8080:8081 is + // 2 ports, for example, not 1) + rangeLen = endPort - startPort + 1 + } + + return startPort, rangeLen, nil +} + +// Turn a single string into a valid U16 port. +func parseAndValidatePort(port string) (uint16, error) { + num, err := strconv.Atoi(port) + if err != nil { + return 0, errors.Wrapf(err, "cannot parse %q as a port number", port) + } + if num < 1 || num > 65535 { + return 0, errors.Errorf("port numbers must be between 1 and 65535 (inclusive), got %d", num) } - return portBindings, nil + return uint16(num), nil } diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 7927da04d..2ecdda2e0 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -55,6 +55,11 @@ func createFlags(flags *pflag.FlagSet) { flags.AddFlagSet(common.GetCreateFlags(&cliVals)) flags.AddFlagSet(common.GetNetFlags()) flags.SetNormalizeFunc(common.AliasFlags) + if registry.IsRemote() { + _ = flags.MarkHidden("authfile") + _ = flags.MarkHidden("env-host") + _ = flags.MarkHidden("http-proxy") + } } func init() { @@ -170,6 +175,13 @@ func createInit(c *cobra.Command) error { val := c.Flag("entrypoint").Value.String() cliVals.Entrypoint = &val } + if c.Flags().Changed("env") { + env, err := c.Flags().GetStringArray("env") + if err != nil { + return errors.Wrapf(err, "retrieve env flag") + } + cliVals.Env = env + } // Docker-compatibility: the "-h" flag for run/create is reserved for // the hostname (see https://github.com/containers/libpod/issues/1367). diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go index 5dec71fdd..2b8c3ed5f 100644 --- a/cmd/podman/containers/logs.go +++ b/cmd/podman/containers/logs.go @@ -27,7 +27,7 @@ var ( ` logsCommand = &cobra.Command{ Use: "logs [flags] CONTAINER [CONTAINER...]", - Short: "Fetch the logs of one or more container", + Short: "Fetch the logs of one or more containers", Long: logsDescription, RunE: logs, Example: `podman logs ctrID diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index 2e3386aa9..ec0ddf838 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -11,6 +11,7 @@ import ( "github.com/cri-o/ocicni/pkg/ocicni" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -28,23 +29,50 @@ var ( podman port ctrID 80/tcp podman port --latest 80`, } + + containerPortCommand = &cobra.Command{ + Use: "port [flags] CONTAINER [PORT]", + Short: portCommand.Short, + Long: portDescription, + RunE: portCommand.RunE, + Args: func(cmd *cobra.Command, args []string) error { + return parse.CheckAllLatestAndCIDFile(cmd, args, true, false) + }, + Example: `podman container port --all + podman container port --latest 80`, + } ) var ( portOpts entities.ContainerPortOptions ) +func portFlags(flags *pflag.FlagSet) { + flags.BoolVarP(&portOpts.All, "all", "a", false, "Display port information for all containers") + flags.BoolVarP(&portOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode}, Command: portCommand, }) + flags := portCommand.Flags() - flags.BoolVarP(&portOpts.All, "all", "a", false, "Display port information for all containers") - flags.BoolVarP(&portOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - if registry.IsRemote() { - _ = flags.MarkHidden("latest") - } + portFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerPortCommand, + Parent: containerCmd, + }) + + containerPortflags := containerPortCommand.Flags() + portFlags(containerPortflags) + } func port(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/prune.go b/cmd/podman/containers/prune.go index d4bea48f9..38168a6e4 100644 --- a/cmd/podman/containers/prune.go +++ b/cmd/podman/containers/prune.go @@ -18,10 +18,10 @@ import ( var ( pruneDescription = fmt.Sprintf(`podman container prune - Removes all stopped | exited containers`) + Removes all non running containers`) pruneCommand = &cobra.Command{ Use: "prune [flags]", - Short: "Remove all stopped | exited containers", + Short: "Remove all non running containers", Long: pruneDescription, RunE: prune, Example: `podman container prune`, @@ -50,7 +50,7 @@ func prune(cmd *cobra.Command, args []string) error { } if !force { reader := bufio.NewReader(os.Stdin) - fmt.Println("WARNING! This will remove all stopped containers.") + fmt.Println("WARNING! This will remove all non running containers.") fmt.Print("Are you sure you want to continue? [y/N] ") answer, err := reader.ReadString('\n') if err != nil { diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go index e9146bda7..4d12d2534 100644 --- a/cmd/podman/containers/ps.go +++ b/cmd/podman/containers/ps.go @@ -206,7 +206,7 @@ func ps(cmd *cobra.Command, args []string) error { return err } if err := tmpl.Execute(w, responses); err != nil { - return nil + return err } if err := w.Flush(); err != nil { return err diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go index 96549cead..2a0f9cc6a 100644 --- a/cmd/podman/containers/rm.go +++ b/cmd/podman/containers/rm.go @@ -35,7 +35,7 @@ var ( containerRmCommand = &cobra.Command{ Use: rmCommand.Use, - Short: rmCommand.Use, + Short: rmCommand.Short, Long: rmCommand.Long, RunE: rmCommand.RunE, Args: func(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index b13983e37..5f3ea9ef4 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -60,6 +60,8 @@ func runFlags(flags *pflag.FlagSet) { flags.BoolVar(&runRmi, "rmi", false, "Remove container image unless used by other containers") if registry.IsRemote() { _ = flags.MarkHidden("authfile") + _ = flags.MarkHidden("env-host") + _ = flags.MarkHidden("http-proxy") } } func init() { @@ -170,9 +172,9 @@ func run(cmd *cobra.Command, args []string) error { return nil } if runRmi { - _, err := registry.ImageEngine().Remove(registry.GetContext(), []string{args[0]}, entities.ImageRemoveOptions{}) - if err != nil { - logrus.Errorf("%s", errors.Wrapf(err, "failed removing image")) + _, rmErrors := registry.ImageEngine().Remove(registry.GetContext(), []string{args[0]}, entities.ImageRemoveOptions{}) + if len(rmErrors) > 0 { + logrus.Errorf("%s", errors.Wrapf(errorhandling.JoinErrors(rmErrors), "failed removing image")) } } return nil diff --git a/cmd/podman/containers/stats.go b/cmd/podman/containers/stats.go new file mode 100644 index 000000000..3f9db671f --- /dev/null +++ b/cmd/podman/containers/stats.go @@ -0,0 +1,244 @@ +package containers + +import ( + "fmt" + "os" + "strings" + "text/tabwriter" + "text/template" + + tm "github.com/buger/goterm" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/utils" + "github.com/docker/go-units" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +var ( + statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers." + statsCommand = &cobra.Command{ + Use: "stats [flags] [CONTAINER...]", + Short: "Display a live stream of container resource usage statistics", + Long: statsDescription, + RunE: stats, + Args: checkStatOptions, + Example: `podman stats --all --no-stream + podman stats ctrID + podman stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, + } + + containerStatsCommand = &cobra.Command{ + Use: statsCommand.Use, + Short: statsCommand.Short, + Long: statsCommand.Long, + RunE: statsCommand.RunE, + Args: checkStatOptions, + Example: `podman container stats --all --no-stream + podman container stats ctrID + podman container stats --no-stream --format "table {{.ID}} {{.Name}} {{.MemUsage}}" ctrID`, + } +) + +var ( + statsOptions entities.ContainerStatsOptions + defaultStatsRow = "{{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}\n" + defaultStatsHeader = "ID\tNAME\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET IO\tBLOCK IO\tPIDS\n" +) + +func statFlags(flags *pflag.FlagSet) { + flags.BoolVarP(&statsOptions.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false") + flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template") + flags.BoolVarP(&statsOptions.Latest, "latest", "l", false, "Act on the latest container Podman is aware of") + flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen between intervals") + flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result, default setting is false") + if registry.IsRemote() { + _ = flags.MarkHidden("latest") + } +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: statsCommand, + }) + flags := statsCommand.Flags() + statFlags(flags) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: containerStatsCommand, + Parent: containerCmd, + }) + + containerStatsFlags := containerStatsCommand.Flags() + statFlags(containerStatsFlags) +} + +// stats is different in that it will assume running containers if +// no input is given, so we need to validate differently +func checkStatOptions(cmd *cobra.Command, args []string) error { + opts := 0 + if statsOptions.All { + opts += 1 + } + if statsOptions.Latest { + opts += 1 + } + if len(args) > 0 { + opts += 1 + } + if opts > 1 { + return errors.Errorf("--all, --latest and containers cannot be used together") + } + return nil +} + +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") + } + } + statsOptions.StatChan = make(chan []*define.ContainerStats, 1) + go func() { + for reports := range statsOptions.StatChan { + if err := outputStats(reports); err != nil { + logrus.Error(err) + } + } + }() + return registry.ContainerEngine().ContainerStats(registry.Context(), args, statsOptions) +} + +func outputStats(reports []*define.ContainerStats) error { + if len(statsOptions.Format) < 1 && !statsOptions.NoReset { + tm.Clear() + tm.MoveCursor(1, 1) + tm.Flush() + } + var stats []*containerStats + for _, r := range reports { + stats = append(stats, &containerStats{r}) + } + if statsOptions.Format == "json" { + return outputJSON(stats) + } + format := defaultStatsRow + if len(statsOptions.Format) > 0 { + format = statsOptions.Format + if !strings.HasSuffix(format, "\n") { + format += "\n" + } + } + format = "{{range . }}" + format + "{{end}}" + if len(statsOptions.Format) < 1 { + format = defaultStatsHeader + format + } + tmpl, err := template.New("stats").Parse(format) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + if err := tmpl.Execute(w, stats); err != nil { + return err + } + if err := w.Flush(); err != nil { + return err + } + return nil +} + +type containerStats struct { + *define.ContainerStats +} + +func (s *containerStats) ID() string { + return s.ContainerID[0:12] +} + +func (s *containerStats) CPUPerc() string { + return floatToPercentString(s.CPU) +} + +func (s *containerStats) MemPerc() string { + return floatToPercentString(s.ContainerStats.MemPerc) +} + +func (s *containerStats) NetIO() string { + return combineHumanValues(s.NetInput, s.NetOutput) +} + +func (s *containerStats) BlockIO() string { + return combineHumanValues(s.BlockInput, s.BlockOutput) +} + +func (s *containerStats) PIDS() string { + if s.PIDs == 0 { + // If things go bazinga, return a safe value + return "--" + } + return fmt.Sprintf("%d", s.PIDs) +} +func (s *containerStats) MemUsage() string { + return combineHumanValues(s.ContainerStats.MemUsage, s.ContainerStats.MemLimit) +} + +func floatToPercentString(f float64) string { + strippedFloat, err := utils.RemoveScientificNotationFromFloat(f) + if err != nil || strippedFloat == 0 { + // If things go bazinga, return a safe value + return "--" + } + return fmt.Sprintf("%.2f", strippedFloat) + "%" +} + +func combineHumanValues(a, b uint64) string { + if a == 0 && b == 0 { + return "-- / --" + } + return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b))) +} + +func outputJSON(stats []*containerStats) error { + type jstat struct { + Id string `json:"id"` + Name string `json:"name"` + CpuPercent string `json:"cpu_percent"` + MemUsage string `json:"mem_usage"` + MemPerc string `json:"mem_percent"` + NetIO string `json:"net_io"` + BlockIO string `json:"block_io"` + Pids string `json:"pids"` + } + var jstats []jstat + for _, j := range stats { + jstats = append(jstats, jstat{ + Id: j.ID(), + Name: j.Name, + CpuPercent: j.CPUPerc(), + MemUsage: j.MemPerc(), + MemPerc: j.MemUsage(), + NetIO: j.NetIO(), + BlockIO: j.BlockIO(), + Pids: j.PIDS(), + }) + } + + b, err := json.MarshalIndent(jstats, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil +} diff --git a/cmd/podman/containers/unpause.go b/cmd/podman/containers/unpause.go index adf8d12ee..7ea8e13c1 100644 --- a/cmd/podman/containers/unpause.go +++ b/cmd/podman/containers/unpause.go @@ -49,7 +49,7 @@ func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, - Command: unpauseCommand, + Command: containerUnpauseCommand, Parent: containerCmd, }) diff --git a/cmd/podman/generate/generate.go b/cmd/podman/generate/generate.go index b112e666a..7803c0c78 100644 --- a/cmd/podman/generate/generate.go +++ b/cmd/podman/generate/generate.go @@ -22,7 +22,7 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode}, + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: generateCmd, }) } diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go new file mode 100644 index 000000000..86a9cc686 --- /dev/null +++ b/cmd/podman/generate/kube.go @@ -0,0 +1,68 @@ +package pods + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + kubeOptions = entities.GenerateKubeOptions{} + kubeFile = "" + kubeDescription = `Command generates Kubernetes pod and service YAML (v1 specification) from a Podman container or pod. + +Whether the input is for a container or pod, Podman will always generate the specification as a pod.` + + kubeCmd = &cobra.Command{ + Use: "kube [flags] CONTAINER | POD", + Short: "Generate Kubernetes YAML from a container or pod.", + Long: kubeDescription, + RunE: kube, + Args: cobra.ExactArgs(1), + Example: `podman generate kube ctrID + podman generate kube podID + podman generate kube --service podID`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: kubeCmd, + Parent: generateCmd, + }) + flags := kubeCmd.Flags() + flags.BoolVarP(&kubeOptions.Service, "service", "s", false, "Generate YAML for a Kubernetes service object") + flags.StringVarP(&kubeFile, "filename", "f", "", "Write output to the specified path") + flags.SetNormalizeFunc(utils.AliasFlags) +} + +func kube(cmd *cobra.Command, args []string) error { + report, err := registry.ContainerEngine().GenerateKube(registry.GetContext(), args[0], kubeOptions) + if err != nil { + return err + } + + content, err := ioutil.ReadAll(report.Reader) + if err != nil { + return err + } + if cmd.Flags().Changed("filename") { + if _, err := os.Stat(kubeFile); err == nil { + return errors.Errorf("cannot write to %q", kubeFile) + } + if err := ioutil.WriteFile(kubeFile, content, 0644); err != nil { + return errors.Wrapf(err, "cannot write to %q", kubeFile) + } + return nil + } + + fmt.Println(string(content)) + return nil +} diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go index 55d770249..20d9748d4 100644 --- a/cmd/podman/generate/systemd.go +++ b/cmd/podman/generate/systemd.go @@ -29,7 +29,7 @@ var ( func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ - Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Mode: []entities.EngineMode{entities.ABIMode}, Command: systemdCmd, Parent: generateCmd, }) diff --git a/cmd/podman/healthcheck/healthcheck.go b/cmd/podman/healthcheck/healthcheck.go index ce90dba31..f48701624 100644 --- a/cmd/podman/healthcheck/healthcheck.go +++ b/cmd/podman/healthcheck/healthcheck.go @@ -11,8 +11,8 @@ var ( // Command: healthcheck healthCmd = &cobra.Command{ Use: "healthcheck", - Short: "Manage Healthcheck", - Long: "Manage Healthcheck", + Short: "Manage health checks on containers", + Long: "Run health checks on containers", TraverseChildren: true, RunE: validate.SubCommandExists, } diff --git a/cmd/podman/build.go b/cmd/podman/images/build.go index 43a2f7ab5..06a7efd25 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/images/build.go @@ -1,4 +1,4 @@ -package main +package images import ( "os" @@ -17,6 +17,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) // buildFlagsWrapper are local to cmd/ as the build code is using Buildah-internal @@ -48,6 +49,17 @@ var ( podman build --layers --force-rm --tag imageName .`, } + imageBuildCmd = &cobra.Command{ + Args: buildCmd.Args, + Use: buildCmd.Use, + Short: buildCmd.Short, + Long: buildCmd.Long, + RunE: buildCmd.RunE, + Example: `podman image build . + podman image build --creds=username:password -t imageName -f Containerfile.simple . + podman image build --layers --force-rm --tag imageName .`, + } + buildOpts = buildFlagsWrapper{} ) @@ -66,8 +78,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: buildCmd, }) - flags := buildCmd.Flags() + buildFlags(buildCmd.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageBuildCmd, + Parent: imageCmd, + }) + buildFlags(imageBuildCmd.Flags()) +} +func buildFlags(flags *pflag.FlagSet) { // Podman flags flags.BoolVarP(&buildOpts.SquashAll, "squash-all", "", false, "Squash all layers into a single layer") diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go index 7cfacfc6c..c24f98369 100644 --- a/cmd/podman/images/diff.go +++ b/cmd/podman/images/diff.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -28,9 +29,11 @@ func init() { Command: diffCmd, Parent: imageCmd, }) + diffFlags(diffCmd.Flags()) +} +func diffFlags(flags *pflag.FlagSet) { diffOpts = &entities.DiffOptions{} - flags := diffCmd.Flags() flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive") _ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.") flags.StringVar(&diffOpts.Format, "format", "", "Change the output format") diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go index ce153aa46..17a80557e 100644 --- a/cmd/podman/images/history.go +++ b/cmd/podman/images/history.go @@ -15,6 +15,7 @@ import ( "github.com/docker/go-units" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -32,6 +33,15 @@ var ( RunE: history, } + imageHistoryCmd = &cobra.Command{ + Args: historyCmd.Args, + Use: historyCmd.Use, + Short: historyCmd.Short, + Long: historyCmd.Long, + RunE: historyCmd.RunE, + Example: `podman image history imageID`, + } + opts = struct { human bool noTrunc bool @@ -45,8 +55,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: historyCmd, }) + historyFlags(historyCmd.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageHistoryCmd, + Parent: imageCmd, + }) + historyFlags(imageHistoryCmd.Flags()) +} - flags := historyCmd.Flags() +func historyFlags(flags *pflag.FlagSet) { flags.StringVar(&opts.format, "format", "", "Change the output to JSON or a Go template") flags.BoolVarP(&opts.human, "human", "H", true, "Display sizes and dates in human readable format") flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output") diff --git a/cmd/podman/images/import.go b/cmd/podman/images/import.go index 1c0568762..0e16128ce 100644 --- a/cmd/podman/images/import.go +++ b/cmd/podman/images/import.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -26,6 +27,17 @@ var ( cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported cat ctr.tar | podman import -`, } + + imageImportCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), + Use: importCommand.Use, + Short: importCommand.Short, + Long: importCommand.Long, + RunE: importCommand.RunE, + Example: `podman image import http://example.com/ctr.tar url-image + cat ctr.tar | podman -q image import --message "importing the ctr.tar tarball" - image-imported + cat ctr.tar | podman image import -`, + } ) var ( @@ -37,8 +49,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: importCommand, }) + importFlags(importCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageImportCommand, + Parent: imageCmd, + }) + importFlags(imageImportCommand.Flags()) +} - flags := importCommand.Flags() +func importFlags(flags *pflag.FlagSet) { flags.StringArrayVarP(&importOpts.Changes, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") flags.StringVarP(&importOpts.Message, "message", "m", "", "Set commit message for imported image") flags.BoolVarP(&importOpts.Quiet, "quiet", "q", false, "Suppress output") diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go index f49f95002..4bbffd432 100644 --- a/cmd/podman/images/load.go +++ b/cmd/podman/images/load.go @@ -15,6 +15,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -27,6 +28,14 @@ var ( RunE: load, Args: cobra.MaximumNArgs(1), } + + imageLoadCommand = &cobra.Command{ + Args: cobra.MinimumNArgs(1), + Use: loadCommand.Use, + Short: loadCommand.Short, + Long: loadCommand.Long, + RunE: loadCommand.RunE, + } ) var ( @@ -38,8 +47,16 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: loadCommand, }) + loadFlags(loadCommand.Flags()) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageLoadCommand, + Parent: imageCmd, + }) + loadFlags(imageLoadCommand.Flags()) +} - flags := loadCommand.Flags() +func loadFlags(flags *pflag.FlagSet) { flags.StringVarP(&loadOpts.Input, "input", "i", "", "Read from specified archive file (default: stdin)") flags.BoolVarP(&loadOpts.Quiet, "quiet", "q", false, "Suppress the output") flags.StringVar(&loadOpts.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file") @@ -61,7 +78,6 @@ func load(cmd *cobra.Command, args []string) error { loadOpts.Tag = "latest" } if r, ok := ref.(reference.Named); ok { - fmt.Println(r.Name()) loadOpts.Name = r.Name() } } diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go index 1cf5fa365..4b9920532 100644 --- a/cmd/podman/images/rm.go +++ b/cmd/podman/images/rm.go @@ -5,6 +5,7 @@ import ( "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/errorhandling" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -48,7 +49,9 @@ func rm(cmd *cobra.Command, args []string) error { return errors.Errorf("when using the --all switch, you may not pass any images names or IDs") } - report, err := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts) + // Note: certain image-removal errors are non-fatal. Hence, the report + // might be set even if err != nil. + report, rmErrors := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts) if report != nil { for _, u := range report.Untagged { fmt.Println("Untagged: " + u) @@ -62,5 +65,5 @@ func rm(cmd *cobra.Command, args []string) error { registry.SetExitCode(report.ExitCode) } - return err + return errorhandling.JoinErrors(rmErrors) } diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 8f7832074..56953e41c 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/pflag" "golang.org/x/crypto/ssh/terminal" ) @@ -43,6 +44,16 @@ var ( podman save --format docker-dir -o ubuntu-dir ubuntu podman save > alpine-all.tar alpine:latest`, } + imageSaveCommand = &cobra.Command{ + Args: saveCommand.Args, + Use: saveCommand.Use, + Short: saveCommand.Short, + Long: saveCommand.Long, + RunE: saveCommand.RunE, + Example: `podman image save --quiet -o myimage.tar imageID + podman image save --format docker-dir -o ubuntu-dir ubuntu + podman image save > alpine-all.tar alpine:latest`, + } ) var ( @@ -54,7 +65,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: saveCommand, }) - flags := saveCommand.Flags() + saveFlags(saveCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageSaveCommand, + Parent: imageCmd, + }) + saveFlags(imageSaveCommand.Flags()) +} + +func saveFlags(flags *pflag.FlagSet) { flags.BoolVar(&saveOpts.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") flags.StringVar(&saveOpts.Format, "format", define.V2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") flags.StringVarP(&saveOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") diff --git a/cmd/podman/images/tag.go b/cmd/podman/images/tag.go index 411313a9b..dae3416c4 100644 --- a/cmd/podman/images/tag.go +++ b/cmd/podman/images/tag.go @@ -18,6 +18,17 @@ var ( podman tag imageID:latest myNewImage:newTag podman tag httpd myregistryhost:5000/fedora/httpd:v2`, } + + imageTagCommand = &cobra.Command{ + Args: tagCommand.Args, + Use: tagCommand.Use, + Short: tagCommand.Short, + Long: tagCommand.Long, + RunE: tagCommand.RunE, + Example: `podman image tag 0e3bbc2 fedora:latest + podman image tag imageID:latest myNewImage:newTag + podman image tag httpd myregistryhost:5000/fedora/httpd:v2`, + } ) func init() { @@ -25,6 +36,11 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: tagCommand, }) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageTagCommand, + Parent: imageCmd, + }) } func tag(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/images/trust.go b/cmd/podman/images/trust.go new file mode 100644 index 000000000..88a567871 --- /dev/null +++ b/cmd/podman/images/trust.go @@ -0,0 +1,27 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + trustDescription = `Manages which registries you trust as a source of container images based on their location. + The location is determined by the transport and the registry host of the image. Using this container image docker://quay.io/podman/stable as an example, docker is the transport and quay.io is the registry host.` + trustCmd = &cobra.Command{ + Use: "trust", + Short: "Manage container image trust policy", + Long: trustDescription, + RunE: validate.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: trustCmd, + Parent: imageCmd, + }) +} diff --git a/cmd/podman/images/trust_set.go b/cmd/podman/images/trust_set.go new file mode 100644 index 000000000..5868f5546 --- /dev/null +++ b/cmd/podman/images/trust_set.go @@ -0,0 +1,56 @@ +package images + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + setTrustDescription = "Set default trust policy or add a new trust policy for a registry" + setTrustCommand = &cobra.Command{ + Use: "set [flags] REGISTRY", + Short: "Set default trust policy or a new trust policy for a registry", + Long: setTrustDescription, + Example: "", + RunE: setTrust, + Args: cobra.ExactArgs(1), + } +) + +var ( + setOptions entities.SetTrustOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: setTrustCommand, + Parent: trustCmd, + }) + setFlags := setTrustCommand.Flags() + setFlags.StringVar(&setOptions.PolicyPath, "policypath", "", "") + _ = setFlags.MarkHidden("policypath") + setFlags.StringSliceVarP(&setOptions.PubKeysFile, "pubkeysfile", "f", []string{}, `Path of installed public key(s) to trust for TARGET. +Absolute path to keys is added to policy.json. May +used multiple times to define multiple public keys. +File(s) must exist before using this command`) + setFlags.StringVarP(&setOptions.Type, "type", "t", "signedBy", "Trust type, accept values: signedBy(default), accept, reject") +} + +func setTrust(cmd *cobra.Command, args []string) error { + validTrustTypes := []string{"accept", "insecureAcceptAnything", "reject", "signedBy"} + + valid, err := image.IsValidImageURI(args[0]) + if err != nil || !valid { + return errors.Wrapf(err, "invalid image uri %s", args[0]) + } + + if !util.StringInSlice(setOptions.Type, validTrustTypes) { + return errors.Errorf("invalid choice: %s (choose from 'accept', 'reject', 'signedBy')", setOptions.Type) + } + return registry.ImageEngine().SetTrust(registry.Context(), args, setOptions) +} diff --git a/cmd/podman/images/trust_show.go b/cmd/podman/images/trust_show.go new file mode 100644 index 000000000..23ee6c709 --- /dev/null +++ b/cmd/podman/images/trust_show.go @@ -0,0 +1,77 @@ +package images + +import ( + "fmt" + "os" + "text/tabwriter" + "text/template" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + showTrustDescription = "Display trust policy for the system" + showTrustCommand = &cobra.Command{ + Use: "show [flags] [REGISTRY]", + Short: "Display trust policy for the system", + Long: showTrustDescription, + RunE: showTrust, + Example: "", + } +) + +var ( + showTrustOptions entities.ShowTrustOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: showTrustCommand, + Parent: trustCmd, + }) + showFlags := showTrustCommand.Flags() + showFlags.BoolVarP(&showTrustOptions.JSON, "json", "j", false, "Output as json") + showFlags.StringVar(&showTrustOptions.PolicyPath, "policypath", "", "") + showFlags.BoolVar(&showTrustOptions.Raw, "raw", false, "Output raw policy file") + _ = showFlags.MarkHidden("policypath") + showFlags.StringVar(&showTrustOptions.RegistryPath, "registrypath", "", "") + _ = showFlags.MarkHidden("registrypath") + +} + +func showTrust(cmd *cobra.Command, args []string) error { + report, err := registry.ImageEngine().ShowTrust(registry.Context(), args, showTrustOptions) + if err != nil { + return err + } + if showTrustOptions.Raw { + fmt.Println(report.Raw) + return nil + } + if showTrustOptions.JSON { + b, err := json.MarshalIndent(report.Policies, "", " ") + if err != nil { + return err + } + fmt.Println(string(b)) + return nil + } + + row := "{{.RepoName}}\t{{.Type}}\t{{.GPGId}}\t{{.SignatureStore}}\n" + format := "{{range . }}" + row + "{{end}}" + tmpl, err := template.New("listContainers").Parse(format) + if err != nil { + return err + } + w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) + if err := tmpl.Execute(w, report.Policies); err != nil { + return err + } + if err := w.Flush(); err != nil { + return err + } + return nil +} diff --git a/cmd/podman/images/untag.go b/cmd/podman/images/untag.go index 3218844b7..266a3f115 100644 --- a/cmd/podman/images/untag.go +++ b/cmd/podman/images/untag.go @@ -17,6 +17,17 @@ var ( podman untag imageID:latest otherImageName:latest podman untag httpd myregistryhost:5000/fedora/httpd:v2`, } + + imageUntagCommand = &cobra.Command{ + Args: untagCommand.Args, + Use: untagCommand.Use, + Short: untagCommand.Short, + Long: untagCommand.Long, + RunE: untagCommand.RunE, + Example: `podman image untag 0e3bbc2 + podman image untag imageID:latest otherImageName:latest + podman image untag httpd myregistryhost:5000/fedora/httpd:v2`, + } ) func init() { @@ -24,6 +35,11 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: untagCommand, }) + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: imageUntagCommand, + Parent: imageCmd, + }) } func untag(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/login.go b/cmd/podman/login.go index dc57758ab..8413861f5 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -8,6 +8,7 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/registries" "github.com/spf13/cobra" ) @@ -23,7 +24,7 @@ var ( Short: "Login to a container registry", Long: "Login to a container registry on a specified server.", RunE: login, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Example: `podman login quay.io podman login --username ... --password ... quay.io podman login --authfile dir/auth.json quay.io`, @@ -48,6 +49,7 @@ func init() { flags.BoolVarP(&loginOptions.GetLoginSet, "get-login", "", false, "Return the current login user for the registry") loginOptions.Stdin = os.Stdin loginOptions.Stdout = os.Stdout + loginOptions.AcceptUnspecifiedRegistry = true } // Implementation of podman-login. @@ -62,7 +64,8 @@ func login(cmd *cobra.Command, args []string) error { AuthFilePath: loginOptions.AuthFile, DockerCertPath: loginOptions.CertDir, DockerInsecureSkipTLSVerify: skipTLS, + SystemRegistriesConfPath: registries.SystemRegistriesConfPath(), } loginOptions.GetLoginSet = cmd.Flag("get-login").Changed - return auth.Login(context.Background(), &sysCtx, &loginOptions.LoginOptions, args[0]) + return auth.Login(context.Background(), &sysCtx, &loginOptions.LoginOptions, args) } diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index c21711fc0..d0afc21b4 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -7,7 +7,7 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" - "github.com/pkg/errors" + "github.com/containers/libpod/pkg/registries" "github.com/spf13/cobra" ) @@ -39,19 +39,14 @@ func init() { flags.AddFlagSet(auth.GetLogoutFlags(&logoutOptions)) logoutOptions.Stdin = os.Stdin logoutOptions.Stdout = os.Stdout + logoutOptions.AcceptUnspecifiedRegistry = true } // Implementation of podman-logout. func logout(cmd *cobra.Command, args []string) error { - sysCtx := types.SystemContext{AuthFilePath: logoutOptions.AuthFile} - - registry := "" - if len(args) > 0 { - if logoutOptions.All { - return errors.New("--all takes no arguments") - } - registry = args[0] + sysCtx := types.SystemContext{ + AuthFilePath: logoutOptions.AuthFile, + SystemRegistriesConfPath: registries.SystemRegistriesConfPath(), } - - return auth.Logout(&sysCtx, &logoutOptions, registry) + return auth.Logout(&sysCtx, &logoutOptions, args) } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 422dee90b..76ec7bc8e 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -10,6 +10,7 @@ import ( _ "github.com/containers/libpod/cmd/podman/images" _ "github.com/containers/libpod/cmd/podman/manifest" _ "github.com/containers/libpod/cmd/podman/networks" + _ "github.com/containers/libpod/cmd/podman/play" _ "github.com/containers/libpod/cmd/podman/pods" "github.com/containers/libpod/cmd/podman/registry" _ "github.com/containers/libpod/cmd/podman/system" diff --git a/cmd/podman/manifest/annotate.go b/cmd/podman/manifest/annotate.go new file mode 100644 index 000000000..21d4fb747 --- /dev/null +++ b/cmd/podman/manifest/annotate.go @@ -0,0 +1,56 @@ +package manifest + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + manifestAnnotateOpts = entities.ManifestAnnotateOptions{} + annotateCmd = &cobra.Command{ + Use: "annotate [flags] LIST IMAGE", + Short: "Add or update information about an entry in a manifest list or image index", + Long: "Adds or updates information about an entry in a manifest list or image index.", + RunE: annotate, + Example: `podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64`, + Args: cobra.ExactArgs(2), + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: annotateCmd, + Parent: manifestCmd, + }) + flags := annotateCmd.Flags() + flags.StringSliceVar(&manifestAnnotateOpts.Annotation, "annotation", nil, "set an `annotation` for the specified image") + flags.StringVar(&manifestAnnotateOpts.Arch, "arch", "", "override the `architecture` of the specified image") + flags.StringSliceVar(&manifestAnnotateOpts.Features, "features", nil, "override the `features` of the specified image") + flags.StringVar(&manifestAnnotateOpts.OS, "os", "", "override the `OS` of the specified image") + flags.StringSliceVar(&manifestAnnotateOpts.OSFeatures, "os-features", nil, "override the OS `features` of the specified image") + flags.StringVar(&manifestAnnotateOpts.OSVersion, "os-version", "", "override the OS `version` of the specified image") + flags.StringVar(&manifestAnnotateOpts.Variant, "variant", "", "override the `variant` of the specified image") +} + +func annotate(cmd *cobra.Command, args []string) error { + listImageSpec := args[0] + instanceSpec := args[1] + if listImageSpec == "" { + return errors.Errorf(`invalid image name "%s"`, listImageSpec) + } + if instanceSpec == "" { + return errors.Errorf(`invalid image digest "%s"`, instanceSpec) + } + updatedListID, err := registry.ImageEngine().ManifestAnnotate(context.Background(), args, manifestAnnotateOpts) + if err != nil { + return errors.Wrapf(err, "error removing from manifest list %s", listImageSpec) + } + fmt.Printf("%s\n", updatedListID) + return nil +} diff --git a/cmd/podman/manifest/manifest.go b/cmd/podman/manifest/manifest.go index b78879b34..d7f042a56 100644 --- a/cmd/podman/manifest/manifest.go +++ b/cmd/podman/manifest/manifest.go @@ -15,8 +15,12 @@ var ( Long: manifestDescription, TraverseChildren: true, RunE: validate.SubCommandExists, - Example: `podman manifest create localhost/list - podman manifest inspect localhost/list`, + Example: `podman manifest add mylist:v1.11 image:v1.11-amd64 + podman manifest create localhost/list + podman manifest inspect localhost/list + podman manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64 + podman manifest push mylist:v1.11 quay.io/myimagelist + podman manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`, } ) diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go new file mode 100644 index 000000000..49c76f40b --- /dev/null +++ b/cmd/podman/manifest/push.go @@ -0,0 +1,66 @@ +package manifest + +import ( + "context" + + "github.com/containers/common/pkg/auth" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + manifestPushOpts = entities.ManifestPushOptions{} + pushCmd = &cobra.Command{ + Use: "push [flags] SOURCE DESTINATION", + Short: "Push a manifest list or image index to a registry", + Long: "Pushes manifest lists and image indexes to registries.", + RunE: push, + Example: `podman manifest push mylist:v1.11 quay.io/myimagelist`, + Args: cobra.ExactArgs(2), + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: pushCmd, + Parent: manifestCmd, + }) + flags := pushCmd.Flags() + flags.BoolVar(&manifestPushOpts.Purge, "purge", false, "remove the manifest list if push succeeds") + flags.BoolVar(&manifestPushOpts.All, "all", false, "also push the images in the list") + flags.StringVar(&manifestPushOpts.Authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&manifestPushOpts.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry") + flags.StringVar(&manifestPushOpts.Creds, "creds", "", "use `[username[:password]]` for accessing the registry") + flags.StringVar(&manifestPushOpts.DigestFile, "digestfile", "", "after copying the image, write the digest of the resulting digest to the file") + flags.StringVarP(&manifestPushOpts.Format, "format", "f", "", "manifest type (oci or v2s2) to attempt to use when pushing the manifest list (default is manifest type of source)") + flags.BoolVarP(&manifestPushOpts.RemoveSignatures, "remove-signatures", "", false, "don't copy signatures when pushing images") + flags.StringVar(&manifestPushOpts.SignBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`") + flags.BoolVar(&manifestPushOpts.TlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") + flags.BoolVarP(&manifestPushOpts.Quiet, "quiet", "q", false, "don't output progress information when pushing lists") + if registry.IsRemote() { + _ = flags.MarkHidden("authfile") + _ = flags.MarkHidden("cert-dir") + _ = flags.MarkHidden("tls-verify") + } +} + +func push(cmd *cobra.Command, args []string) error { + if err := auth.CheckAuthFile(manifestPushOpts.Authfile); err != nil { + return err + } + listImageSpec := args[0] + destSpec := args[1] + if listImageSpec == "" { + return errors.Errorf(`invalid image name "%s"`, listImageSpec) + } + if destSpec == "" { + return errors.Errorf(`invalid destination "%s"`, destSpec) + } + if err := registry.ImageEngine().ManifestPush(context.Background(), args, manifestPushOpts); err != nil { + return errors.Wrapf(err, "error pushing manifest %s to %s", listImageSpec, destSpec) + } + return nil +} diff --git a/cmd/podman/manifest/remove.go b/cmd/podman/manifest/remove.go new file mode 100644 index 000000000..4d345efc0 --- /dev/null +++ b/cmd/podman/manifest/remove.go @@ -0,0 +1,47 @@ +package manifest + +import ( + "context" + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + removeCmd = &cobra.Command{ + Use: "remove [flags] LIST IMAGE", + Short: "Remove an entry from a manifest list or image index", + Long: "Removes an image from a manifest list or image index.", + RunE: remove, + Example: `podman manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`, + Args: cobra.ExactArgs(2), + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: removeCmd, + Parent: manifestCmd, + }) +} + +func remove(cmd *cobra.Command, args []string) error { + listImageSpec := args[0] + instanceSpec := args[1] + if listImageSpec == "" { + return errors.Errorf(`invalid image name "%s"`, listImageSpec) + } + if instanceSpec == "" { + return errors.Errorf(`invalid image digest "%s"`, instanceSpec) + } + updatedListID, err := registry.ImageEngine().ManifestRemove(context.Background(), args) + if err != nil { + return errors.Wrapf(err, "error removing from manifest list %s", listImageSpec) + } + fmt.Printf("%s\n", updatedListID) + return nil +} diff --git a/cmd/podman/parse/common.go b/cmd/podman/parse/common.go index a5e9b4fc2..13f425b6d 100644 --- a/cmd/podman/parse/common.go +++ b/cmd/podman/parse/common.go @@ -30,13 +30,20 @@ func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool return errors.Errorf("--all and --latest cannot be used together") } + if (argLen > 0) && specifiedAll { + return errors.Errorf("no arguments are needed with --all") + } + if ignoreArgLen { return nil } - if (argLen > 0) && (specifiedAll || specifiedLatest) { - return errors.Errorf("no arguments are needed with --all or --latest") - } else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) { - return errors.Errorf("no arguments are needed with --all, --latest or --cidfile") + + if argLen > 0 { + if specifiedLatest { + return errors.Errorf("no arguments are needed with --latest") + } else if cidfile && (specifiedLatest || specifiedCIDFile) { + return errors.Errorf("no arguments are needed with --latest or --cidfile") + } } if specifiedCIDFile { diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go new file mode 100644 index 000000000..2499b54b9 --- /dev/null +++ b/cmd/podman/play/kube.go @@ -0,0 +1,101 @@ +package pods + +import ( + "fmt" + "os" + + "github.com/containers/common/pkg/auth" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +// playKubeOptionsWrapper allows for separating CLI-only fields from API-only +// fields. +type playKubeOptionsWrapper struct { + entities.PlayKubeOptions + + TLSVerifyCLI bool +} + +var ( + // 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. + + It creates the pod and containers described in the YAML. The containers within the pod are then started and the ID of the new Pod is output.` + + kubeCmd = &cobra.Command{ + Use: "kube [flags] KUBEFILE", + Short: "Play a pod based on Kubernetes YAML.", + Long: kubeDescription, + RunE: kube, + Args: cobra.ExactArgs(1), + Example: `podman play kube nginx.yml + podman play kube --creds user:password --seccomp-profile-root /custom/path apache.yml`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: kubeCmd, + Parent: playCmd, + }) + + flags := kubeCmd.Flags() + flags.SetNormalizeFunc(utils.AliasFlags) + flags.StringVar(&kubeOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") + flags.StringVar(&kubeOptions.Network, "network", "", "Connect pod to CNI network(s)") + flags.BoolVarP(&kubeOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images") + if !registry.IsRemote() { + flags.StringVar(&kubeOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") + flags.StringVar(&kubeOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") + flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + flags.StringVar(&kubeOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") + flags.StringVar(&kubeOptions.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles") + } +} + +func kube(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) + } + if kubeOptions.Authfile != "" { + if _, err := os.Stat(kubeOptions.Authfile); err != nil { + return errors.Wrapf(err, "error getting authfile %s", kubeOptions.Authfile) + } + } + + report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), args[0], kubeOptions.PlayKubeOptions) + if err != nil { + return err + } + + for _, l := range report.Logs { + fmt.Fprintf(os.Stderr, l) + } + + fmt.Printf("Pod:\n%s\n", report.Pod) + switch len(report.Containers) { + case 0: + return nil + case 1: + fmt.Printf("Container:\n") + default: + fmt.Printf("Containers:\n") + } + for _, ctr := range report.Containers { + fmt.Println(ctr) + } + + return nil +} diff --git a/cmd/podman/play/play.go b/cmd/podman/play/play.go new file mode 100644 index 000000000..b151e5f5d --- /dev/null +++ b/cmd/podman/play/play.go @@ -0,0 +1,26 @@ +package pods + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _play_ + playCmd = &cobra.Command{ + Use: "play", + Short: "Play a pod and its containers from a structured file.", + Long: "Play structured data (e.g., Kubernetes pod or service yaml) based on containers and pods.", + TraverseChildren: true, + RunE: validate.SubCommandExists, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: playCmd, + }) +} diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 85b96d37b..0a2016496 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -12,6 +12,7 @@ import ( "github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/errorhandling" + createconfig "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" @@ -57,7 +58,7 @@ func init() { flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod") flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod") flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file") - flags.StringVar(&share, "share", common.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") + flags.StringVar(&share, "share", createconfig.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") } func create(cmd *cobra.Command, args []string) error { diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go index edca08202..ed265ef90 100644 --- a/cmd/podman/pods/pod.go +++ b/cmd/podman/pods/pod.go @@ -16,7 +16,7 @@ var ( podCmd = &cobra.Command{ Use: "pod", Short: "Manage pods", - Long: "Manage pods", + Long: "Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.", TraverseChildren: true, RunE: validate.SubCommandExists, } diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go index b97dfeb66..5703bd172 100644 --- a/cmd/podman/pods/ps.go +++ b/cmd/podman/pods/ps.go @@ -26,7 +26,7 @@ var ( psCmd = &cobra.Command{ Use: "ps", Aliases: []string{"ls", "list"}, - Short: "list pods", + Short: "List pods", Long: psDescription, RunE: pods, Args: validate.NoArgs, diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go index 7c3597d9a..d3950fdbc 100644 --- a/cmd/podman/pods/stats.go +++ b/cmd/podman/pods/stats.go @@ -35,7 +35,7 @@ var ( // Command: podman pod _pod_ statsCmd = &cobra.Command{ Use: "stats [flags] [POD...]", - Short: "Display resource-usage statistics of pods", + Short: "Display a live stream of resource usage statistics for the containers in one or more pods", Long: statsDescription, RunE: stats, Example: `podman pod stats diff --git a/cmd/podman/pods/top.go b/cmd/podman/pods/top.go index ad602f4ea..9cf2bd525 100644 --- a/cmd/podman/pods/top.go +++ b/cmd/podman/pods/top.go @@ -25,7 +25,7 @@ var ( topCommand = &cobra.Command{ Use: "top [flags] POD [FORMAT-DESCRIPTORS|ARGS]", - Short: "Display the running processes in a pod", + Short: "Display the running processes of containers in a pod", Long: topDescription, RunE: top, Args: cobra.ArbitraryArgs, diff --git a/cmd/podman/root.go b/cmd/podman/root.go index 375faf8b1..502b6c03c 100644 --- a/cmd/podman/root.go +++ b/cmd/podman/root.go @@ -212,7 +212,7 @@ func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) { flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file") cfg := opts.Config - flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, opts.CGroupUsage) + flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, "Cgroup manager to use (\"cgroupfs\"|\"systemd\")") flags.StringVar(&opts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") flags.StringVar(&opts.ConmonPath, "conmon", "", "Path of the conmon binary") flags.StringVar(&cfg.Engine.NetworkCmdPath, "network-cmd-path", cfg.Engine.NetworkCmdPath, "Path to the command for configuring the network") diff --git a/cmd/podman/system/df.go b/cmd/podman/system/df.go new file mode 100644 index 000000000..7caa8e39a --- /dev/null +++ b/cmd/podman/system/df.go @@ -0,0 +1,282 @@ +package system + +import ( + "fmt" + "html/template" + "io" + "os" + "strings" + "text/tabwriter" + "time" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/docker/go-units" + "github.com/spf13/cobra" +) + +var ( + dfSystemDescription = ` + podman system df + + Show podman disk usage + ` + dfSystemCommand = &cobra.Command{ + Use: "df", + Args: validate.NoArgs, + Short: "Show podman disk usage", + Long: dfSystemDescription, + RunE: df, + } +) + +var ( + dfOptions entities.SystemDfOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: dfSystemCommand, + Parent: systemCmd, + }) + flags := dfSystemCommand.Flags() + flags.BoolVarP(&dfOptions.Verbose, "verbose", "v", false, "Show detailed information on disk usage") + flags.StringVar(&dfOptions.Format, "format", "", "Pretty-print images using a Go template") +} + +func df(cmd *cobra.Command, args []string) error { + reports, err := registry.ContainerEngine().SystemDf(registry.Context(), dfOptions) + if err != nil { + return err + } + if dfOptions.Verbose { + return printVerbose(reports) + } + return printSummary(reports, dfOptions.Format) +} + +func printSummary(reports *entities.SystemDfReport, userFormat string) error { + + var ( + dfSummaries []*dfSummary + active int + size, reclaimable int64 + format string = "{{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}\n" + w io.Writer = os.Stdout + ) + + // Images + if len(userFormat) > 0 { + format = userFormat + } + + for _, i := range reports.Images { + if i.Containers > 0 { + active += 1 + } + size += i.Size + if i.Containers < 1 { + reclaimable += i.Size + } + } + + imageSummary := dfSummary{ + Type: "Images", + Total: len(reports.Images), + Active: active, + size: size, + reclaimable: reclaimable, + } + dfSummaries = append(dfSummaries, &imageSummary) + + // Containers + + var ( + conActive int + conSize, conReclaimable int64 + ) + for _, c := range reports.Containers { + if c.Status == "running" { + conActive += 1 + } else { + conReclaimable += c.RWSize + } + conSize += c.RWSize + } + + containerSummary := dfSummary{ + Type: "Containers", + Total: len(reports.Containers), + Active: conActive, + size: conSize, + reclaimable: conReclaimable, + } + + dfSummaries = append(dfSummaries, &containerSummary) + + // Volumes + var ( + activeVolumes int + volumesSize, volumesReclaimable int64 + ) + + for _, v := range reports.Volumes { + activeVolumes += v.Links + volumesSize += v.Size + volumesReclaimable += v.Size + } + volumeSummary := dfSummary{ + Type: "Local Volumes", + Total: len(reports.Volumes), + Active: activeVolumes, + size: volumesSize, + reclaimable: volumesReclaimable, + } + + dfSummaries = append(dfSummaries, &volumeSummary) + + headers := "TYPE\tTOTAL\tACTIVE\tSIZE\tRECLAIMABLE\n" + format = "{{range . }}" + format + "{{end}}" + if len(userFormat) == 0 { + format = headers + format + } + return writeTemplate(w, format, dfSummaries) +} + +func printVerbose(reports *entities.SystemDfReport) error { + var ( + dfImages []*dfImage + dfContainers []*dfContainer + dfVolumes []*dfVolume + w io.Writer = os.Stdout + ) + + // Images + fmt.Print("\nImages space usage:\n\n") + // convert to dfImage for output + for _, d := range reports.Images { + dfImages = append(dfImages, &dfImage{SystemDfImageReport: d}) + } + imageHeaders := "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tSIZE\tSHARED SIZE\tUNIQUE SIZE\tCONTAINERS\n" + imageRow := "{{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}\n" + format := imageHeaders + "{{range . }}" + imageRow + "{{end}}" + if err := writeTemplate(w, format, dfImages); err != nil { + return nil + } + + // Containers + fmt.Print("\nContainers space usage:\n\n") + + // convert to dfContainers for output + for _, d := range reports.Containers { + dfContainers = append(dfContainers, &dfContainer{SystemDfContainerReport: d}) + } + containerHeaders := "CONTAINER ID\tIMAGE\tCOMMAND\tLOCAL VOLUMES\tSIZE\tCREATED\tSTATUS\tNAMES\n" + containerRow := "{{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.Created}}\t{{.Status}}\t{{.Names}}\n" + format = containerHeaders + "{{range . }}" + containerRow + "{{end}}" + if err := writeTemplate(w, format, dfContainers); err != nil { + return nil + } + + // Volumes + fmt.Print("\nLocal Volumes space usage:\n\n") + + // convert to dfVolume for output + for _, d := range reports.Volumes { + dfVolumes = append(dfVolumes, &dfVolume{SystemDfVolumeReport: d}) + } + volumeHeaders := "VOLUME NAME\tLINKS\tSIZE\n" + volumeRow := "{{.VolumeName}}\t{{.Links}}\t{{.Size}}\n" + format = volumeHeaders + "{{range . }}" + volumeRow + "{{end}}" + return writeTemplate(w, format, dfVolumes) +} + +func writeTemplate(w io.Writer, format string, output interface{}) error { + tmpl, err := template.New("dfout").Parse(format) + if err != nil { + return err + } + w = tabwriter.NewWriter(w, 8, 2, 2, ' ', 0) //nolint + if err := tmpl.Execute(w, output); err != nil { + return err + } + if flusher, ok := w.(interface{ Flush() error }); ok { + return flusher.Flush() + } + return nil +} + +type dfImage struct { + *entities.SystemDfImageReport +} + +func (d *dfImage) ImageID() string { + return d.SystemDfImageReport.ImageID[0:12] +} + +func (d *dfImage) Created() string { + return units.HumanDuration(time.Since(d.SystemDfImageReport.Created)) +} + +func (d *dfImage) Size() string { + return units.HumanSize(float64(d.SystemDfImageReport.Size)) +} + +func (d *dfImage) SharedSize() string { + return units.HumanSize(float64(d.SystemDfImageReport.SharedSize)) +} + +func (d *dfImage) UniqueSize() string { + return units.HumanSize(float64(d.SystemDfImageReport.UniqueSize)) +} + +type dfContainer struct { + *entities.SystemDfContainerReport +} + +func (d *dfContainer) ContainerID() string { + return d.SystemDfContainerReport.ContainerID[0:12] +} + +func (d *dfContainer) Image() string { + return d.SystemDfContainerReport.Image[0:12] +} + +func (d *dfContainer) Command() string { + return strings.Join(d.SystemDfContainerReport.Command, " ") +} + +func (d *dfContainer) Size() string { + return units.HumanSize(float64(d.SystemDfContainerReport.Size)) +} + +func (d *dfContainer) Created() string { + return units.HumanDuration(time.Since(d.SystemDfContainerReport.Created)) +} + +type dfVolume struct { + *entities.SystemDfVolumeReport +} + +func (d *dfVolume) Size() string { + return units.HumanSize(float64(d.SystemDfVolumeReport.Size)) +} + +type dfSummary struct { + Type string + Total int + Active int + size int64 + reclaimable int64 +} + +func (d *dfSummary) Size() string { + return units.HumanSize(float64(d.size)) +} + +func (d *dfSummary) Reclaimable() string { + percent := int(float64(d.reclaimable)/float64(d.size)) * 100 + return fmt.Sprintf("%s (%d%%)", units.HumanSize(float64(d.reclaimable)), percent) +} diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go index 26be794c5..dad63bcd4 100644 --- a/cmd/podman/system/info.go +++ b/cmd/podman/system/info.go @@ -10,6 +10,7 @@ import ( "github.com/containers/libpod/pkg/domain/entities" "github.com/ghodss/yaml" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) var ( @@ -25,6 +26,15 @@ var ( RunE: info, Example: `podman info`, } + + systemInfoCommand = &cobra.Command{ + Args: infoCommand.Args, + Use: infoCommand.Use, + Short: infoCommand.Short, + Long: infoCommand.Long, + RunE: infoCommand.RunE, + Example: `podman system info`, + } ) var ( @@ -37,7 +47,17 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: infoCommand, }) - flags := infoCommand.Flags() + infoFlags(infoCommand.Flags()) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: systemInfoCommand, + Parent: systemCmd, + }) + infoFlags(systemInfoCommand.Flags()) +} + +func infoFlags(flags *pflag.FlagSet) { flags.BoolVarP(&debug, "debug", "D", false, "Display additional debug information") flags.StringVarP(&inFormat, "format", "f", "", "Change the output format to JSON or a Go template") } diff --git a/cmd/podman/system/migrate.go b/cmd/podman/system/migrate.go new file mode 100644 index 000000000..13aa162c7 --- /dev/null +++ b/cmd/podman/system/migrate.go @@ -0,0 +1,63 @@ +package system + +import ( + "fmt" + "os" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra" + "github.com/spf13/cobra" +) + +var ( + migrateDescription = ` + podman system migrate + + Migrate existing containers to a new version of Podman. +` + + migrateCommand = &cobra.Command{ + Use: "migrate", + Args: validate.NoArgs, + Short: "Migrate containers", + Long: migrateDescription, + Run: migrate, + } +) + +var ( + migrateOptions entities.SystemMigrateOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: migrateCommand, + Parent: systemCmd, + }) + + flags := migrateCommand.Flags() + flags.StringVar(&migrateOptions.NewRuntime, "new-runtime", "", "Specify a new runtime for all containers") +} + +func migrate(cmd *cobra.Command, args []string) { + // Shutdown all running engines, `renumber` will hijack repository + registry.ContainerEngine().Shutdown(registry.Context()) + registry.ImageEngine().Shutdown(registry.Context()) + + engine, err := infra.NewSystemEngine(entities.MigrateMode, registry.PodmanConfig()) + if err != nil { + fmt.Println(err) + os.Exit(125) + } + defer engine.Shutdown(registry.Context()) + + err = engine.Migrate(registry.Context(), cmd.Flags(), registry.PodmanConfig(), migrateOptions) + if err != nil { + fmt.Println(err) + os.Exit(125) + } + os.Exit(0) +} diff --git a/cmd/podman/system/renumber.go b/cmd/podman/system/renumber.go new file mode 100644 index 000000000..5ee6b3be6 --- /dev/null +++ b/cmd/podman/system/renumber.go @@ -0,0 +1,57 @@ +package system + +import ( + "fmt" + "os" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra" + "github.com/spf13/cobra" +) + +var ( + renumberDescription = ` + podman system renumber + + Migrate lock numbers to handle a change in maximum number of locks. + Mandatory after the number of locks in libpod.conf is changed. +` + + renumberCommand = &cobra.Command{ + Use: "renumber", + Args: validate.NoArgs, + Short: "Migrate lock numbers", + Long: renumberDescription, + Run: renumber, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: renumberCommand, + Parent: systemCmd, + }) + +} +func renumber(cmd *cobra.Command, args []string) { + // Shutdown all running engines, `renumber` will hijack all methods + registry.ContainerEngine().Shutdown(registry.Context()) + registry.ImageEngine().Shutdown(registry.Context()) + + engine, err := infra.NewSystemEngine(entities.RenumberMode, registry.PodmanConfig()) + if err != nil { + fmt.Println(err) + os.Exit(125) + } + defer engine.Shutdown(registry.Context()) + + err = engine.Renumber(registry.Context(), cmd.Flags(), registry.PodmanConfig()) + if err != nil { + fmt.Println(err) + os.Exit(125) + } + os.Exit(0) +} diff --git a/cmd/podman/system/reset.go b/cmd/podman/system/reset.go new file mode 100644 index 000000000..22ddc7529 --- /dev/null +++ b/cmd/podman/system/reset.go @@ -0,0 +1,82 @@ +package system + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/validate" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + systemResetDescription = `Reset podman storage back to default state" + + All containers will be stopped and removed, and all images, volumes and container content will be removed. +` + systemResetCommand = &cobra.Command{ + Use: "reset", + Args: validate.NoArgs, + Short: "Reset podman storage", + Long: systemResetDescription, + Run: reset, + } +) + +var ( + systemResetOptions entities.SystemResetOptions +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: systemResetCommand, + Parent: systemCmd, + }) + flags := systemResetCommand.Flags() + flags.BoolVarP(&systemResetOptions.Force, "force", "f", false, "Do not prompt for confirmation") +} + +func reset(cmd *cobra.Command, args []string) { + // Prompt for confirmation if --force is not set + if !systemResetOptions.Force { + reader := bufio.NewReader(os.Stdin) + fmt.Print(` +WARNING! This will remove: + - all containers + - all pods + - all images + - all build cache +Are you sure you want to continue? [y/N] `) + answer, err := reader.ReadString('\n') + if err != nil { + fmt.Println(errors.Wrapf(err, "error reading input")) + os.Exit(1) + } + if strings.ToLower(answer)[0] != 'y' { + os.Exit(0) + } + } + + // Shutdown all running engines, `reset` will hijack repository + registry.ContainerEngine().Shutdown(registry.Context()) + registry.ImageEngine().Shutdown(registry.Context()) + + engine, err := infra.NewSystemEngine(entities.ResetMode, registry.PodmanConfig()) + if err != nil { + fmt.Println(err) + os.Exit(125) + } + defer engine.Shutdown(registry.Context()) + + if err := engine.Reset(registry.Context(), systemResetOptions); err != nil { + fmt.Println(err) + os.Exit(125) + } + os.Exit(0) +} |