summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/specgen.go50
-rw-r--r--cmd/podman/common/types.go3
-rw-r--r--cmd/podman/common/util.go216
-rw-r--r--cmd/podman/containers/logs.go2
-rw-r--r--cmd/podman/containers/prune.go6
-rw-r--r--cmd/podman/containers/ps.go2
-rw-r--r--cmd/podman/containers/rm.go2
-rw-r--r--cmd/podman/containers/run.go6
-rw-r--r--cmd/podman/containers/stats.go244
-rw-r--r--cmd/podman/containers/unpause.go2
-rw-r--r--cmd/podman/generate/generate.go2
-rw-r--r--cmd/podman/generate/kube.go68
-rw-r--r--cmd/podman/generate/systemd.go2
-rw-r--r--cmd/podman/healthcheck/healthcheck.go4
-rw-r--r--cmd/podman/images/build.go (renamed from cmd/podman/build.go)25
-rw-r--r--cmd/podman/images/diff.go5
-rw-r--r--cmd/podman/images/history.go21
-rw-r--r--cmd/podman/images/import.go23
-rw-r--r--cmd/podman/images/load.go20
-rw-r--r--cmd/podman/images/rm.go7
-rw-r--r--cmd/podman/images/save.go23
-rw-r--r--cmd/podman/images/tag.go16
-rw-r--r--cmd/podman/images/untag.go16
-rw-r--r--cmd/podman/main.go1
-rw-r--r--cmd/podman/manifest/annotate.go56
-rw-r--r--cmd/podman/manifest/manifest.go6
-rw-r--r--cmd/podman/play/kube.go101
-rw-r--r--cmd/podman/play/play.go26
-rw-r--r--cmd/podman/pods/create.go3
-rw-r--r--cmd/podman/pods/pod.go2
-rw-r--r--cmd/podman/pods/ps.go2
-rw-r--r--cmd/podman/pods/stats.go2
-rw-r--r--cmd/podman/pods/top.go2
-rw-r--r--cmd/podman/root.go2
-rw-r--r--cmd/podman/system/df.go282
-rw-r--r--cmd/podman/system/info.go22
-rw-r--r--cmd/podman/system/migrate.go63
-rw-r--r--cmd/podman/system/renumber.go57
-rw-r--r--cmd/podman/system/reset.go82
-rw-r--r--completions/bash/podman27
-rw-r--r--docs/source/markdown/podman-manifest-annotate.1.md61
-rw-r--r--docs/source/markdown/podman-manifest.1.md13
-rw-r--r--libpod/container.go2
-rw-r--r--libpod/container_inspect.go83
-rw-r--r--libpod/define/annotations.go68
-rw-r--r--libpod/define/containerstate.go19
-rw-r--r--libpod/image/manifests.go56
-rw-r--r--libpod/kube.go2
-rw-r--r--libpod/pod.go10
-rw-r--r--libpod/runtime_img.go2
-rw-r--r--libpod/stats.go4
-rw-r--r--libpod/stats_config.go20
-rw-r--r--libpod/stats_unsupported.go2
-rw-r--r--libpod/util.go20
-rw-r--r--libpod/util_test.go3
-rw-r--r--libpod/volume.go13
-rw-r--r--pkg/api/handlers/compat/containers_stats.go2
-rw-r--r--pkg/api/handlers/libpod/generate.go38
-rw-r--r--pkg/api/handlers/libpod/images.go51
-rw-r--r--pkg/api/handlers/libpod/play.go64
-rw-r--r--pkg/api/handlers/swagger/swagger.go7
-rw-r--r--pkg/api/handlers/types.go2
-rw-r--r--pkg/api/server/register_generate.go41
-rw-r--r--pkg/api/server/register_images.go64
-rw-r--r--pkg/api/server/register_play.go42
-rw-r--r--pkg/api/server/server.go2
-rw-r--r--pkg/bindings/generate/generate.go32
-rw-r--r--pkg/bindings/images/images.go30
-rw-r--r--pkg/bindings/images/rm.go65
-rw-r--r--pkg/bindings/manifests/manifests.go21
-rw-r--r--pkg/bindings/play/play.go42
-rw-r--r--pkg/bindings/test/images_test.go40
-rw-r--r--pkg/bindings/test/manifests_test.go22
-rw-r--r--pkg/domain/entities/containers.go11
-rw-r--r--pkg/domain/entities/engine.go9
-rw-r--r--pkg/domain/entities/engine_container.go4
-rw-r--r--pkg/domain/entities/engine_image.go3
-rw-r--r--pkg/domain/entities/engine_system.go14
-rw-r--r--pkg/domain/entities/generate.go14
-rw-r--r--pkg/domain/entities/manifest.go10
-rw-r--r--pkg/domain/entities/play.go36
-rw-r--r--pkg/domain/entities/system.go57
-rw-r--r--pkg/domain/entities/types.go3
-rw-r--r--pkg/domain/infra/abi/containers.go78
-rw-r--r--pkg/domain/infra/abi/generate.go85
-rw-r--r--pkg/domain/infra/abi/images.go109
-rw-r--r--pkg/domain/infra/abi/manifest.go39
-rw-r--r--pkg/domain/infra/abi/play.go544
-rw-r--r--pkg/domain/infra/abi/pods_stats.go3
-rw-r--r--pkg/domain/infra/abi/runtime.go5
-rw-r--r--pkg/domain/infra/abi/system.go177
-rw-r--r--pkg/domain/infra/runtime_abi.go31
-rw-r--r--pkg/domain/infra/runtime_abi_unsupported.go14
-rw-r--r--pkg/domain/infra/runtime_image_proxy.go21
-rw-r--r--pkg/domain/infra/runtime_proxy.go8
-rw-r--r--pkg/domain/infra/tunnel/containers.go4
-rw-r--r--pkg/domain/infra/tunnel/generate.go5
-rw-r--r--pkg/domain/infra/tunnel/images.go4
-rw-r--r--pkg/domain/infra/tunnel/manifest.go29
-rw-r--r--pkg/domain/infra/tunnel/play.go12
-rw-r--r--pkg/domain/infra/tunnel/system.go6
-rw-r--r--pkg/errorhandling/errorhandling.go36
-rw-r--r--pkg/spec/namespaces.go8
-rw-r--r--pkg/spec/security.go7
-rw-r--r--pkg/spec/spec.go17
-rw-r--r--pkg/specgen/generate/container_create.go15
-rw-r--r--pkg/specgen/generate/namespaces.go23
-rw-r--r--pkg/specgen/generate/oci.go11
-rw-r--r--pkg/specgen/generate/pod_create.go6
-rw-r--r--pkg/specgen/generate/ports.go333
-rw-r--r--pkg/specgen/podspecgen.go4
-rw-r--r--pkg/specgen/specgen.go56
-rw-r--r--pkg/varlinkapi/containers.go2
-rw-r--r--pkg/varlinkapi/pods.go8
-rw-r--r--pkg/varlinkapi/remote_client.go6
-rw-r--r--test/e2e/create_test.go49
-rw-r--r--test/e2e/generate_kube_test.go1
-rw-r--r--test/e2e/manifest_test.go16
-rw-r--r--test/e2e/play_kube_test.go1
-rw-r--r--test/e2e/run_networking_test.go105
-rw-r--r--test/e2e/stats_test.go1
-rw-r--r--test/e2e/system_df_test.go1
-rw-r--r--test/e2e/system_reset_test.go1
-rw-r--r--test/e2e/systemd_test.go2
-rw-r--r--test/system/015-help.bats17
-rw-r--r--utils/utils.go19
126 files changed, 4072 insertions, 452 deletions
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go
index 33cba30cd..3e9772576 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 = &quota
+ 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,
@@ -515,10 +534,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 +623,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/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/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..f72446cb6 100644
--- a/cmd/podman/containers/run.go
+++ b/cmd/podman/containers/run.go
@@ -170,9 +170,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/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/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..88d264c1f 100644
--- a/cmd/podman/manifest/manifest.go
+++ b/cmd/podman/manifest/manifest.go
@@ -15,8 +15,10 @@ 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`,
}
)
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)
+}
diff --git a/completions/bash/podman b/completions/bash/podman
index d6e9408c6..61af7ac59 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1782,6 +1782,33 @@ _podman_manifest_add() {
esac
}
+_podman_manifest_annotate() {
+ local options_with_args="
+ --annotation
+ --arch
+ --features
+ --os
+ --os-features
+ --os-version
+ --variant
+ "
+
+ local boolean_options="
+ --help
+ -h
+ "
+
+ _complete_ "$options_with_args" "$boolean_options"
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ __podman_complete_images --id
+ ;;
+ esac
+}
+
_podman_manifest_create() {
local boolean_options="
--all
diff --git a/docs/source/markdown/podman-manifest-annotate.1.md b/docs/source/markdown/podman-manifest-annotate.1.md
new file mode 100644
index 000000000..4450de7fd
--- /dev/null
+++ b/docs/source/markdown/podman-manifest-annotate.1.md
@@ -0,0 +1,61 @@
+% podman-manifest-annotate(1)
+
+## NAME
+podman\-manifest\-annotate - Add or update information about an entry in a manifest list or image index
+
+## SYNOPSIS
+**podman manifest annotate** [options...] *listnameorindexname* *imagemanifestdigest*
+
+## DESCRIPTION
+
+Adds or updates information about an image included in a manifest list or image index.
+
+## OPTIONS
+
+**--annotation** *annotation=value*
+
+Set an annotation on the entry for the specified image.
+
+**--arch**
+
+Override the architecture which the list or index records as a requirement for
+the image. This is usually automatically retrieved from the image's
+configuration information, so it is rarely necessary to use this option.
+
+
+**--features**
+
+Specify the features list which the list or index records as requirements for
+the image. This option is rarely used.
+
+**--os**
+
+Override the OS which the list or index records as a requirement for the image.
+This is usually automatically retrieved from the image's configuration
+information, so it is rarely necessary to use this option.
+
+**--os-features**
+
+Specify the OS features list which the list or index records as requirements
+for the image. This option is rarely used.
+
+**--os-version**
+
+Specify the OS version which the list or index records as a requirement for the
+image. This option is rarely used.
+
+**--variant**
+
+Specify the variant which the list or index records for the image. This option
+is typically used to distinguish between multiple entries which share the same
+architecture value, but which expect different versions of its instruction set.
+
+## EXAMPLE
+
+```
+podman manifest annotate --arch arm64 --variant v8 mylist:v1.11 sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610
+07ec8dc22b5dba3a33c60b68bce28bbd2b905e383fdb32a90708fa5eeac13a07: sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610
+```
+
+## SEE ALSO
+podman(1), podman-manifest(1), podman-manifest-add(1), podman-manifest-create(1), podman-manifest-inspect(1), podman-rmi(1)
diff --git a/docs/source/markdown/podman-manifest.1.md b/docs/source/markdown/podman-manifest.1.md
index 70d695883..c86035ce3 100644
--- a/docs/source/markdown/podman-manifest.1.md
+++ b/docs/source/markdown/podman-manifest.1.md
@@ -13,11 +13,12 @@ The `podman manifest` command provides subcommands which can be used to:
## SUBCOMMANDS
-| Command | Man Page | Description |
-| ------- | ---------------------------------------------------------- | --------------------------------------------------------------------------- |
-| add | [podman-manifest-add(1)](podman-manifest-add.1.md) | Add an image to a manifest list or image index. |
-| create | [podman-manifest-create(1)](podman-manifest-create.1.md) | Create a manifest list or image index. |
-| inspect | [podman-manifest-inspect(1)](podman-manifest-inspect.1.md) | Display a manifest list or image index. |
+| Command | Man Page | Description |
+| -------- | ------------------------------------------------------------ | --------------------------------------------------------------------------- |
+| add | [podman-manifest-add(1)](podman-manifest-add.1.md) | Add an image to a manifest list or image index. |
+| annotate | [podman-manifest-annotate(1)](podman-manifest-annotate.1.md) | Add or update information about an entry in a manifest list or image index. |
+| create | [podman-manifest-create(1)](podman-manifest-create.1.md) | Create a manifest list or image index. |
+| inspect | [podman-manifest-inspect(1)](podman-manifest-inspect.1.md) | Display a manifest list or image index. |
## SEE ALSO
-podman(1), podman-manifest-add(1), podman-manifest-create(1), podman-manifest-inspect(1)
+podman(1), podman-manifest-add(1), podman-manifest-annotate(1), podman-manifest-create(1), podman-manifest-inspect(1)
diff --git a/libpod/container.go b/libpod/container.go
index 5cd719ab6..d4a779b13 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -1221,5 +1221,5 @@ func (c *Container) AutoRemove() bool {
if spec.Annotations == nil {
return false
}
- return c.Spec().Annotations[InspectAnnotationAutoremove] == InspectResponseTrue
+ return c.Spec().Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue
}
diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go
index 276429b11..ae28dde94 100644
--- a/libpod/container_inspect.go
+++ b/libpod/container_inspect.go
@@ -16,73 +16,6 @@ import (
"github.com/syndtr/gocapability/capability"
)
-const (
- // InspectAnnotationCIDFile is used by Inspect to determine if a
- // container ID file was created for the container.
- // If an annotation with this key is found in the OCI spec, it will be
- // used in the output of Inspect().
- InspectAnnotationCIDFile = "io.podman.annotations.cid-file"
- // InspectAnnotationAutoremove is used by Inspect to determine if a
- // container will be automatically removed on exit.
- // If an annotation with this key is found in the OCI spec and is one of
- // the two supported boolean values (InspectResponseTrue and
- // InspectResponseFalse) it will be used in the output of Inspect().
- InspectAnnotationAutoremove = "io.podman.annotations.autoremove"
- // InspectAnnotationVolumesFrom is used by Inspect to identify
- // containers whose volumes are are being used by this container.
- // It is expected to be a comma-separated list of container names and/or
- // IDs.
- // If an annotation with this key is found in the OCI spec, it will be
- // used in the output of Inspect().
- InspectAnnotationVolumesFrom = "io.podman.annotations.volumes-from"
- // InspectAnnotationPrivileged is used by Inspect to identify containers
- // which are privileged (IE, running with elevated privileges).
- // It is expected to be a boolean, populated by one of
- // InspectResponseTrue or InspectResponseFalse.
- // If an annotation with this key is found in the OCI spec, it will be
- // used in the output of Inspect().
- InspectAnnotationPrivileged = "io.podman.annotations.privileged"
- // InspectAnnotationPublishAll is used by Inspect to identify containers
- // which have all the ports from their image published.
- // It is expected to be a boolean, populated by one of
- // InspectResponseTrue or InspectResponseFalse.
- // If an annotation with this key is found in the OCI spec, it will be
- // used in the output of Inspect().
- InspectAnnotationPublishAll = "io.podman.annotations.publish-all"
- // InspectAnnotationInit is used by Inspect to identify containers that
- // mount an init binary in.
- // It is expected to be a boolean, populated by one of
- // InspectResponseTrue or InspectResponseFalse.
- // If an annotation with this key is found in the OCI spec, it will be
- // used in the output of Inspect().
- InspectAnnotationInit = "io.podman.annotations.init"
- // InspectAnnotationLabel is used by Inspect to identify containers with
- // special SELinux-related settings. It is used to populate the output
- // of the SecurityOpt setting.
- // If an annotation with this key is found in the OCI spec, it will be
- // used in the output of Inspect().
- InspectAnnotationLabel = "io.podman.annotations.label"
- // InspectAnnotationSeccomp is used by Inspect to identify containers
- // with special Seccomp-related settings. It is used to populate the
- // output of the SecurityOpt setting in Inspect.
- // If an annotation with this key is found in the OCI spec, it will be
- // used in the output of Inspect().
- InspectAnnotationSeccomp = "io.podman.annotations.seccomp"
- // InspectAnnotationApparmor is used by Inspect to identify containers
- // with special Apparmor-related settings. It is used to populate the
- // output of the SecurityOpt setting.
- // If an annotation with this key is found in the OCI spec, it will be
- // used in the output of Inspect().
- InspectAnnotationApparmor = "io.podman.annotations.apparmor"
-
- // InspectResponseTrue is a boolean True response for an inspect
- // annotation.
- InspectResponseTrue = "TRUE"
- // InspectResponseFalse is a boolean False response for an inspect
- // annotation.
- InspectResponseFalse = "FALSE"
-)
-
// inspectLocked inspects a container for low-level information.
// The caller must held c.lock.
func (c *Container) inspectLocked(size bool) (*define.InspectContainerData, error) {
@@ -452,26 +385,26 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
// Annotations
if ctrSpec.Annotations != nil {
- hostConfig.ContainerIDFile = ctrSpec.Annotations[InspectAnnotationCIDFile]
- if ctrSpec.Annotations[InspectAnnotationAutoremove] == InspectResponseTrue {
+ hostConfig.ContainerIDFile = ctrSpec.Annotations[define.InspectAnnotationCIDFile]
+ if ctrSpec.Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue {
hostConfig.AutoRemove = true
}
- if ctrs, ok := ctrSpec.Annotations[InspectAnnotationVolumesFrom]; ok {
+ if ctrs, ok := ctrSpec.Annotations[define.InspectAnnotationVolumesFrom]; ok {
hostConfig.VolumesFrom = strings.Split(ctrs, ",")
}
- if ctrSpec.Annotations[InspectAnnotationPrivileged] == InspectResponseTrue {
+ if ctrSpec.Annotations[define.InspectAnnotationPrivileged] == define.InspectResponseTrue {
hostConfig.Privileged = true
}
- if ctrSpec.Annotations[InspectAnnotationInit] == InspectResponseTrue {
+ if ctrSpec.Annotations[define.InspectAnnotationInit] == define.InspectResponseTrue {
hostConfig.Init = true
}
- if label, ok := ctrSpec.Annotations[InspectAnnotationLabel]; ok {
+ if label, ok := ctrSpec.Annotations[define.InspectAnnotationLabel]; ok {
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("label=%s", label))
}
- if seccomp, ok := ctrSpec.Annotations[InspectAnnotationSeccomp]; ok {
+ if seccomp, ok := ctrSpec.Annotations[define.InspectAnnotationSeccomp]; ok {
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp))
}
- if apparmor, ok := ctrSpec.Annotations[InspectAnnotationApparmor]; ok {
+ if apparmor, ok := ctrSpec.Annotations[define.InspectAnnotationApparmor]; ok {
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor))
}
}
diff --git a/libpod/define/annotations.go b/libpod/define/annotations.go
new file mode 100644
index 000000000..f6b1c06ea
--- /dev/null
+++ b/libpod/define/annotations.go
@@ -0,0 +1,68 @@
+package define
+
+const (
+ // InspectAnnotationCIDFile is used by Inspect to determine if a
+ // container ID file was created for the container.
+ // If an annotation with this key is found in the OCI spec, it will be
+ // used in the output of Inspect().
+ InspectAnnotationCIDFile = "io.podman.annotations.cid-file"
+ // InspectAnnotationAutoremove is used by Inspect to determine if a
+ // container will be automatically removed on exit.
+ // If an annotation with this key is found in the OCI spec and is one of
+ // the two supported boolean values (InspectResponseTrue and
+ // InspectResponseFalse) it will be used in the output of Inspect().
+ InspectAnnotationAutoremove = "io.podman.annotations.autoremove"
+ // InspectAnnotationVolumesFrom is used by Inspect to identify
+ // containers whose volumes are are being used by this container.
+ // It is expected to be a comma-separated list of container names and/or
+ // IDs.
+ // If an annotation with this key is found in the OCI spec, it will be
+ // used in the output of Inspect().
+ InspectAnnotationVolumesFrom = "io.podman.annotations.volumes-from"
+ // InspectAnnotationPrivileged is used by Inspect to identify containers
+ // which are privileged (IE, running with elevated privileges).
+ // It is expected to be a boolean, populated by one of
+ // InspectResponseTrue or InspectResponseFalse.
+ // If an annotation with this key is found in the OCI spec, it will be
+ // used in the output of Inspect().
+ InspectAnnotationPrivileged = "io.podman.annotations.privileged"
+ // InspectAnnotationPublishAll is used by Inspect to identify containers
+ // which have all the ports from their image published.
+ // It is expected to be a boolean, populated by one of
+ // InspectResponseTrue or InspectResponseFalse.
+ // If an annotation with this key is found in the OCI spec, it will be
+ // used in the output of Inspect().
+ InspectAnnotationPublishAll = "io.podman.annotations.publish-all"
+ // InspectAnnotationInit is used by Inspect to identify containers that
+ // mount an init binary in.
+ // It is expected to be a boolean, populated by one of
+ // InspectResponseTrue or InspectResponseFalse.
+ // If an annotation with this key is found in the OCI spec, it will be
+ // used in the output of Inspect().
+ InspectAnnotationInit = "io.podman.annotations.init"
+ // InspectAnnotationLabel is used by Inspect to identify containers with
+ // special SELinux-related settings. It is used to populate the output
+ // of the SecurityOpt setting.
+ // If an annotation with this key is found in the OCI spec, it will be
+ // used in the output of Inspect().
+ InspectAnnotationLabel = "io.podman.annotations.label"
+ // InspectAnnotationSeccomp is used by Inspect to identify containers
+ // with special Seccomp-related settings. It is used to populate the
+ // output of the SecurityOpt setting in Inspect.
+ // If an annotation with this key is found in the OCI spec, it will be
+ // used in the output of Inspect().
+ InspectAnnotationSeccomp = "io.podman.annotations.seccomp"
+ // InspectAnnotationApparmor is used by Inspect to identify containers
+ // with special Apparmor-related settings. It is used to populate the
+ // output of the SecurityOpt setting.
+ // If an annotation with this key is found in the OCI spec, it will be
+ // used in the output of Inspect().
+ InspectAnnotationApparmor = "io.podman.annotations.apparmor"
+
+ // InspectResponseTrue is a boolean True response for an inspect
+ // annotation.
+ InspectResponseTrue = "TRUE"
+ // InspectResponseFalse is a boolean False response for an inspect
+ // annotation.
+ InspectResponseFalse = "FALSE"
+)
diff --git a/libpod/define/containerstate.go b/libpod/define/containerstate.go
index 6da49a594..825e77387 100644
--- a/libpod/define/containerstate.go
+++ b/libpod/define/containerstate.go
@@ -112,3 +112,22 @@ func (s ContainerExecStatus) String() string {
return "bad state"
}
}
+
+// ContainerStats contains the statistics information for a running container
+type ContainerStats struct {
+ ContainerID string
+ Name string
+ PerCPU []uint64
+ CPU float64
+ CPUNano uint64
+ CPUSystemNano uint64
+ SystemNano uint64
+ MemUsage uint64
+ MemLimit uint64
+ MemPerc float64
+ NetInput uint64
+ NetOutput uint64
+ BlockInput uint64
+ BlockOutput uint64
+ PIDs uint64
+}
diff --git a/libpod/image/manifests.go b/libpod/image/manifests.go
index 7ca17f86c..59678fdb2 100644
--- a/libpod/image/manifests.go
+++ b/libpod/image/manifests.go
@@ -24,6 +24,18 @@ type ManifestAddOpts struct {
Variant string `json:"variant"`
}
+// ManifestAnnotateOptions defines the options for
+// manifest annotate
+type ManifestAnnotateOpts struct {
+ Annotation map[string]string `json:"annotation"`
+ Arch string `json:"arch"`
+ Features []string `json:"features"`
+ OS string `json:"os"`
+ OSFeatures []string `json:"os_feature"`
+ OSVersion string `json:"os_version"`
+ Variant string `json:"variant"`
+}
+
// InspectManifest returns a dockerized version of the manifest list
func (i *Image) InspectManifest() (*manifest.Schema2List, error) {
list, err := i.getManifestList()
@@ -158,3 +170,47 @@ func (i *Image) PushManifest(dest types.ImageReference, opts manifests.PushOptio
_, d, err := list.Push(context.Background(), dest, opts)
return d, err
}
+
+// AnnotateManifest updates an image configuration of a manifest list.
+func (i *Image) AnnotateManifest(systemContext types.SystemContext, d digest.Digest, opts ManifestAnnotateOpts) (string, error) {
+ list, err := i.getManifestList()
+ if err != nil {
+ return "", err
+ }
+ if len(opts.OS) > 0 {
+ if err := list.SetOS(d, opts.OS); err != nil {
+ return "", err
+ }
+ }
+ if len(opts.OSVersion) > 0 {
+ if err := list.SetOSVersion(d, opts.OSVersion); err != nil {
+ return "", err
+ }
+ }
+ if len(opts.Features) > 0 {
+ if err := list.SetFeatures(d, opts.Features); err != nil {
+ return "", err
+ }
+ }
+ if len(opts.OSFeatures) > 0 {
+ if err := list.SetOSFeatures(d, opts.OSFeatures); err != nil {
+ return "", err
+ }
+ }
+ if len(opts.Arch) > 0 {
+ if err := list.SetArchitecture(d, opts.Arch); err != nil {
+ return "", err
+ }
+ }
+ if len(opts.Variant) > 0 {
+ if err := list.SetVariant(d, opts.Variant); err != nil {
+ return "", err
+ }
+ }
+ if len(opts.Annotation) > 0 {
+ if err := list.SetAnnotations(&d, opts.Annotation); err != nil {
+ return "", err
+ }
+ }
+ return list.SaveToImage(i.imageruntime.store, i.ID(), nil, "")
+}
diff --git a/libpod/kube.go b/libpod/kube.go
index 5511d303d..a3c5e912f 100644
--- a/libpod/kube.go
+++ b/libpod/kube.go
@@ -469,7 +469,7 @@ func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) {
}
var selinuxOpts v1.SELinuxOptions
- opts := strings.SplitN(c.config.Spec.Annotations[InspectAnnotationLabel], ":", 2)
+ opts := strings.SplitN(c.config.Spec.Annotations[define.InspectAnnotationLabel], ":", 2)
if len(opts) == 2 {
switch opts[0] {
case "type":
diff --git a/libpod/pod.go b/libpod/pod.go
index b5a14c165..8eb06ae2f 100644
--- a/libpod/pod.go
+++ b/libpod/pod.go
@@ -247,14 +247,14 @@ func (p *Pod) InfraContainerID() (string, error) {
// PodContainerStats is an organization struct for pods and their containers
type PodContainerStats struct {
Pod *Pod
- ContainerStats map[string]*ContainerStats
+ ContainerStats map[string]*define.ContainerStats
}
// GetPodStats returns the stats for each of its containers
-func (p *Pod) GetPodStats(previousContainerStats map[string]*ContainerStats) (map[string]*ContainerStats, error) {
+func (p *Pod) GetPodStats(previousContainerStats map[string]*define.ContainerStats) (map[string]*define.ContainerStats, error) {
var (
ok bool
- prevStat *ContainerStats
+ prevStat *define.ContainerStats
)
p.lock.Lock()
defer p.lock.Unlock()
@@ -266,10 +266,10 @@ func (p *Pod) GetPodStats(previousContainerStats map[string]*ContainerStats) (ma
if err != nil {
return nil, err
}
- newContainerStats := make(map[string]*ContainerStats)
+ newContainerStats := make(map[string]*define.ContainerStats)
for _, c := range containers {
if prevStat, ok = previousContainerStats[c.ID()]; !ok {
- prevStat = &ContainerStats{}
+ prevStat = &define.ContainerStats{}
}
newStats, err := c.GetContainerStats(prevStat)
// If the container wasn't running, don't include it
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index 919080c42..cd7f54799 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -56,7 +56,7 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
}
}
} else {
- return nil, fmt.Errorf("could not remove image %s as it is being used by %d containers", img.ID(), len(imageCtrs))
+ return nil, errors.Wrapf(define.ErrImageInUse, "could not remove image %s as it is being used by %d containers", img.ID(), len(imageCtrs))
}
}
diff --git a/libpod/stats.go b/libpod/stats.go
index 6f42afd18..9f4986144 100644
--- a/libpod/stats.go
+++ b/libpod/stats.go
@@ -13,8 +13,8 @@ import (
)
// GetContainerStats gets the running stats for a given container
-func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) {
- stats := new(ContainerStats)
+func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) {
+ stats := new(define.ContainerStats)
stats.ContainerID = c.ID()
stats.Name = c.Name()
diff --git a/libpod/stats_config.go b/libpod/stats_config.go
deleted file mode 100644
index 91d3d1493..000000000
--- a/libpod/stats_config.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package libpod
-
-// ContainerStats contains the statistics information for a running container
-type ContainerStats struct {
- ContainerID string
- Name string
- PerCPU []uint64
- CPU float64
- CPUNano uint64
- CPUSystemNano uint64
- SystemNano uint64
- MemUsage uint64
- MemLimit uint64
- MemPerc float64
- NetInput uint64
- NetOutput uint64
- BlockInput uint64
- BlockOutput uint64
- PIDs uint64
-}
diff --git a/libpod/stats_unsupported.go b/libpod/stats_unsupported.go
index ec19a89a1..6d21ae8f2 100644
--- a/libpod/stats_unsupported.go
+++ b/libpod/stats_unsupported.go
@@ -5,6 +5,6 @@ package libpod
import "github.com/containers/libpod/libpod/define"
// GetContainerStats gets the running stats for a given container
-func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) {
+func (c *Container) GetContainerStats(previousStats *define.ContainerStats) (*define.ContainerStats, error) {
return nil, define.ErrOSNotSupported
}
diff --git a/libpod/util.go b/libpod/util.go
index 6457dac1c..bdfd153ed 100644
--- a/libpod/util.go
+++ b/libpod/util.go
@@ -9,12 +9,10 @@ import (
"os/exec"
"path/filepath"
"sort"
- "strconv"
"strings"
"time"
"github.com/containers/common/pkg/config"
-
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/utils"
"github.com/fsnotify/fsnotify"
@@ -36,24 +34,6 @@ func FuncTimer(funcName string) {
fmt.Printf("%s executed in %d ms\n", funcName, elapsed)
}
-// RemoveScientificNotationFromFloat returns a float without any
-// scientific notation if the number has any.
-// golang does not handle conversion of float64s that have scientific
-// notation in them and otherwise stinks. please replace this if you have
-// a better implementation.
-func RemoveScientificNotationFromFloat(x float64) (float64, error) {
- bigNum := strconv.FormatFloat(x, 'g', -1, 64)
- breakPoint := strings.IndexAny(bigNum, "Ee")
- if breakPoint > 0 {
- bigNum = bigNum[:breakPoint]
- }
- result, err := strconv.ParseFloat(bigNum, 64)
- if err != nil {
- return x, errors.Wrapf(err, "unable to remove scientific number from calculations")
- }
- return result, nil
-}
-
// MountExists returns true if dest exists in the list of mounts
func MountExists(specMounts []spec.Mount, dest string) bool {
for _, m := range specMounts {
diff --git a/libpod/util_test.go b/libpod/util_test.go
index 227686c2b..4e18a7e4e 100644
--- a/libpod/util_test.go
+++ b/libpod/util_test.go
@@ -3,6 +3,7 @@ package libpod
import (
"testing"
+ "github.com/containers/libpod/utils"
"github.com/stretchr/testify/assert"
)
@@ -10,7 +11,7 @@ func TestRemoveScientificNotationFromFloat(t *testing.T) {
numbers := []float64{0.0, .5, 1.99999932, 1.04e+10}
results := []float64{0.0, .5, 1.99999932, 1.04}
for i, x := range numbers {
- result, err := RemoveScientificNotationFromFloat(x)
+ result, err := utils.RemoveScientificNotationFromFloat(x)
assert.NoError(t, err)
assert.Equal(t, result, results[i])
}
diff --git a/libpod/volume.go b/libpod/volume.go
index 70099d6f4..82f389833 100644
--- a/libpod/volume.go
+++ b/libpod/volume.go
@@ -3,6 +3,7 @@ package libpod
import (
"time"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/lock"
)
@@ -133,3 +134,15 @@ func (v *Volume) Config() (*VolumeConfig, error) {
err := JSONDeepCopy(v.config, &config)
return &config, err
}
+
+// VolumeInUse goes through the container dependencies of a volume
+// and checks if the volume is being used by any container.
+func (v *Volume) VolumesInUse() ([]string, error) {
+ v.lock.Lock()
+ defer v.lock.Unlock()
+
+ if !v.valid {
+ return nil, define.ErrVolumeRemoved
+ }
+ return v.runtime.state.VolumeInUse(v)
+}
diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go
index 53ad0a632..62ccd2b93 100644
--- a/pkg/api/handlers/compat/containers_stats.go
+++ b/pkg/api/handlers/compat/containers_stats.go
@@ -50,7 +50,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
return
}
- stats, err := ctnr.GetContainerStats(&libpod.ContainerStats{})
+ stats, err := ctnr.GetContainerStats(&define.ContainerStats{})
if err != nil {
utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain Container %s stats", name))
return
diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go
new file mode 100644
index 000000000..23320d346
--- /dev/null
+++ b/pkg/api/handlers/libpod/generate.go
@@ -0,0 +1,38 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func GenerateKube(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Service bool `schema:"service"`
+ }{
+ // Defaults would go here.
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ containerEngine := abi.ContainerEngine{Libpod: runtime}
+ options := entities.GenerateKubeOptions{Service: query.Service}
+ report, err := containerEngine.GenerateKube(r.Context(), utils.GetName(r), options)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error generating YAML"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, report.Reader)
+}
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index f7be5ce9a..93b4564a1 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -23,6 +23,7 @@ import (
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/util"
utils2 "github.com/containers/libpod/utils"
"github.com/gorilla/schema"
@@ -700,8 +701,8 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, reports)
}
-// ImagesRemove is the endpoint for image removal.
-func ImagesRemove(w http.ResponseWriter, r *http.Request) {
+// ImagesBatchRemove is the endpoint for batch image removal.
+func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
@@ -722,7 +723,49 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) {
opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force}
imageEngine := abi.ImageEngine{Libpod: runtime}
- rmReport, rmError := imageEngine.Remove(r.Context(), query.Images, opts)
- report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Error: rmError.Error()}
+ rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts)
+
+ strErrs := errorhandling.ErrorsToStrings(rmErrors)
+ report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: strErrs}
utils.WriteResponse(w, http.StatusOK, report)
}
+
+// ImagesRemove is the endpoint for removing one image.
+func ImagesRemove(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Force bool `schema:"force"`
+ }{
+ Force: false,
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ opts := entities.ImageRemoveOptions{Force: query.Force}
+ imageEngine := abi.ImageEngine{Libpod: runtime}
+ rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts)
+
+ // In contrast to batch-removal, where we're only setting the exit
+ // code, we need to have another closer look at the errors here and set
+ // the appropriate http status code.
+
+ switch rmReport.ExitCode {
+ case 0:
+ report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Errors: []string{}}
+ utils.WriteResponse(w, http.StatusOK, report)
+ case 1:
+ // 404 - no such image
+ utils.Error(w, "error removing image", http.StatusNotFound, errorhandling.JoinErrors(rmErrors))
+ case 2:
+ // 409 - conflict error (in use by containers)
+ utils.Error(w, "error removing image", http.StatusConflict, errorhandling.JoinErrors(rmErrors))
+ default:
+ // 500 - internal error
+ utils.Error(w, "failed to remove image", http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors))
+ }
+}
diff --git a/pkg/api/handlers/libpod/play.go b/pkg/api/handlers/libpod/play.go
new file mode 100644
index 000000000..26e02bf4f
--- /dev/null
+++ b/pkg/api/handlers/libpod/play.go
@@ -0,0 +1,64 @@
+package libpod
+
+import (
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+func PlayKube(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Network string `schema:"reference"`
+ TLSVerify bool `schema:"tlsVerify"`
+ }{
+ TLSVerify: true,
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ // Fetch the K8s YAML file from the body, and copy it to a temp file.
+ tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml")
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
+ return
+ }
+ defer os.Remove(tmpfile.Name())
+ if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
+ tmpfile.Close()
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
+ return
+ }
+ if err := tmpfile.Close(); err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error closing temporary file"))
+ return
+ }
+
+ containerEngine := abi.ContainerEngine{Libpod: runtime}
+ options := entities.PlayKubeOptions{Network: query.Network, Quiet: true}
+ if _, found := r.URL.Query()["tlsVerify"]; found {
+ options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
+ }
+
+ report, err := containerEngine.PlayKube(r.Context(), tmpfile.Name(), options)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error playing YAML file"))
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, report)
+}
diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go
index 0aceaf5f6..5d125417b 100644
--- a/pkg/api/handlers/swagger/swagger.go
+++ b/pkg/api/handlers/swagger/swagger.go
@@ -56,6 +56,13 @@ type swagLibpodImagesRemoveResponse struct {
Body handlers.LibpodImagesRemoveReport
}
+// PlayKube response
+// swagger:response DocsLibpodPlayKubeResponse
+type swagLibpodPlayKubeResponse struct {
+ // in:body
+ Body entities.PlayKubeReport
+}
+
// Delete response
// swagger:response DocsImageDeleteResponse
type swagImageDeleteResponse struct {
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 58a12ea6a..a7abf59c0 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -41,7 +41,7 @@ type LibpodImagesPullReport struct {
type LibpodImagesRemoveReport struct {
entities.ImageRemoveReport
// Image removal requires is to return data and an error.
- Error string
+ Errors []string
}
type ContainersPruneReport struct {
diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go
new file mode 100644
index 000000000..391e60111
--- /dev/null
+++ b/pkg/api/server/register_generate.go
@@ -0,0 +1,41 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerGenerateHandlers(r *mux.Router) error {
+ // swagger:operation GET /libpod/generate/{name:.*}/kube libpod libpodGenerateKube
+ // ---
+ // tags:
+ // - containers
+ // - pods
+ // summary: Play a Kubernetes YAML file.
+ // description: Create and run pods based on a Kubernetes YAML file (pod or service kind).
+ // parameters:
+ // - in: path
+ // name: name:.*
+ // type: string
+ // required: true
+ // description: Name or ID of the container or pod.
+ // - in: query
+ // name: service
+ // type: boolean
+ // default: false
+ // description: Generate YAML for a Kubernetes service object.
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // description: no error
+ // schema:
+ // type: string
+ // format: binary
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/generate/{name:.*}/kube"), s.APIHandler(libpod.GenerateKube)).Methods(http.MethodGet)
+ return nil
+}
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index f59dca6f5..0e8d68b7e 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -822,7 +822,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/import"), s.APIHandler(libpod.ImagesImport)).Methods(http.MethodPost)
- // swagger:operation GET /libpod/images/remove libpod libpodImagesRemove
+ // swagger:operation DELETE /libpod/images/remove libpod libpodImagesRemove
// ---
// tags:
// - images
@@ -853,7 +853,37 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// $ref: "#/responses/BadParamError"
// 500:
// $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/libpod/images/remove"), s.APIHandler(libpod.ImagesRemove)).Methods(http.MethodGet)
+ r.Handle(VersionedPath("/libpod/images/remove"), s.APIHandler(libpod.ImagesBatchRemove)).Methods(http.MethodDelete)
+ // swagger:operation DELETE /libpod/images/{name:.*}/remove libpod libpodRemoveImage
+ // ---
+ // tags:
+ // - images
+ // summary: Remove an image from the local storage.
+ // description: Remove an image from the local storage.
+ // parameters:
+ // - in: path
+ // name: name:.*
+ // type: string
+ // required: true
+ // description: name or ID of image to remove
+ // - in: query
+ // name: force
+ // type: boolean
+ // description: remove the image even if used by containers or has other tags
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/DocsImageDeleteResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 404:
+ // $ref: '#/responses/NoSuchImage'
+ // 409:
+ // $ref: '#/responses/ConflictError'
+ // 500:
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/{name:.*}/remove"), s.APIHandler(libpod.ImagesRemove)).Methods(http.MethodDelete)
// swagger:operation POST /libpod/images/pull libpod libpodImagesPull
// ---
// tags:
@@ -952,36 +982,6 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/search"), s.APIHandler(libpod.SearchImages)).Methods(http.MethodGet)
- // swagger:operation DELETE /libpod/images/{name:.*} libpod libpodRemoveImage
- // ---
- // tags:
- // - images
- // summary: Remove Image
- // description: Delete an image from local store
- // parameters:
- // - in: path
- // name: name:.*
- // type: string
- // required: true
- // description: name or ID of image to delete
- // - in: query
- // name: force
- // type: boolean
- // description: remove the image even if used by containers or has other tags
- // produces:
- // - application/json
- // responses:
- // 200:
- // $ref: "#/responses/DocsImageDeleteResponse"
- // 400:
- // $ref: "#/responses/BadParamError"
- // 404:
- // $ref: '#/responses/NoSuchImage'
- // 409:
- // $ref: '#/responses/ConflictError'
- // 500:
- // $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/libpod/images/{name:.*}"), s.APIHandler(compat.RemoveImage)).Methods(http.MethodDelete)
// swagger:operation GET /libpod/images/{name:.*}/get libpod libpodExportImage
// ---
// tags:
diff --git a/pkg/api/server/register_play.go b/pkg/api/server/register_play.go
new file mode 100644
index 000000000..d04879c19
--- /dev/null
+++ b/pkg/api/server/register_play.go
@@ -0,0 +1,42 @@
+package server
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
+ "github.com/gorilla/mux"
+)
+
+func (s *APIServer) registerPlayHandlers(r *mux.Router) error {
+ // swagger:operation POST /libpod/play/kube libpod libpodPlayKube
+ // ---
+ // tags:
+ // - containers
+ // - pods
+ // summary: Play a Kubernetes YAML file.
+ // description: Create and run pods based on a Kubernetes YAML file (pod or service kind).
+ // parameters:
+ // - in: query
+ // name: network
+ // type: string
+ // description: Connect the pod to this network.
+ // - in: query
+ // name: tlsVerify
+ // type: boolean
+ // default: true
+ // description: Require HTTPS and verify signatures when contating registries.
+ // - in: body
+ // name: request
+ // description: Kubernetes YAML file.
+ // schema:
+ // type: string
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/DocsLibpodPlayKubeResponse"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.HandleFunc(VersionedPath("/libpod/play/kube"), s.APIHandler(libpod.PlayKube)).Methods(http.MethodPost)
+ return nil
+}
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
index ce2d152e0..a6c5d8e1e 100644
--- a/pkg/api/server/server.go
+++ b/pkg/api/server/server.go
@@ -98,12 +98,14 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
server.registerDistributionHandlers,
server.registerEventsHandlers,
server.registerExecHandlers,
+ server.registerGenerateHandlers,
server.registerHealthCheckHandlers,
server.registerImagesHandlers,
server.registerInfoHandlers,
server.registerManifestHandlers,
server.registerMonitorHandlers,
server.registerPingHandlers,
+ server.registerPlayHandlers,
server.registerPluginsHandlers,
server.registerPodsHandlers,
server.RegisterSwaggerHandlers,
diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go
index 2916754b8..d3177133f 100644
--- a/pkg/bindings/generate/generate.go
+++ b/pkg/bindings/generate/generate.go
@@ -1,4 +1,32 @@
package generate
-func GenerateKube() {}
-func GenerateSystemd() {}
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ params.Set("service", strconv.FormatBool(options.Service))
+
+ response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/kube", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+
+ if response.StatusCode == http.StatusOK {
+ return &entities.GenerateKubeReport{Reader: response.Body}, nil
+ }
+
+ // Unpack the error.
+ return nil, response.Process(nil)
+}
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index 4d8ae6a6e..034ade618 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -109,36 +109,6 @@ func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadRe
return &report, response.Process(&report)
}
-// Remove deletes an image from local storage. The optional force parameter
-// will forcibly remove the image by removing all all containers, including
-// those that are Running, first.
-func Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
- var report handlers.LibpodImagesRemoveReport
- conn, err := bindings.GetClient(ctx)
- if err != nil {
- return nil, err
- }
- params := url.Values{}
- params.Set("all", strconv.FormatBool(opts.All))
- params.Set("force", strconv.FormatBool(opts.Force))
- for _, i := range images {
- params.Add("images", i)
- }
-
- response, err := conn.DoRequest(nil, http.MethodGet, "/images/remove", params)
- if err != nil {
- return nil, err
- }
- if err := response.Process(&report); err != nil {
- return nil, err
- }
- var rmError error
- if report.Error != "" {
- rmError = errors.New(report.Error)
- }
- return &report.ImageRemoveReport, rmError
-}
-
// Export saves an image from local storage as a tarball or image archive. The optional format
// parameter is used to change the format of the output.
func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, compress *bool) error {
diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go
new file mode 100644
index 000000000..e3b5590df
--- /dev/null
+++ b/pkg/bindings/images/rm.go
@@ -0,0 +1,65 @@
+package images
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/errorhandling"
+)
+
+// BachtRemove removes a batch of images from the local storage.
+func BatchRemove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, []error) {
+ // FIXME - bindings tests are missing for this endpoint. Once the CI is
+ // re-enabled for bindings, we need to add them. At the time of writing,
+ // the tests don't compile.
+ var report handlers.LibpodImagesRemoveReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ params := url.Values{}
+ params.Set("all", strconv.FormatBool(opts.All))
+ params.Set("force", strconv.FormatBool(opts.Force))
+ for _, i := range images {
+ params.Add("images", i)
+ }
+
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/images/remove", params)
+ if err != nil {
+ return nil, []error{err}
+ }
+ if err := response.Process(&report); err != nil {
+ return nil, []error{err}
+ }
+
+ return &report.ImageRemoveReport, errorhandling.StringsToErrors(report.Errors)
+}
+
+// Remove removes an image from the local storage. Use force to remove an
+// image, even if it's used by containers.
+func Remove(ctx context.Context, nameOrID string, force bool) (*entities.ImageRemoveReport, error) {
+ var report handlers.LibpodImagesRemoveReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ params := url.Values{}
+ params.Set("force", strconv.FormatBool(force))
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s/remove", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ if err := response.Process(&report); err != nil {
+ return nil, err
+ }
+
+ errs := errorhandling.StringsToErrors(report.Errors)
+ return &report.ImageRemoveReport, errorhandling.JoinErrors(errs)
+}
diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go
index a8d1e6ca3..b85169410 100644
--- a/pkg/bindings/manifests/manifests.go
+++ b/pkg/bindings/manifests/manifests.go
@@ -124,3 +124,24 @@ func Push(ctx context.Context, name string, destination *string, all *bool) (str
}
return idr.ID, response.Process(&idr)
}
+
+// Annotate updates the image configuration of a given manifest list
+func Annotate(ctx context.Context, name, digest string, options image.ManifestAnnotateOpts) (string, error) {
+ var idr handlers.IDResponse
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return "", err
+ }
+ params := url.Values{}
+ params.Set("digest", digest)
+ optionsString, err := jsoniter.MarshalToString(options)
+ if err != nil {
+ return "", err
+ }
+ stringReader := strings.NewReader(optionsString)
+ response, err := conn.DoRequest(stringReader, http.MethodPost, "/manifests/%s/annotate", params, name)
+ if err != nil {
+ return "", err
+ }
+ return idr.ID, response.Process(&idr)
+}
diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go
index a6f03cad2..653558a3c 100644
--- a/pkg/bindings/play/play.go
+++ b/pkg/bindings/play/play.go
@@ -1,7 +1,43 @@
package play
-import "github.com/containers/libpod/pkg/bindings"
+import (
+ "context"
+ "net/http"
+ "net/url"
+ "os"
+ "strconv"
-func PlayKube() error {
- return bindings.ErrNotImplemented
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
+ var report entities.PlayKubeReport
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ params := url.Values{}
+ params.Set("network", options.Network)
+ if options.SkipTLSVerify != types.OptionalBoolUndefined {
+ params.Set("tlsVerify", strconv.FormatBool(options.SkipTLSVerify == types.OptionalBoolTrue))
+ }
+
+ response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params)
+ if err != nil {
+ return nil, err
+ }
+ if err := response.Process(&report); err != nil {
+ return nil, err
+ }
+
+ return &report, nil
}
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
index 58210efd0..9c8e82149 100644
--- a/pkg/bindings/test/images_test.go
+++ b/pkg/bindings/test/images_test.go
@@ -84,17 +84,20 @@ var _ = Describe("Podman images", func() {
// Test to validate the remove image api
It("remove image", func() {
// Remove invalid image should be a 404
- _, err = images.Remove(bt.conn, "foobar5000", &bindings.PFalse)
+ response, err := images.Remove(bt.conn, "foobar5000", false)
Expect(err).ToNot(BeNil())
+ Expect(response).To(BeNil())
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
// Remove an image by name, validate image is removed and error is nil
inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil)
Expect(err).To(BeNil())
- response, err := images.Remove(bt.conn, busybox.shortName, nil)
+ response, err = images.Remove(bt.conn, busybox.shortName, false)
Expect(err).To(BeNil())
- Expect(inspectData.ID).To(Equal(response[0]["Deleted"]))
+ code, _ = bindings.CheckResponseCode(err)
+
+ Expect(inspectData.ID).To(Equal(response.Deleted[0]))
inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
@@ -104,30 +107,31 @@ var _ = Describe("Podman images", func() {
_, err = bt.RunTopContainer(&top, &bindings.PFalse, nil)
Expect(err).To(BeNil())
// we should now have a container called "top" running
- containerResponse, err := containers.Inspect(bt.conn, "top", &bindings.PFalse)
+ containerResponse, err := containers.Inspect(bt.conn, "top", nil)
Expect(err).To(BeNil())
Expect(containerResponse.Name).To(Equal("top"))
// try to remove the image "alpine". This should fail since we are not force
// deleting hence image cannot be deleted until the container is deleted.
- response, err = images.Remove(bt.conn, alpine.shortName, &bindings.PFalse)
+ response, err = images.Remove(bt.conn, alpine.shortName, false)
code, _ = bindings.CheckResponseCode(err)
- Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
+ Expect(code).To(BeNumerically("==", http.StatusConflict))
// Removing the image "alpine" where force = true
- response, err = images.Remove(bt.conn, alpine.shortName, &bindings.PTrue)
+ response, err = images.Remove(bt.conn, alpine.shortName, true)
Expect(err).To(BeNil())
-
- // Checking if both the images are gone as well as the container is deleted
- inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil)
+ // To be extra sure, check if the previously created container
+ // is gone as well.
+ _, err = containers.Inspect(bt.conn, "top", &bindings.PFalse)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
- inspectData, err = images.GetImage(bt.conn, alpine.shortName, nil)
+ // Now make sure both images are gone.
+ inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
- _, err = containers.Inspect(bt.conn, "top", &bindings.PFalse)
+ inspectData, err = images.GetImage(bt.conn, alpine.shortName, nil)
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusNotFound))
})
@@ -209,7 +213,7 @@ var _ = Describe("Podman images", func() {
It("Load|Import Image", func() {
// load an image
- _, err := images.Remove(bt.conn, alpine.name, nil)
+ _, err := images.Remove(bt.conn, alpine.name, false)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -219,7 +223,7 @@ var _ = Describe("Podman images", func() {
Expect(err).To(BeNil())
names, err := images.Load(bt.conn, f, nil)
Expect(err).To(BeNil())
- Expect(names.Name).To(Equal(alpine.name))
+ Expect(names.Names[0]).To(Equal(alpine.name))
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
Expect(exists).To(BeTrue())
@@ -227,7 +231,7 @@ var _ = Describe("Podman images", func() {
// load with a repo name
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
- _, err = images.Remove(bt.conn, alpine.name, nil)
+ _, err = images.Remove(bt.conn, alpine.name, false)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -235,7 +239,7 @@ var _ = Describe("Podman images", func() {
newName := "quay.io/newname:fizzle"
names, err = images.Load(bt.conn, f, &newName)
Expect(err).To(BeNil())
- Expect(names.Name).To(Equal(alpine.name))
+ Expect(names.Names[0]).To(Equal(alpine.name))
exists, err = images.Exists(bt.conn, newName)
Expect(err).To(BeNil())
Expect(exists).To(BeTrue())
@@ -243,7 +247,7 @@ var _ = Describe("Podman images", func() {
// load with a bad repo name should trigger a 500
f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName))
Expect(err).To(BeNil())
- _, err = images.Remove(bt.conn, alpine.name, nil)
+ _, err = images.Remove(bt.conn, alpine.name, false)
Expect(err).To(BeNil())
exists, err = images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
@@ -271,7 +275,7 @@ var _ = Describe("Podman images", func() {
It("Import Image", func() {
// load an image
- _, err = images.Remove(bt.conn, alpine.name, nil)
+ _, err = images.Remove(bt.conn, alpine.name, false)
Expect(err).To(BeNil())
exists, err := images.Exists(bt.conn, alpine.name)
Expect(err).To(BeNil())
diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go
index 23c3d8194..ddb75865c 100644
--- a/pkg/bindings/test/manifests_test.go
+++ b/pkg/bindings/test/manifests_test.go
@@ -47,7 +47,7 @@ var _ = Describe("Podman containers ", func() {
code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusInternalServerError))
- _, err = images.Remove(bt.conn, id, nil)
+ _, err = images.Remove(bt.conn, id, false)
Expect(err).To(BeNil())
// create manifest list with images
@@ -118,6 +118,26 @@ var _ = Describe("Podman containers ", func() {
Expect(len(data.Manifests)).To(BeZero())
})
+ It("annotate manifest", func() {
+ id, err := manifests.Create(bt.conn, []string{"quay.io/libpod/foobar:latest"}, []string{}, nil)
+ Expect(err).To(BeNil())
+ opts := image.ManifestAddOpts{Images: []string{"docker.io/library/alpine:latest"}}
+
+ _, err = manifests.Add(bt.conn, id, opts)
+ Expect(err).To(BeNil())
+ data, err := manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(data.Manifests)).To(BeNumerically("==", 1))
+ digest := data.Manifests[0].Digest.String()
+ annoOpts := image.ManifestAnnotateOpts{OS: "foo"}
+ _, err = manifests.Annotate(bt.conn, id, digest, annoOpts)
+ Expect(err).To(BeNil())
+ list, err := manifests.Inspect(bt.conn, id)
+ Expect(err).To(BeNil())
+ Expect(len(list.Manifests)).To(BeNumerically("==", 1))
+ Expect(list.Manifests[0].Platform.OS).To(Equal("foo"))
+ })
+
It("push manifest", func() {
Skip("TODO")
})
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 622e8eb5b..071eff2fc 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -367,3 +367,14 @@ type ContainerCpOptions struct {
// ContainerCpReport describes the output from a cp operation
type ContainerCpReport struct {
}
+
+// ContainerStatsOptions describes input options for getting
+// stats on containers
+type ContainerStatsOptions struct {
+ All bool
+ Format string
+ Latest bool
+ NoReset bool
+ NoStream bool
+ StatChan chan []*define.ContainerStats
+}
diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go
index f45218d14..265c9f36f 100644
--- a/pkg/domain/entities/engine.go
+++ b/pkg/domain/entities/engine.go
@@ -12,9 +12,18 @@ import (
// EngineMode is the connection type podman is using to access libpod
type EngineMode string
+// EngineSetup calls out whether a "normal" or specialized engine should be created
+type EngineSetup string
+
const (
ABIMode = EngineMode("abi")
TunnelMode = EngineMode("tunnel")
+
+ MigrateMode = EngineSetup("migrate")
+ NoFDsMode = EngineSetup("disablefds")
+ NormalMode = EngineSetup("normal")
+ RenumberMode = EngineSetup("renumber")
+ ResetMode = EngineSetup("reset")
)
// Convert EngineMode to String
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 734f72e5f..1bfac4514 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -35,6 +35,7 @@ type ContainerEngine interface {
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error)
ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error)
+ ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error)
ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error)
ContainerUnmount(ctx context.Context, nameOrIds []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error)
@@ -42,6 +43,7 @@ type ContainerEngine interface {
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
Events(ctx context.Context, opts EventsOptions) error
GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
+ GenerateKube(ctx context.Context, nameOrID string, opts GenerateKubeOptions) (*GenerateKubeReport, error)
SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error)
HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error)
@@ -49,6 +51,7 @@ type ContainerEngine interface {
NetworkInspect(ctx context.Context, namesOrIds []string, options NetworkInspectOptions) ([]NetworkInspectReport, error)
NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error)
NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)
+ PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
PodExists(ctx context.Context, nameOrId string) (*BoolReport, error)
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)
@@ -65,6 +68,7 @@ type ContainerEngine interface {
PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error)
SetupRootless(ctx context.Context, cmd *cobra.Command) error
Shutdown(ctx context.Context)
+ SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error)
VarlinkService(ctx context.Context, opts ServiceOptions) error
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error)
VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error)
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index 46a96ca20..c46ba815a 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -19,7 +19,7 @@ type ImageEngine interface {
Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error)
Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error
- Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, error)
+ Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, []error)
Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error
Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error)
Shutdown(ctx context.Context)
@@ -29,4 +29,5 @@ type ImageEngine interface {
ManifestCreate(ctx context.Context, names, images []string, opts ManifestCreateOptions) (string, error)
ManifestInspect(ctx context.Context, name string) ([]byte, error)
ManifestAdd(ctx context.Context, opts ManifestAddOptions) (string, error)
+ ManifestAnnotate(ctx context.Context, names []string, opts ManifestAnnotateOptions) (string, error)
}
diff --git a/pkg/domain/entities/engine_system.go b/pkg/domain/entities/engine_system.go
new file mode 100644
index 000000000..e2000f5cb
--- /dev/null
+++ b/pkg/domain/entities/engine_system.go
@@ -0,0 +1,14 @@
+package entities
+
+import (
+ "context"
+
+ "github.com/spf13/pflag"
+)
+
+type SystemEngine interface {
+ Renumber(ctx context.Context, flags *pflag.FlagSet, config *PodmanConfig) error
+ Migrate(ctx context.Context, flags *pflag.FlagSet, config *PodmanConfig, options SystemMigrateOptions) error
+ Reset(ctx context.Context, options SystemResetOptions) error
+ Shutdown(ctx context.Context)
+}
diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go
index 6d65b52f8..edd217615 100644
--- a/pkg/domain/entities/generate.go
+++ b/pkg/domain/entities/generate.go
@@ -1,5 +1,7 @@
package entities
+import "io"
+
// GenerateSystemdOptions control the generation of systemd unit files.
type GenerateSystemdOptions struct {
// Files - generate files instead of printing to stdout.
@@ -20,3 +22,15 @@ type GenerateSystemdReport struct {
// entire content.
Output string
}
+
+// GenerateKubeOptions control the generation of Kubernetes YAML files.
+type GenerateKubeOptions struct {
+ // Service - generate YAML for a Kubernetes _service_ object.
+ Service bool
+}
+
+// GenerateKubeReport
+type GenerateKubeReport struct {
+ // Reader - the io.Reader to reader the generated YAML file.
+ Reader io.Reader
+}
diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go
index 7316735b0..d92b1dc9b 100644
--- a/pkg/domain/entities/manifest.go
+++ b/pkg/domain/entities/manifest.go
@@ -14,3 +14,13 @@ type ManifestAddOptions struct {
OSVersion string `json:"os_version" schema:"os_version"`
Variant string `json:"variant" schema:"variant"`
}
+
+type ManifestAnnotateOptions struct {
+ Annotation []string `json:"annotation"`
+ Arch string `json:"arch" schema:"arch"`
+ Features []string `json:"features" schema:"features"`
+ OS string `json:"os" schema:"os"`
+ OSFeatures []string `json:"os_features" schema:"os_features"`
+ OSVersion string `json:"os_version" schema:"os_version"`
+ Variant string `json:"variant" schema:"variant"`
+}
diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go
new file mode 100644
index 000000000..93864c23b
--- /dev/null
+++ b/pkg/domain/entities/play.go
@@ -0,0 +1,36 @@
+package entities
+
+import "github.com/containers/image/v5/types"
+
+// PlayKubeOptions controls playing kube YAML files.
+type PlayKubeOptions struct {
+ // Authfile - path to an authentication file.
+ Authfile string
+ // CertDir - to a directory containing TLS certifications and keys.
+ CertDir string
+ // Credentials - `username:password` for authentication against a
+ // container registry.
+ Credentials string
+ // Network - name of the CNI network to connect to.
+ Network string
+ // Quiet - suppress output when pulling images.
+ Quiet bool
+ // SignaturePolicy - path to a signature-policy file.
+ SignaturePolicy string
+ // SkipTLSVerify - skip https and certificate validation when
+ // contacting container registries.
+ SkipTLSVerify types.OptionalBool
+ // SeccompProfileRoot - path to a directory containing seccomp
+ // profiles.
+ SeccompProfileRoot string
+}
+
+// PlayKubeReport contains the results of running play kube.
+type PlayKubeReport struct {
+ // Pod - the ID of the created pod.
+ Pod string
+ // Containers - the IDs of the containers running in the created pod.
+ Containers []string
+ // Logs - non-fatal erros and log messages while processing.
+ Logs []string
+}
diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go
index de93a382f..c62f40025 100644
--- a/pkg/domain/entities/system.go
+++ b/pkg/domain/entities/system.go
@@ -26,3 +26,60 @@ type SystemPruneReport struct {
*ImagePruneReport
VolumePruneReport []*VolumePruneReport
}
+
+// SystemMigrateOptions describes the options needed for the
+// cli to migrate runtimes of containers
+type SystemMigrateOptions struct {
+ NewRuntime string
+}
+
+// SystemDfOptions describes the options for getting df information
+type SystemDfOptions struct {
+ Format string
+ Verbose bool
+}
+
+// SystemDfReport describes the response for df information
+type SystemDfReport struct {
+ Images []*SystemDfImageReport
+ Containers []*SystemDfContainerReport
+ Volumes []*SystemDfVolumeReport
+}
+
+// SystemDfImageReport describes an image for use with df
+type SystemDfImageReport struct {
+ Repository string
+ Tag string
+ ImageID string
+ Created time.Time
+ Size int64
+ SharedSize int64
+ UniqueSize int64
+ Containers int
+}
+
+// SystemDfContainerReport describes a container for use with df
+type SystemDfContainerReport struct {
+ ContainerID string
+ Image string
+ Command []string
+ LocalVolumes int
+ Size int64
+ RWSize int64
+ Created time.Time
+ Status string
+ Names string
+}
+
+// SystemDfVolumeReport describes a volume and its size
+type SystemDfVolumeReport struct {
+ VolumeName string
+ Links int
+ Size int64
+}
+
+// SystemResetOptions describes the options for resetting your
+// container runtime storage, etc
+type SystemResetOptions struct {
+ Force bool
+}
diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go
index 9fbe04c9a..21ab025de 100644
--- a/pkg/domain/entities/types.go
+++ b/pkg/domain/entities/types.go
@@ -8,7 +8,6 @@ import (
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/storage/pkg/archive"
- "github.com/cri-o/ocicni/pkg/ocicni"
)
type Container struct {
@@ -40,7 +39,7 @@ type NetOptions struct {
DNSServers []net.IP
Network specgen.Namespace
NoHosts bool
- PublishPorts []ocicni.PortMapping
+ PublishPorts []specgen.PortMapping
StaticIP *net.IP
StaticMAC *net.HardwareAddr
}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 244fbc5cd..249e8147c 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -8,8 +8,7 @@ import (
"strconv"
"strings"
"sync"
-
- lpfilters "github.com/containers/libpod/libpod/filters"
+ "time"
"github.com/containers/buildah"
"github.com/containers/common/pkg/config"
@@ -17,8 +16,10 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
+ lpfilters "github.com/containers/libpod/libpod/filters"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/libpod/logs"
+ "github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/checkpoint"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi/terminal"
@@ -1003,3 +1004,76 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) {
_ = ic.Libpod.Shutdown(false)
})
}
+
+func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error {
+ containerFunc := ic.Libpod.GetRunningContainers
+ switch {
+ case len(namesOrIds) > 0:
+ containerFunc = func() ([]*libpod.Container, error) { return ic.Libpod.GetContainersByList(namesOrIds) }
+ case options.Latest:
+ containerFunc = func() ([]*libpod.Container, error) {
+ lastCtr, err := ic.Libpod.GetLatestContainer()
+ if err != nil {
+ return nil, err
+ }
+ return []*libpod.Container{lastCtr}, nil
+ }
+ case options.All:
+ containerFunc = ic.Libpod.GetAllContainers
+ }
+
+ ctrs, err := containerFunc()
+ if err != nil {
+ return errors.Wrapf(err, "unable to get list of containers")
+ }
+ containerStats := map[string]*define.ContainerStats{}
+ for _, ctr := range ctrs {
+ initialStats, err := ctr.GetContainerStats(&define.ContainerStats{})
+ if err != nil {
+ // when doing "all", don't worry about containers that are not running
+ cause := errors.Cause(err)
+ if options.All && (cause == define.ErrCtrRemoved || cause == define.ErrNoSuchCtr || cause == define.ErrCtrStateInvalid) {
+ continue
+ }
+ if cause == cgroups.ErrCgroupV1Rootless {
+ err = cause
+ }
+ return err
+ }
+ containerStats[ctr.ID()] = initialStats
+ }
+ for {
+ reportStats := []*define.ContainerStats{}
+ for _, ctr := range ctrs {
+ id := ctr.ID()
+ if _, ok := containerStats[ctr.ID()]; !ok {
+ initialStats, err := ctr.GetContainerStats(&define.ContainerStats{})
+ if errors.Cause(err) == define.ErrCtrRemoved || errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == define.ErrCtrStateInvalid {
+ // skip dealing with a container that is gone
+ continue
+ }
+ if err != nil {
+ return err
+ }
+ containerStats[id] = initialStats
+ }
+ stats, err := ctr.GetContainerStats(containerStats[id])
+ if err != nil && errors.Cause(err) != define.ErrNoSuchCtr {
+ return err
+ }
+ // replace the previous measurement with the current one
+ containerStats[id] = stats
+ reportStats = append(reportStats, stats)
+ }
+ ctrs, err = containerFunc()
+ if err != nil {
+ return err
+ }
+ options.StatChan <- reportStats
+ if options.NoStream {
+ break
+ }
+ time.Sleep(time.Second)
+ }
+ return nil
+}
diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go
index f69ba560e..be5d452bd 100644
--- a/pkg/domain/infra/abi/generate.go
+++ b/pkg/domain/infra/abi/generate.go
@@ -1,14 +1,18 @@
package abi
import (
+ "bytes"
"context"
"fmt"
"strings"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/systemd/generate"
+ "github.com/ghodss/yaml"
"github.com/pkg/errors"
+ k8sAPI "k8s.io/api/core/v1"
)
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
@@ -172,3 +176,84 @@ func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entitie
}
return ctrName, fmt.Sprintf("%s-%s", kind, name)
}
+
+func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
+ var (
+ pod *libpod.Pod
+ podYAML *k8sAPI.Pod
+ err error
+ ctr *libpod.Container
+ servicePorts []k8sAPI.ServicePort
+ serviceYAML k8sAPI.Service
+ )
+ // Get the container in question.
+ ctr, err = ic.Libpod.LookupContainer(nameOrID)
+ if err != nil {
+ pod, err = ic.Libpod.LookupPod(nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ podYAML, servicePorts, err = pod.GenerateForKube()
+ } else {
+ if len(ctr.Dependencies()) > 0 {
+ return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies")
+ }
+ podYAML, err = ctr.GenerateForKube()
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ if options.Service {
+ serviceYAML = libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts)
+ }
+
+ content, err := generateKubeOutput(podYAML, &serviceYAML)
+ if err != nil {
+ return nil, err
+ }
+
+ return &entities.GenerateKubeReport{Reader: bytes.NewReader(content)}, nil
+}
+
+func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service) ([]byte, error) {
+ var (
+ output []byte
+ marshalledPod []byte
+ marshalledService []byte
+ err error
+ )
+
+ marshalledPod, err = yaml.Marshal(podYAML)
+ if err != nil {
+ return nil, err
+ }
+
+ if serviceYAML != nil {
+ marshalledService, err = yaml.Marshal(serviceYAML)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ header := `# Generation of Kubernetes YAML is still under development!
+#
+# Save the output of this file and use kubectl create -f to import
+# it into Kubernetes.
+#
+# Created with podman-%s
+`
+ podmanVersion, err := define.GetVersion()
+ if err != nil {
+ return nil, err
+ }
+
+ output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...)
+ output = append(output, marshalledPod...)
+ if serviceYAML != nil {
+ output = append(output, []byte("---\n")...)
+ output = append(output, marshalledService...)
+ }
+
+ return output, nil
+}
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 0af06fb89..7ab5131f0 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -21,7 +21,6 @@ import (
domainUtils "github.com/containers/libpod/pkg/domain/utils"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
- "github.com/hashicorp/go-multierror"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -423,8 +422,10 @@ func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.
return &entities.ImageTreeReport{Tree: results}, nil
}
-// Remove removes one or more images from local storage.
-func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, finalError error) {
+// removeErrorsToExitCode returns an exit code for the specified slice of
+// image-removal errors. The error codes are set according to the documented
+// behaviour in the Podman man pages.
+func removeErrorsToExitCode(rmErrors []error) int {
var (
// noSuchImageErrors indicates that at least one image was not found.
noSuchImageErrors bool
@@ -434,59 +435,53 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
// otherErrors indicates that at least one error other than the two
// above occured.
otherErrors bool
- // deleteError is a multierror to conveniently collect errors during
- // removal. We really want to delete as many images as possible and not
- // error out immediately.
- deleteError *multierror.Error
)
- report = &entities.ImageRemoveReport{}
+ if len(rmErrors) == 0 {
+ return 0
+ }
- // Set the removalCode and the error after all work is done.
- defer func() {
- switch {
- // 2
- case inUseErrors:
- // One of the specified images has child images or is
- // being used by a container.
- report.ExitCode = 2
- // 1
- case noSuchImageErrors && !(otherErrors || inUseErrors):
- // One of the specified images did not exist, and no other
- // failures.
- report.ExitCode = 1
- // 0
+ for _, e := range rmErrors {
+ switch errors.Cause(e) {
+ case define.ErrNoSuchImage:
+ noSuchImageErrors = true
+ case define.ErrImageInUse, storage.ErrImageUsedByContainer:
+ inUseErrors = true
default:
- // Nothing to do.
- }
- if deleteError != nil {
- // go-multierror has a trailing new line which we need to remove to normalize the string.
- finalError = deleteError.ErrorOrNil()
- finalError = errors.New(strings.TrimSpace(finalError.Error()))
+ otherErrors = true
}
+ }
+
+ switch {
+ case inUseErrors:
+ // One of the specified images has child images or is
+ // being used by a container.
+ return 2
+ case noSuchImageErrors && !(otherErrors || inUseErrors):
+ // One of the specified images did not exist, and no other
+ // failures.
+ return 1
+ default:
+ return 125
+ }
+}
+
+// Remove removes one or more images from local storage.
+func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, rmErrors []error) {
+ report = &entities.ImageRemoveReport{}
+
+ // Set the exit code at very end.
+ defer func() {
+ report.ExitCode = removeErrorsToExitCode(rmErrors)
}()
// deleteImage is an anonymous function to conveniently delete an image
// without having to pass all local data around.
deleteImage := func(img *image.Image) error {
results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
- switch errors.Cause(err) {
- case nil:
- break
- case define.ErrNoSuchImage:
- inUseErrors = true // ExitCode is expected
- case storage.ErrImageUsedByContainer:
- inUseErrors = true // Important for exit codes in Podman.
- return errors.New(
- fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()))
- case define.ErrImageInUse:
- inUseErrors = true
- return err
- default:
- otherErrors = true // Important for exit codes in Podman.
+ if err != nil {
return err
}
-
report.Deleted = append(report.Deleted, results.Deleted)
report.Untagged = append(report.Untagged, results.Untagged...)
return nil
@@ -499,9 +494,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
for {
storageImages, err := ir.Libpod.ImageRuntime().GetRWImages()
if err != nil {
- deleteError = multierror.Append(deleteError,
- errors.Wrapf(err, "unable to query local images"))
- otherErrors = true // Important for exit codes in Podman.
+ rmErrors = append(rmErrors, err)
return
}
// No images (left) to remove, so we're done.
@@ -510,9 +503,7 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
}
// Prevent infinity loops by making a delete-progress check.
if previousImages == len(storageImages) {
- otherErrors = true // Important for exit codes in Podman.
- deleteError = multierror.Append(deleteError,
- errors.New("unable to delete all images, check errors and re-run image removal if needed"))
+ rmErrors = append(rmErrors, errors.New("unable to delete all images, check errors and re-run image removal if needed"))
break
}
previousImages = len(storageImages)
@@ -520,15 +511,15 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
for _, img := range storageImages {
isParent, err := img.IsParent(ctx)
if err != nil {
- otherErrors = true // Important for exit codes in Podman.
- deleteError = multierror.Append(deleteError, err)
+ rmErrors = append(rmErrors, err)
+ continue
}
// Skip parent images.
if isParent {
continue
}
if err := deleteImage(img); err != nil {
- deleteError = multierror.Append(deleteError, err)
+ rmErrors = append(rmErrors, err)
}
}
}
@@ -539,21 +530,13 @@ func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entitie
// Delete only the specified images.
for _, id := range images {
img, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
- switch errors.Cause(err) {
- case nil:
- break
- case image.ErrNoSuchImage:
- noSuchImageErrors = true // Important for exit codes in Podman.
- fallthrough
- default:
- deleteError = multierror.Append(deleteError, errors.Wrapf(err, "failed to remove image '%s'", id))
+ if err != nil {
+ rmErrors = append(rmErrors, err)
continue
}
-
err = deleteImage(img)
if err != nil {
- otherErrors = true // Important for exit codes in Podman.
- deleteError = multierror.Append(deleteError, err)
+ rmErrors = append(rmErrors, err)
}
}
diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go
index 88331f96c..812507f0a 100644
--- a/pkg/domain/infra/abi/manifest.go
+++ b/pkg/domain/infra/abi/manifest.go
@@ -14,6 +14,7 @@ import (
libpodImage "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
+ "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
@@ -71,7 +72,7 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd
}
listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec)
if err != nil {
- return "", errors.Wrapf(err, "error retriving local image from image name %s", listImageSpec)
+ return "", errors.Wrapf(err, "error retrieving local image from image name %s", listImageSpec)
}
manifestAddOpts := libpodImage.ManifestAddOpts{
@@ -100,3 +101,39 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd
}
return listID, nil
}
+
+// ManifestAnnotate updates an entry of the manifest list
+func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) {
+ listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(names[0])
+ if err != nil {
+ return "", errors.Wrapf(err, "error retreiving local image from image name %s", names[0])
+ }
+ digest, err := digest.Parse(names[1])
+ if err != nil {
+ return "", errors.Errorf(`invalid image digest "%s": %v`, names[1], err)
+ }
+ manifestAnnotateOpts := libpodImage.ManifestAnnotateOpts{
+ Arch: opts.Arch,
+ Features: opts.Features,
+ OS: opts.OS,
+ OSFeatures: opts.OSFeatures,
+ OSVersion: opts.OSVersion,
+ Variant: opts.Variant,
+ }
+ if len(opts.Annotation) > 0 {
+ annotations := make(map[string]string)
+ for _, annotationSpec := range opts.Annotation {
+ spec := strings.SplitN(annotationSpec, "=", 2)
+ if len(spec) != 2 {
+ return "", errors.Errorf("no value given for annotation %q", spec[0])
+ }
+ annotations[spec[0]] = spec[1]
+ }
+ manifestAnnotateOpts.Annotation = annotations
+ }
+ updatedListID, err := listImage.AnnotateManifest(*ir.Libpod.SystemContext(), digest, manifestAnnotateOpts)
+ if err == nil {
+ return fmt.Sprintf("%s: %s", updatedListID, digest.String()), nil
+ }
+ return "", err
+}
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
new file mode 100644
index 000000000..cd7eec7e6
--- /dev/null
+++ b/pkg/domain/infra/abi/play.go
@@ -0,0 +1,544 @@
+package abi
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ ann "github.com/containers/libpod/pkg/annotations"
+ "github.com/containers/libpod/pkg/domain/entities"
+ envLib "github.com/containers/libpod/pkg/env"
+ ns "github.com/containers/libpod/pkg/namespaces"
+ createconfig "github.com/containers/libpod/pkg/spec"
+ "github.com/containers/libpod/pkg/specgen/generate"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/containers/storage"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/docker/distribution/reference"
+ "github.com/ghodss/yaml"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ v1 "k8s.io/api/core/v1"
+)
+
+const (
+ // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
+ kubeDirectoryPermission = 0755
+ // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
+ kubeFilePermission = 0644
+)
+
+func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
+ var (
+ containers []*libpod.Container
+ pod *libpod.Pod
+ podOptions []libpod.PodCreateOption
+ podYAML v1.Pod
+ registryCreds *types.DockerAuthConfig
+ writer io.Writer
+ report entities.PlayKubeReport
+ )
+
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := yaml.Unmarshal(content, &podYAML); err != nil {
+ return nil, errors.Wrapf(err, "unable to read %q as YAML", path)
+ }
+
+ if podYAML.Kind != "Pod" {
+ return nil, errors.Errorf("invalid YAML kind: %q. Pod is the only supported Kubernetes YAML kind", podYAML.Kind)
+ }
+
+ // check for name collision between pod and container
+ podName := podYAML.ObjectMeta.Name
+ if podName == "" {
+ return nil, errors.Errorf("pod does not have a name")
+ }
+ for _, n := range podYAML.Spec.Containers {
+ if n.Name == podName {
+ report.Logs = append(report.Logs,
+ fmt.Sprintf("a container exists with the same name (%q) as the pod in your YAML file; changing pod name to %s_pod\n", podName, podName))
+ podName = fmt.Sprintf("%s_pod", podName)
+ }
+ }
+
+ podOptions = append(podOptions, libpod.WithInfraContainer())
+ podOptions = append(podOptions, libpod.WithPodName(podName))
+ // TODO for now we just used the default kernel namespaces; we need to add/subtract this from yaml
+
+ hostname := podYAML.Spec.Hostname
+ if hostname == "" {
+ hostname = podName
+ }
+ podOptions = append(podOptions, libpod.WithPodHostname(hostname))
+
+ if podYAML.Spec.HostNetwork {
+ podOptions = append(podOptions, libpod.WithPodHostNetwork())
+ }
+
+ nsOptions, err := generate.GetNamespaceOptions(strings.Split(createconfig.DefaultKernelNamespaces, ","))
+ if err != nil {
+ return nil, err
+ }
+ podOptions = append(podOptions, nsOptions...)
+ podPorts := getPodPorts(podYAML.Spec.Containers)
+ podOptions = append(podOptions, libpod.WithInfraContainerPorts(podPorts))
+
+ if options.Network != "" {
+ switch strings.ToLower(options.Network) {
+ case "bridge", "host":
+ return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML")
+ case "":
+ return nil, errors.Errorf("invalid value passed to --network: must provide a comma-separated list of CNI networks")
+ default:
+ // We'll assume this is a comma-separated list of CNI
+ // networks.
+ networks := strings.Split(options.Network, ",")
+ logrus.Debugf("Pod joining CNI networks: %v", networks)
+ podOptions = append(podOptions, libpod.WithPodNetworks(networks))
+ }
+ }
+
+ // Create the Pod
+ pod, err = ic.Libpod.NewPod(ctx, podOptions...)
+ if err != nil {
+ return nil, err
+ }
+
+ podInfraID, err := pod.InfraContainerID()
+ if err != nil {
+ return nil, err
+ }
+ hasUserns := false
+ if podInfraID != "" {
+ podCtr, err := ic.Libpod.GetContainer(podInfraID)
+ if err != nil {
+ return nil, err
+ }
+ mappings, err := podCtr.IDMappings()
+ if err != nil {
+ return nil, err
+ }
+ hasUserns = len(mappings.UIDMap) > 0
+ }
+
+ namespaces := map[string]string{
+ // Disabled during code review per mheon
+ //"pid": fmt.Sprintf("container:%s", podInfraID),
+ "net": fmt.Sprintf("container:%s", podInfraID),
+ "ipc": fmt.Sprintf("container:%s", podInfraID),
+ "uts": fmt.Sprintf("container:%s", podInfraID),
+ }
+ if hasUserns {
+ namespaces["user"] = fmt.Sprintf("container:%s", podInfraID)
+ }
+ if !options.Quiet {
+ writer = os.Stderr
+ }
+
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerRegistryCreds: registryCreds,
+ DockerCertPath: options.CertDir,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
+ }
+
+ // map from name to mount point
+ volumes := make(map[string]string)
+ for _, volume := range podYAML.Spec.Volumes {
+ hostPath := volume.VolumeSource.HostPath
+ if hostPath == nil {
+ return nil, errors.Errorf("HostPath is currently the only supported VolumeSource")
+ }
+ if hostPath.Type != nil {
+ switch *hostPath.Type {
+ case v1.HostPathDirectoryOrCreate:
+ if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
+ if err := os.Mkdir(hostPath.Path, kubeDirectoryPermission); err != nil {
+ return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path)
+ }
+ }
+ // Label a newly created volume
+ if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
+ return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path)
+ }
+ case v1.HostPathFileOrCreate:
+ if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
+ f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, kubeFilePermission)
+ if err != nil {
+ return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path)
+ }
+ if err := f.Close(); err != nil {
+ logrus.Warnf("Error in closing newly created HostPath file: %v", err)
+ }
+ }
+ // unconditionally label a newly created volume
+ if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
+ return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path)
+ }
+ case v1.HostPathDirectory:
+ case v1.HostPathFile:
+ case v1.HostPathUnset:
+ // do nothing here because we will verify the path exists in validateVolumeHostDir
+ break
+ default:
+ return nil, errors.Errorf("Directories are the only supported HostPath type")
+ }
+ }
+
+ if err := parse.ValidateVolumeHostDir(hostPath.Path); err != nil {
+ return nil, errors.Wrapf(err, "Error in parsing HostPath in YAML")
+ }
+ volumes[volume.Name] = hostPath.Path
+ }
+
+ seccompPaths, err := initializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, container := range podYAML.Spec.Containers {
+ pullPolicy := util.PullImageMissing
+ if len(container.ImagePullPolicy) > 0 {
+ pullPolicy, err = util.ValidatePullType(string(container.ImagePullPolicy))
+ if err != nil {
+ return nil, err
+ }
+ }
+ named, err := reference.ParseNormalizedNamed(container.Image)
+ if err != nil {
+ return nil, err
+ }
+ // In kube, if the image is tagged with latest, it should always pull
+ if tagged, isTagged := named.(reference.NamedTagged); isTagged {
+ if tagged.Tag() == image.LatestTag {
+ pullPolicy = util.PullImageAlways
+ }
+ }
+ newImage, err := ic.Libpod.ImageRuntime().New(ctx, container.Image, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullPolicy)
+ if err != nil {
+ return nil, err
+ }
+ conf, err := kubeContainerToCreateConfig(ctx, container, ic.Libpod, newImage, namespaces, volumes, pod.ID(), podInfraID, seccompPaths)
+ if err != nil {
+ return nil, err
+ }
+ ctr, err := createconfig.CreateContainerFromCreateConfig(ic.Libpod, conf, ctx, pod)
+ if err != nil {
+ return nil, err
+ }
+ containers = append(containers, ctr)
+ }
+
+ // start the containers
+ for _, ctr := range containers {
+ if err := ctr.Start(ctx, true); err != nil {
+ // Making this a hard failure here to avoid a mess
+ // the other containers are in created status
+ return nil, err
+ }
+ }
+
+ report.Pod = pod.ID()
+ for _, ctr := range containers {
+ report.Containers = append(report.Containers, ctr.ID())
+ }
+
+ return &report, nil
+}
+
+// getPodPorts converts a slice of kube container descriptions to an
+// array of ocicni portmapping descriptions usable in libpod
+func getPodPorts(containers []v1.Container) []ocicni.PortMapping {
+ var infraPorts []ocicni.PortMapping
+ for _, container := range containers {
+ for _, p := range container.Ports {
+ if p.HostPort != 0 && p.ContainerPort == 0 {
+ p.ContainerPort = p.HostPort
+ }
+ if p.Protocol == "" {
+ p.Protocol = "tcp"
+ }
+ portBinding := ocicni.PortMapping{
+ HostPort: p.HostPort,
+ ContainerPort: p.ContainerPort,
+ Protocol: strings.ToLower(string(p.Protocol)),
+ }
+ if p.HostIP != "" {
+ logrus.Debug("HostIP on port bindings is not supported")
+ }
+ // only hostPort is utilized in podman context, all container ports
+ // are accessible inside the shared network namespace
+ if p.HostPort != 0 {
+ infraPorts = append(infraPorts, portBinding)
+ }
+
+ }
+ }
+ return infraPorts
+}
+
+func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfig *createconfig.UserConfig, containerYAML v1.Container) {
+ if containerYAML.SecurityContext == nil {
+ return
+ }
+ if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil {
+ securityConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem
+ }
+ if containerYAML.SecurityContext.Privileged != nil {
+ securityConfig.Privileged = *containerYAML.SecurityContext.Privileged
+ }
+
+ if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil {
+ securityConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation
+ }
+
+ if seopt := containerYAML.SecurityContext.SELinuxOptions; seopt != nil {
+ if seopt.User != "" {
+ securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=user:%s", seopt.User))
+ securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("user:%s", seopt.User))
+ }
+ if seopt.Role != "" {
+ securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=role:%s", seopt.Role))
+ securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("role:%s", seopt.Role))
+ }
+ if seopt.Type != "" {
+ securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=type:%s", seopt.Type))
+ securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("type:%s", seopt.Type))
+ }
+ if seopt.Level != "" {
+ securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=level:%s", seopt.Level))
+ securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("level:%s", seopt.Level))
+ }
+ }
+ if caps := containerYAML.SecurityContext.Capabilities; caps != nil {
+ for _, capability := range caps.Add {
+ securityConfig.CapAdd = append(securityConfig.CapAdd, string(capability))
+ }
+ for _, capability := range caps.Drop {
+ securityConfig.CapDrop = append(securityConfig.CapDrop, string(capability))
+ }
+ }
+ if containerYAML.SecurityContext.RunAsUser != nil {
+ userConfig.User = fmt.Sprintf("%d", *containerYAML.SecurityContext.RunAsUser)
+ }
+ if containerYAML.SecurityContext.RunAsGroup != nil {
+ if userConfig.User == "" {
+ userConfig.User = "0"
+ }
+ userConfig.User = fmt.Sprintf("%s:%d", userConfig.User, *containerYAML.SecurityContext.RunAsGroup)
+ }
+}
+
+// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container
+func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, infraID string, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) {
+ var (
+ containerConfig createconfig.CreateConfig
+ pidConfig createconfig.PidConfig
+ networkConfig createconfig.NetworkConfig
+ cgroupConfig createconfig.CgroupConfig
+ utsConfig createconfig.UtsConfig
+ ipcConfig createconfig.IpcConfig
+ userConfig createconfig.UserConfig
+ securityConfig createconfig.SecurityConfig
+ )
+
+ // The default for MemorySwappiness is -1, not 0
+ containerConfig.Resources.MemorySwappiness = -1
+
+ containerConfig.Image = containerYAML.Image
+ containerConfig.ImageID = newImage.ID()
+ containerConfig.Name = containerYAML.Name
+ containerConfig.Tty = containerYAML.TTY
+
+ containerConfig.Pod = podID
+
+ imageData, _ := newImage.Inspect(ctx)
+
+ userConfig.User = "0"
+ if imageData != nil {
+ userConfig.User = imageData.Config.User
+ }
+
+ setupSecurityContext(&securityConfig, &userConfig, containerYAML)
+
+ securityConfig.SeccompProfilePath = seccompPaths.findForContainer(containerConfig.Name)
+
+ containerConfig.Command = []string{}
+ if imageData != nil && imageData.Config != nil {
+ containerConfig.Command = append(containerConfig.Command, imageData.Config.Entrypoint...)
+ }
+ if len(containerYAML.Command) != 0 {
+ containerConfig.Command = append(containerConfig.Command, containerYAML.Command...)
+ } else if imageData != nil && imageData.Config != nil {
+ containerConfig.Command = append(containerConfig.Command, imageData.Config.Cmd...)
+ }
+ if imageData != nil && len(containerConfig.Command) == 0 {
+ return nil, errors.Errorf("No command specified in container YAML or as CMD or ENTRYPOINT in this image for %s", containerConfig.Name)
+ }
+
+ containerConfig.UserCommand = containerConfig.Command
+
+ containerConfig.StopSignal = 15
+
+ containerConfig.WorkDir = "/"
+ if imageData != nil {
+ // FIXME,
+ // we are currently ignoring imageData.Config.ExposedPorts
+ containerConfig.BuiltinImgVolumes = imageData.Config.Volumes
+ if imageData.Config.WorkingDir != "" {
+ containerConfig.WorkDir = imageData.Config.WorkingDir
+ }
+ containerConfig.Labels = imageData.Config.Labels
+ if imageData.Config.StopSignal != "" {
+ stopSignal, err := util.ParseSignal(imageData.Config.StopSignal)
+ if err != nil {
+ return nil, err
+ }
+ containerConfig.StopSignal = stopSignal
+ }
+ }
+
+ if containerYAML.WorkingDir != "" {
+ containerConfig.WorkDir = containerYAML.WorkingDir
+ }
+ // If the user does not pass in ID mappings, just set to basics
+ if userConfig.IDMappings == nil {
+ userConfig.IDMappings = &storage.IDMappingOptions{}
+ }
+
+ networkConfig.NetMode = ns.NetworkMode(namespaces["net"])
+ ipcConfig.IpcMode = ns.IpcMode(namespaces["ipc"])
+ utsConfig.UtsMode = ns.UTSMode(namespaces["uts"])
+ // disabled in code review per mheon
+ //containerConfig.PidMode = ns.PidMode(namespaces["pid"])
+ userConfig.UsernsMode = ns.UsernsMode(namespaces["user"])
+ if len(containerConfig.WorkDir) == 0 {
+ containerConfig.WorkDir = "/"
+ }
+
+ containerConfig.Pid = pidConfig
+ containerConfig.Network = networkConfig
+ containerConfig.Uts = utsConfig
+ containerConfig.Ipc = ipcConfig
+ containerConfig.Cgroup = cgroupConfig
+ containerConfig.User = userConfig
+ containerConfig.Security = securityConfig
+
+ annotations := make(map[string]string)
+ if infraID != "" {
+ annotations[ann.SandboxID] = infraID
+ annotations[ann.ContainerType] = ann.ContainerTypeContainer
+ }
+ containerConfig.Annotations = annotations
+
+ // Environment Variables
+ envs := map[string]string{}
+ if imageData != nil {
+ imageEnv, err := envLib.ParseSlice(imageData.Config.Env)
+ if err != nil {
+ return nil, errors.Wrap(err, "error parsing image environment variables")
+ }
+ envs = imageEnv
+ }
+ for _, e := range containerYAML.Env {
+ envs[e.Name] = e.Value
+ }
+ containerConfig.Env = envs
+
+ for _, volume := range containerYAML.VolumeMounts {
+ hostPath, exists := volumes[volume.Name]
+ if !exists {
+ return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
+ }
+ if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil {
+ return nil, errors.Wrapf(err, "error in parsing MountPath")
+ }
+ containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", hostPath, volume.MountPath))
+ }
+ return &containerConfig, nil
+}
+
+// kubeSeccompPaths holds information about a pod YAML's seccomp configuration
+// it holds both container and pod seccomp paths
+type kubeSeccompPaths struct {
+ containerPaths map[string]string
+ podPath string
+}
+
+// findForContainer checks whether a container has a seccomp path configured for it
+// if not, it returns the podPath, which should always have a value
+func (k *kubeSeccompPaths) findForContainer(ctrName string) string {
+ if path, ok := k.containerPaths[ctrName]; ok {
+ return path
+ }
+ return k.podPath
+}
+
+// initializeSeccompPaths takes annotations from the pod object metadata and finds annotations pertaining to seccomp
+// it parses both pod and container level
+// if the annotation is of the form "localhost/%s", the seccomp profile will be set to profileRoot/%s
+func initializeSeccompPaths(annotations map[string]string, profileRoot string) (*kubeSeccompPaths, error) {
+ seccompPaths := &kubeSeccompPaths{containerPaths: make(map[string]string)}
+ var err error
+ if annotations != nil {
+ for annKeyValue, seccomp := range annotations {
+ // check if it is prefaced with container.seccomp.security.alpha.kubernetes.io/
+ prefixAndCtr := strings.Split(annKeyValue, "/")
+ if prefixAndCtr[0]+"/" != v1.SeccompContainerAnnotationKeyPrefix {
+ continue
+ } else if len(prefixAndCtr) != 2 {
+ // this could be caused by a user inputting either of
+ // container.seccomp.security.alpha.kubernetes.io{,/}
+ // both of which are invalid
+ return nil, errors.Errorf("Invalid seccomp path: %s", prefixAndCtr[0])
+ }
+
+ path, err := verifySeccompPath(seccomp, profileRoot)
+ if err != nil {
+ return nil, err
+ }
+ seccompPaths.containerPaths[prefixAndCtr[1]] = path
+ }
+
+ podSeccomp, ok := annotations[v1.SeccompPodAnnotationKey]
+ if ok {
+ seccompPaths.podPath, err = verifySeccompPath(podSeccomp, profileRoot)
+ } else {
+ seccompPaths.podPath, err = libpod.DefaultSeccompPath()
+ }
+ if err != nil {
+ return nil, err
+ }
+ }
+ return seccompPaths, nil
+}
+
+// verifySeccompPath takes a path and checks whether it is a default, unconfined, or a path
+// the available options are parsed as defined in https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp
+func verifySeccompPath(path string, profileRoot string) (string, error) {
+ switch path {
+ case v1.DeprecatedSeccompProfileDockerDefault:
+ fallthrough
+ case v1.SeccompProfileRuntimeDefault:
+ return libpod.DefaultSeccompPath()
+ case "unconfined":
+ return path, nil
+ default:
+ parts := strings.Split(path, "/")
+ if parts[0] == "localhost" {
+ return filepath.Join(profileRoot, parts[1]), nil
+ }
+ return "", errors.Errorf("invalid seccomp path: %s", path)
+ }
+}
diff --git a/pkg/domain/infra/abi/pods_stats.go b/pkg/domain/infra/abi/pods_stats.go
index a41c01da0..c6befcf95 100644
--- a/pkg/domain/infra/abi/pods_stats.go
+++ b/pkg/domain/infra/abi/pods_stats.go
@@ -8,6 +8,7 @@ import (
"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"
)
@@ -68,7 +69,7 @@ func combineHumanValues(a, b uint64) string {
}
func floatToPercentString(f float64) string {
- strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f)
+ strippedFloat, err := utils.RemoveScientificNotationFromFloat(f)
if err != nil || strippedFloat == 0 {
// If things go bazinga, return a safe value
return "--"
diff --git a/pkg/domain/infra/abi/runtime.go b/pkg/domain/infra/abi/runtime.go
index fba422d8e..b9020e9a5 100644
--- a/pkg/domain/infra/abi/runtime.go
+++ b/pkg/domain/infra/abi/runtime.go
@@ -16,4 +16,9 @@ type ContainerEngine struct {
Libpod *libpod.Runtime
}
+// Container-related runtime linked against libpod library
+type SystemEngine struct {
+ Libpod *libpod.Runtime
+}
+
var shutdownSync sync.Once
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index ab1b282d8..692fcfa0f 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -5,6 +5,7 @@ import (
"fmt"
"io/ioutil"
"os"
+ "path/filepath"
"strconv"
"syscall"
@@ -18,9 +19,11 @@ import (
iopodmanAPI "github.com/containers/libpod/pkg/varlinkapi"
"github.com/containers/libpod/utils"
"github.com/containers/libpod/version"
+ "github.com/docker/distribution/reference"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
"github.com/varlink/go/varlink"
)
@@ -213,3 +216,177 @@ func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.Sys
}
return systemPruneReport, nil
}
+
+func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.SystemDfOptions) (*entities.SystemDfReport, error) {
+ var (
+ dfImages []*entities.SystemDfImageReport
+ dfContainers []*entities.SystemDfContainerReport
+ dfVolumes []*entities.SystemDfVolumeReport
+ runningContainers []string
+ )
+
+ // Get Images and iterate them
+ imgs, err := ic.Libpod.ImageRuntime().GetImages()
+ if err != nil {
+ return nil, err
+ }
+ for _, i := range imgs {
+ var sharedSize uint64
+ cons, err := i.Containers()
+ if err != nil {
+ return nil, err
+ }
+ imageSize, err := i.Size(ctx)
+ if err != nil {
+ return nil, err
+ }
+ uniqueSize := *imageSize
+
+ parent, err := i.GetParent(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if parent != nil {
+ parentSize, err := parent.Size(ctx)
+ if err != nil {
+ return nil, err
+ }
+ uniqueSize = *parentSize - *imageSize
+ sharedSize = *imageSize - uniqueSize
+ }
+ var name, repository, tag string
+ for _, n := range i.Names() {
+ if len(n) > 0 {
+ name = n
+ break
+ }
+ }
+
+ named, err := reference.ParseNormalizedNamed(name)
+ if err != nil {
+ return nil, err
+ }
+ repository = named.Name()
+ if tagged, isTagged := named.(reference.NamedTagged); isTagged {
+ tag = tagged.Tag()
+ }
+
+ report := entities.SystemDfImageReport{
+ Repository: repository,
+ Tag: tag,
+ ImageID: i.ID(),
+ Created: i.Created(),
+ Size: int64(*imageSize),
+ SharedSize: int64(sharedSize),
+ UniqueSize: int64(uniqueSize),
+ Containers: len(cons),
+ }
+ dfImages = append(dfImages, &report)
+ }
+
+ // GetContainers and iterate them
+ cons, err := ic.Libpod.GetAllContainers()
+ if err != nil {
+ return nil, err
+ }
+ for _, c := range cons {
+ iid, _ := c.Image()
+ conSize, err := c.RootFsSize()
+ if err != nil {
+ return nil, err
+ }
+ state, err := c.State()
+ if err != nil {
+ return nil, err
+ }
+ rwsize, err := c.RWSize()
+ if err != nil {
+ return nil, err
+ }
+ report := entities.SystemDfContainerReport{
+ ContainerID: c.ID(),
+ Image: iid,
+ Command: c.Command(),
+ LocalVolumes: len(c.UserVolumes()),
+ RWSize: rwsize,
+ Size: conSize,
+ Created: c.CreatedTime(),
+ Status: state.String(),
+ Names: c.Name(),
+ }
+ dfContainers = append(dfContainers, &report)
+ }
+
+ // Get volumes and iterate them
+ vols, err := ic.Libpod.GetAllVolumes()
+ if err != nil {
+ return nil, err
+ }
+
+ running, err := ic.Libpod.GetRunningContainers()
+ if err != nil {
+ return nil, err
+ }
+ for _, c := range running {
+ runningContainers = append(runningContainers, c.ID())
+ }
+
+ for _, v := range vols {
+ var consInUse int
+ volSize, err := sizeOfPath(v.MountPoint())
+ if err != nil {
+ return nil, err
+ }
+ inUse, err := v.VolumesInUse()
+ if err != nil {
+ return nil, err
+ }
+ for _, viu := range inUse {
+ if util.StringInSlice(viu, runningContainers) {
+ consInUse += 1
+ }
+ }
+ report := entities.SystemDfVolumeReport{
+ VolumeName: v.Name(),
+ Links: consInUse,
+ Size: volSize,
+ }
+ dfVolumes = append(dfVolumes, &report)
+ }
+ return &entities.SystemDfReport{
+ Images: dfImages,
+ Containers: dfContainers,
+ Volumes: dfVolumes,
+ }, nil
+}
+
+// sizeOfPath determines the file usage of a given path. it was called volumeSize in v1
+// and now is made to be generic and take a path instead of a libpod volume
+func sizeOfPath(path string) (int64, error) {
+ var size int64
+ err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
+ if err == nil && !info.IsDir() {
+ size += info.Size()
+ }
+ return err
+ })
+ return size, err
+}
+
+func (se *SystemEngine) Reset(ctx context.Context, options entities.SystemResetOptions) error {
+ return se.Libpod.Reset(ctx)
+}
+
+func (se *SystemEngine) Renumber(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig) error {
+ return nil
+}
+
+func (s SystemEngine) Migrate(ctx context.Context, flags *pflag.FlagSet, config *entities.PodmanConfig, options entities.SystemMigrateOptions) error {
+ return nil
+}
+
+func (s SystemEngine) Shutdown(ctx context.Context) {
+ if err := s.Libpod.Shutdown(false); err != nil {
+ logrus.Error(err)
+ }
+}
diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go
index 7aa6986a7..67c1cd534 100644
--- a/pkg/domain/infra/runtime_abi.go
+++ b/pkg/domain/infra/runtime_abi.go
@@ -6,8 +6,10 @@ import (
"context"
"fmt"
+ "github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/containers/libpod/pkg/domain/infra/tunnel"
)
@@ -36,3 +38,32 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error)
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
}
+
+// NewSystemEngine factory provides a libpod runtime for specialized system operations
+func NewSystemEngine(setup entities.EngineSetup, facts *entities.PodmanConfig) (entities.SystemEngine, error) {
+ switch facts.EngineMode {
+ case entities.ABIMode:
+ var r *libpod.Runtime
+ var err error
+ switch setup {
+ case entities.NormalMode:
+ r, err = GetRuntime(context.Background(), facts.FlagSet, facts)
+ case entities.RenumberMode:
+ r, err = GetRuntimeRenumber(context.Background(), facts.FlagSet, facts)
+ case entities.ResetMode:
+ r, err = GetRuntimeRenumber(context.Background(), facts.FlagSet, facts)
+ case entities.MigrateMode:
+ name, flagErr := facts.FlagSet.GetString("new-runtime")
+ if flagErr != nil {
+ return nil, flagErr
+ }
+ r, err = GetRuntimeMigrate(context.Background(), facts.FlagSet, facts, name)
+ case entities.NoFDsMode:
+ r, err = GetRuntimeDisableFDs(context.Background(), facts.FlagSet, facts)
+ }
+ return &abi.SystemEngine{Libpod: r}, err
+ case entities.TunnelMode:
+ return nil, fmt.Errorf("tunnel system runtime not supported")
+ }
+ return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
+}
diff --git a/pkg/domain/infra/runtime_abi_unsupported.go b/pkg/domain/infra/runtime_abi_unsupported.go
new file mode 100644
index 000000000..c4e25e990
--- /dev/null
+++ b/pkg/domain/infra/runtime_abi_unsupported.go
@@ -0,0 +1,14 @@
+// +build !ABISupport
+
+package infra
+
+import (
+ "errors"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+// NewSystemEngine factory provides a libpod runtime for specialized system operations
+func NewSystemEngine(setup entities.EngineSetup, facts *entities.PodmanConfig) (entities.SystemEngine, error) {
+ return nil, errors.New("not implemented")
+}
diff --git a/pkg/domain/infra/runtime_image_proxy.go b/pkg/domain/infra/runtime_image_proxy.go
deleted file mode 100644
index ea5d0e6f2..000000000
--- a/pkg/domain/infra/runtime_image_proxy.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// +build ABISupport
-
-package infra
-
-import (
- "context"
-
- "github.com/containers/libpod/pkg/domain/entities"
- "github.com/containers/libpod/pkg/domain/infra/abi"
- "github.com/spf13/pflag"
-)
-
-// ContainerEngine Image Proxy will be EOL'ed after podman is separated from libpod repo
-
-func NewLibpodImageRuntime(flags *pflag.FlagSet, opts *entities.PodmanConfig) (entities.ImageEngine, error) {
- r, err := GetRuntime(context.Background(), flags, opts)
- if err != nil {
- return nil, err
- }
- return &abi.ImageEngine{Libpod: r}, nil
-}
diff --git a/pkg/domain/infra/runtime_proxy.go b/pkg/domain/infra/runtime_proxy.go
index 41193fd89..e7002e20f 100644
--- a/pkg/domain/infra/runtime_proxy.go
+++ b/pkg/domain/infra/runtime_proxy.go
@@ -19,3 +19,11 @@ func NewLibpodRuntime(flags *flag.FlagSet, opts *entities.PodmanConfig) (entitie
}
return &abi.ContainerEngine{Libpod: r}, nil
}
+
+func NewLibpodImageRuntime(flags *flag.FlagSet, opts *entities.PodmanConfig) (entities.ImageEngine, error) {
+ r, err := GetRuntime(context.Background(), flags, opts)
+ if err != nil {
+ return nil, err
+ }
+ return &abi.ImageEngine{Libpod: r}, nil
+}
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 32f9c4e36..227b660f7 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -387,3 +387,7 @@ func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string,
// Shutdown Libpod engine
func (ic *ContainerEngine) Shutdown(_ context.Context) {
}
+
+func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) error {
+ return errors.New("not implemented")
+}
diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go
index 3cd483053..eb5587f89 100644
--- a/pkg/domain/infra/tunnel/generate.go
+++ b/pkg/domain/infra/tunnel/generate.go
@@ -3,6 +3,7 @@ package tunnel
import (
"context"
+ "github.com/containers/libpod/pkg/bindings/generate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
)
@@ -10,3 +11,7 @@ import (
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
return nil, errors.New("not implemented for tunnel")
}
+
+func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
+ return generate.GenerateKube(ic.ClientCxt, nameOrID, options)
+}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index dcc5fc3e7..00893194c 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -20,8 +20,8 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
return &entities.BoolReport{Value: found}, err
}
-func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
- return images.Remove(ir.ClientCxt, imagesArg, opts)
+func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, []error) {
+ return images.BatchRemove(ir.ClientCxt, imagesArg, opts)
}
func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) {
diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go
index 18b400533..3d3196019 100644
--- a/pkg/domain/infra/tunnel/manifest.go
+++ b/pkg/domain/infra/tunnel/manifest.go
@@ -3,6 +3,7 @@ package tunnel
import (
"context"
"encoding/json"
+ "fmt"
"strings"
"github.com/containers/libpod/libpod/image"
@@ -62,3 +63,31 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd
}
return listID, nil
}
+
+// ManifestAnnotate updates an entry of the manifest list
+func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, names []string, opts entities.ManifestAnnotateOptions) (string, error) {
+ manifestAnnotateOpts := image.ManifestAnnotateOpts{
+ Arch: opts.Arch,
+ Features: opts.Features,
+ OS: opts.OS,
+ OSFeatures: opts.OSFeatures,
+ OSVersion: opts.OSVersion,
+ Variant: opts.Variant,
+ }
+ if len(opts.Annotation) > 0 {
+ annotations := make(map[string]string)
+ for _, annotationSpec := range opts.Annotation {
+ spec := strings.SplitN(annotationSpec, "=", 2)
+ if len(spec) != 2 {
+ return "", errors.Errorf("no value given for annotation %q", spec[0])
+ }
+ annotations[spec[0]] = spec[1]
+ }
+ manifestAnnotateOpts.Annotation = annotations
+ }
+ updatedListID, err := manifests.Annotate(ctx, names[0], names[1], manifestAnnotateOpts)
+ if err != nil {
+ return updatedListID, errors.Wrapf(err, "error annotating %s of manifest list %s", names[1], names[0])
+ }
+ return fmt.Sprintf("%s :%s", updatedListID, names[1]), nil
+}
diff --git a/pkg/domain/infra/tunnel/play.go b/pkg/domain/infra/tunnel/play.go
new file mode 100644
index 000000000..15383a703
--- /dev/null
+++ b/pkg/domain/infra/tunnel/play.go
@@ -0,0 +1,12 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/bindings/play"
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
+ return play.PlayKube(ic.ClientCxt, path, options)
+}
diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go
index 18cb6c75a..448fbed1f 100644
--- a/pkg/domain/infra/tunnel/system.go
+++ b/pkg/domain/infra/tunnel/system.go
@@ -3,7 +3,6 @@ package tunnel
import (
"context"
"errors"
- "fmt"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/bindings/system"
@@ -25,6 +24,9 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command)
// SystemPrune prunes unused data from the system.
func (ic *ContainerEngine) SystemPrune(ctx context.Context, options entities.SystemPruneOptions) (*entities.SystemPruneReport, error) {
- fmt.Println("in tunnel")
return system.Prune(ic.ClientCxt, &options.All, &options.Volume)
}
+
+func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.SystemDfOptions) (*entities.SystemDfReport, error) {
+ panic(errors.New("system df is not supported on remote clients"))
+}
diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go
index 970d47636..3117b0ca4 100644
--- a/pkg/errorhandling/errorhandling.go
+++ b/pkg/errorhandling/errorhandling.go
@@ -2,10 +2,46 @@ package errorhandling
import (
"os"
+ "strings"
+ "github.com/hashicorp/go-multierror"
+ "github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
+// JoinErrors converts the error slice into a single human-readable error.
+func JoinErrors(errs []error) error {
+ if len(errs) == 0 {
+ return nil
+ }
+
+ // `multierror` appends new lines which we need to remove to prevent
+ // blank lines when printing the error.
+ var multiE *multierror.Error
+ multiE = multierror.Append(multiE, errs...)
+ return errors.New(strings.TrimSpace(multiE.ErrorOrNil().Error()))
+}
+
+// ErrorsToString converts the slice of errors into a slice of corresponding
+// error messages.
+func ErrorsToStrings(errs []error) []string {
+ strErrs := make([]string, len(errs))
+ for i := range errs {
+ strErrs[i] = errs[i].Error()
+ }
+ return strErrs
+}
+
+// StringsToErrors converts a slice of error messages into a slice of
+// corresponding errors.
+func StringsToErrors(strErrs []string) []error {
+ errs := make([]error, len(strErrs))
+ for i := range strErrs {
+ errs[i] = errors.New(strErrs[i])
+ }
+ return errs
+}
+
// SyncQuiet syncs a file and logs any error. Should only be used within
// a defer.
func SyncQuiet(f *os.File) {
diff --git a/pkg/spec/namespaces.go b/pkg/spec/namespaces.go
index aebc90f68..40364b054 100644
--- a/pkg/spec/namespaces.go
+++ b/pkg/spec/namespaces.go
@@ -17,6 +17,10 @@ import (
"github.com/sirupsen/logrus"
)
+// DefaultKernelNamespaces is a comma-separated list of default kernel
+// namespaces.
+const DefaultKernelNamespaces = "cgroup,ipc,net,uts"
+
// ToCreateOptions converts the input to a slice of container create options.
func (c *NetworkConfig) ToCreateOptions(runtime *libpod.Runtime, userns *UserConfig) ([]libpod.CtrCreateOption, error) {
var portBindings []ocicni.PortMapping
@@ -154,9 +158,9 @@ func (c *NetworkConfig) ConfigureGenerator(g *generate.Generator) error {
}
if c.PublishAll {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
+ g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue
} else {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
+ g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse
}
return nil
diff --git a/pkg/spec/security.go b/pkg/spec/security.go
index 0f8d36f00..6d74e97e6 100644
--- a/pkg/spec/security.go
+++ b/pkg/spec/security.go
@@ -6,6 +6,7 @@ import (
"github.com/containers/common/pkg/capabilities"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/util"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
@@ -184,11 +185,11 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon
}
switch splitOpt[0] {
case "label":
- configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1]
+ configSpec.Annotations[define.InspectAnnotationLabel] = splitOpt[1]
case "seccomp":
- configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1]
+ configSpec.Annotations[define.InspectAnnotationSeccomp] = splitOpt[1]
case "apparmor":
- configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1]
+ configSpec.Annotations[define.InspectAnnotationApparmor] = splitOpt[1]
}
}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 41ed5f1f0..77e92ae29 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -7,6 +7,7 @@ import (
cconfig "github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/sysinfo"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/env"
"github.com/containers/libpod/pkg/rootless"
@@ -436,29 +437,29 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
if config.CidFile != "" {
- configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile
+ configSpec.Annotations[define.InspectAnnotationCIDFile] = config.CidFile
}
if config.Rm {
- configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseFalse
}
if len(config.VolumesFrom) > 0 {
- configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",")
+ configSpec.Annotations[define.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",")
}
if config.Security.Privileged {
- configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseFalse
}
if config.Init {
- configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseFalse
}
return configSpec, nil
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 14836035d..f3aaf96bf 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -85,7 +85,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
if err != nil {
return nil, err
}
- options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName))
+ imgName := s.Image
+ names := newImage.Names()
+ if len(names) > 0 {
+ imgName = names[0]
+ }
+ options = append(options, libpod.WithRootFSFromImage(newImage.ID(), imgName, s.Image))
}
if err := s.Validate(); err != nil {
return nil, errors.Wrap(err, "invalid config provided")
@@ -96,7 +101,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return nil, err
}
- opts, err := createContainerOptions(rt, s, pod, finalVolumes)
+ opts, err := createContainerOptions(ctx, rt, s, pod, finalVolumes, newImage)
if err != nil {
return nil, err
}
@@ -115,7 +120,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
return rt.NewContainer(ctx, runtimeSpec, options...)
}
-func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume) ([]libpod.CtrCreateOption, error) {
+func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume, img *image.Image) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
@@ -134,7 +139,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
options = append(options, rt.WithPod(pod))
}
destinations := []string{}
- // // Take all mount and named volume destinations.
+ // Take all mount and named volume destinations.
for _, mount := range s.Mounts {
destinations = append(destinations, mount.Destination)
}
@@ -188,7 +193,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
- namespaceOptions, err := GenerateNamespaceOptions(s, rt, pod)
+ namespaceOptions, err := GenerateNamespaceOptions(ctx, s, rt, pod, img)
if err != nil {
return nil, err
}
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index 5c065cdda..138d9e0cd 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -1,12 +1,14 @@
package generate
import (
+ "context"
"os"
"strings"
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/util"
@@ -76,7 +78,7 @@ func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod)
// joining a pod.
// TODO: Consider grouping options that are not directly attached to a namespace
// elsewhere.
-func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) {
+func GenerateNamespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod, img *image.Image) ([]libpod.CtrCreateOption, error) {
toReturn := []libpod.CtrCreateOption{}
// If pod is not nil, get infra container.
@@ -204,7 +206,6 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod
}
// Net
- // TODO image ports
// TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we
// are in bridge mode.
postConfigureNetNS := !s.UserNS.IsHost()
@@ -221,9 +222,17 @@ func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod
}
toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr))
case specgen.Slirp:
- toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "slirp4netns", nil))
+ portMappings, err := createPortMappings(ctx, s, img)
+ if err != nil {
+ return nil, err
+ }
+ toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "slirp4netns", nil))
case specgen.Bridge:
- toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "bridge", s.CNINetworks))
+ portMappings, err := createPortMappings(ctx, s, img)
+ if err != nil {
+ return nil, err
+ }
+ toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "bridge", s.CNINetworks))
}
if s.UseImageHosts {
@@ -428,10 +437,10 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt
if g.Config.Annotations == nil {
g.Config.Annotations = make(map[string]string)
}
- if s.PublishImagePorts {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
+ if s.PublishExposedPorts {
+ g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue
} else {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
+ g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse
}
return nil
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 8136c0993..a2bb66a44 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -6,6 +6,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/specgen"
@@ -327,19 +328,19 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
//}
if s.Remove {
- configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseFalse
}
if len(s.VolumesFrom) > 0 {
- configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",")
+ configSpec.Annotations[define.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",")
}
if s.Privileged {
- configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue
+ configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseTrue
} else {
- configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse
+ configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseFalse
}
// TODO Init might not make it into the specgen and therefore is not available here. We should deal
diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go
index babfba9bc..df5775f8b 100644
--- a/pkg/specgen/generate/pod_create.go
+++ b/pkg/specgen/generate/pod_create.go
@@ -83,7 +83,11 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er
options = append(options, libpod.WithPodUseImageHosts())
}
if len(p.PortMappings) > 0 {
- options = append(options, libpod.WithInfraContainerPorts(p.PortMappings))
+ ports, _, _, err := parsePortMapping(p.PortMappings)
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, libpod.WithInfraContainerPorts(ports))
}
options = append(options, libpod.WithPodCgroups())
return options, nil
diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go
new file mode 100644
index 000000000..91c8e68d1
--- /dev/null
+++ b/pkg/specgen/generate/ports.go
@@ -0,0 +1,333 @@
+package generate
+
+import (
+ "context"
+ "net"
+ "strconv"
+ "strings"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ protoTCP = "tcp"
+ protoUDP = "udp"
+ protoSCTP = "sctp"
+)
+
+// Parse port maps to OCICNI port mappings.
+// Returns a set of OCICNI port mappings, and maps of utilized container and
+// host ports.
+func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) {
+ // First, we need to validate the ports passed in the specgen, and then
+ // convert them into CNI port mappings.
+ finalMappings := []ocicni.PortMapping{}
+
+ // To validate, we need two maps: one for host ports, one for container
+ // ports.
+ // Each is a map of protocol to map of IP address to map of port to
+ // port (for hostPortValidate, it's host port to container port;
+ // for containerPortValidate, container port to host port.
+ // These will ensure no collisions.
+ hostPortValidate := make(map[string]map[string]map[uint16]uint16)
+ containerPortValidate := make(map[string]map[string]map[uint16]uint16)
+
+ // Initialize the first level of maps (we can't really guess keys for
+ // the rest).
+ for _, proto := range []string{protoTCP, protoUDP, protoSCTP} {
+ hostPortValidate[proto] = make(map[string]map[uint16]uint16)
+ containerPortValidate[proto] = make(map[string]map[uint16]uint16)
+ }
+
+ // Iterate through all port mappings, generating OCICNI PortMapping
+ // structs and validating there is no overlap.
+ for _, port := range portMappings {
+ // First, check proto
+ protocols, err := checkProtocol(port.Protocol, true)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+
+ // Validate host IP
+ hostIP := port.HostIP
+ if hostIP == "" {
+ hostIP = "0.0.0.0"
+ }
+ if ip := net.ParseIP(hostIP); ip == nil {
+ return nil, nil, nil, errors.Errorf("invalid IP address %s in port mapping", port.HostIP)
+ }
+
+ // Validate port numbers and range.
+ len := port.Range
+ if len == 0 {
+ len = 1
+ }
+ containerPort := port.ContainerPort
+ if containerPort == 0 {
+ return nil, nil, nil, errors.Errorf("container port number must be non-0")
+ }
+ hostPort := port.HostPort
+ if hostPort == 0 {
+ hostPort = containerPort
+ }
+ if uint32(len-1)+uint32(containerPort) > 65535 {
+ return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number")
+ }
+ if uint32(len-1)+uint32(hostPort) > 65536 {
+ return nil, nil, nil, errors.Errorf("host port range exceeds maximum allowable port number")
+ }
+
+ // Iterate through ports, populating maps to check for conflicts
+ // and generating CNI port mappings.
+ for _, p := range protocols {
+ hostIPMap := hostPortValidate[p]
+ ctrIPMap := containerPortValidate[p]
+
+ hostPortMap, ok := hostIPMap[hostIP]
+ if !ok {
+ hostPortMap = make(map[uint16]uint16)
+ hostIPMap[hostIP] = hostPortMap
+ }
+ ctrPortMap, ok := ctrIPMap[hostIP]
+ if !ok {
+ ctrPortMap = make(map[uint16]uint16)
+ ctrIPMap[hostIP] = ctrPortMap
+ }
+
+ // Iterate through all port numbers in the requested
+ // range.
+ var index uint16
+ for index = 0; index < len; index++ {
+ cPort := containerPort + index
+ hPort := hostPort + index
+
+ if cPort == 0 || hPort == 0 {
+ return nil, nil, nil, errors.Errorf("host and container ports cannot be 0")
+ }
+
+ testCPort := ctrPortMap[cPort]
+ if testCPort != 0 && testCPort != hPort {
+ // This is an attempt to redefine a port
+ return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p)
+ }
+ ctrPortMap[cPort] = hPort
+
+ testHPort := hostPortMap[hPort]
+ if testHPort != 0 && testHPort != cPort {
+ return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p)
+ }
+ hostPortMap[hPort] = cPort
+
+ // If we have an exact duplicate, just continue
+ if testCPort == hPort && testHPort == cPort {
+ continue
+ }
+
+ // We appear to be clear. Make an OCICNI port
+ // struct.
+ // Don't use hostIP - we want to preserve the
+ // empty string hostIP by default for compat.
+ cniPort := ocicni.PortMapping{
+ HostPort: int32(hPort),
+ ContainerPort: int32(cPort),
+ Protocol: p,
+ HostIP: port.HostIP,
+ }
+ finalMappings = append(finalMappings, cniPort)
+ }
+ }
+ }
+
+ return finalMappings, containerPortValidate, hostPortValidate, nil
+}
+
+// Make final port mappings for the container
+func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, img *image.Image) ([]ocicni.PortMapping, error) {
+ finalMappings, containerPortValidate, hostPortValidate, err := parsePortMapping(s.PortMappings)
+ if err != nil {
+ return nil, err
+ }
+
+ // If not publishing exposed ports, or if we are publishing and there is
+ // nothing to publish - then just return the port mappings we've made so
+ // far.
+ if !s.PublishExposedPorts || (len(s.Expose) == 0 && img == nil) {
+ return finalMappings, nil
+ }
+
+ logrus.Debugf("Adding exposed ports")
+
+ // We need to merge s.Expose into image exposed ports
+ expose := make(map[uint16]string)
+ for k, v := range s.Expose {
+ expose[k] = v
+ }
+ if img != nil {
+ inspect, err := img.InspectNoSize(ctx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error inspecting image to get exposed ports")
+ }
+ for imgExpose := range inspect.Config.ExposedPorts {
+ // Expose format is portNumber[/protocol]
+ splitExpose := strings.SplitN(imgExpose, "/", 2)
+ num, err := strconv.Atoi(splitExpose[0])
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to convert image EXPOSE statement %q to port number", imgExpose)
+ }
+ if num > 65535 || num < 1 {
+ return nil, errors.Errorf("%d from image EXPOSE statement %q is not a valid port number", num, imgExpose)
+ }
+ // No need to validate protocol, we'll do it below.
+ if len(splitExpose) == 1 {
+ expose[uint16(num)] = "tcp"
+ } else {
+ expose[uint16(num)] = splitExpose[1]
+ }
+ }
+ }
+
+ // There's been a request to expose some ports. Let's do that.
+ // Start by figuring out what needs to be exposed.
+ // This is a map of container port number to protocols to expose.
+ toExpose := make(map[uint16][]string)
+ for port, proto := range expose {
+ // Validate protocol first
+ protocols, err := checkProtocol(proto, false)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port)
+ }
+
+ if port == 0 {
+ return nil, errors.Errorf("cannot expose 0 as it is not a valid port number")
+ }
+
+ // Check to see if the port is already present in existing
+ // mappings.
+ for _, p := range protocols {
+ ctrPortMap, ok := containerPortValidate[p]["0.0.0.0"]
+ if !ok {
+ ctrPortMap = make(map[uint16]uint16)
+ containerPortValidate[p]["0.0.0.0"] = ctrPortMap
+ }
+
+ if portNum := ctrPortMap[port]; portNum == 0 {
+ // We want to expose this port for this protocol
+ exposeProto, ok := toExpose[port]
+ if !ok {
+ exposeProto = []string{}
+ }
+ exposeProto = append(exposeProto, p)
+ toExpose[port] = exposeProto
+ }
+ }
+ }
+
+ // We now have a final list of ports that we want exposed.
+ // Let's find empty, unallocated host ports for them.
+ for port, protocols := range toExpose {
+ for _, p := range protocols {
+ // Find an open port on the host.
+ // I see a faint possibility that this will infinite
+ // loop trying to find a valid open port, so I've
+ // included a max-tries counter.
+ hostPort := 0
+ tries := 15
+ for hostPort == 0 && tries > 0 {
+ // We can't select a specific protocol, which is
+ // unfortunate for the UDP case.
+ candidate, err := getRandomPort()
+ if err != nil {
+ return nil, err
+ }
+
+ // Check if the host port is already bound
+ hostPortMap, ok := hostPortValidate[p]["0.0.0.0"]
+ if !ok {
+ hostPortMap = make(map[uint16]uint16)
+ hostPortValidate[p]["0.0.0.0"] = hostPortMap
+ }
+
+ if checkPort := hostPortMap[uint16(candidate)]; checkPort != 0 {
+ // Host port is already allocated, try again
+ tries--
+ continue
+ }
+
+ hostPortMap[uint16(candidate)] = port
+ hostPort = candidate
+ logrus.Debugf("Mapping exposed port %d/%s to host port %d", port, p, hostPort)
+
+ // Make a CNI port mapping
+ cniPort := ocicni.PortMapping{
+ HostPort: int32(candidate),
+ ContainerPort: int32(port),
+ Protocol: p,
+ HostIP: "",
+ }
+ finalMappings = append(finalMappings, cniPort)
+ }
+ if tries == 0 && hostPort == 0 {
+ // We failed to find an open port.
+ return nil, errors.Errorf("failed to find an open port to expose container port %d on the host", port)
+ }
+ }
+ }
+
+ return finalMappings, nil
+}
+
+// Check a string to ensure it is a comma-separated set of valid protocols
+func checkProtocol(protocol string, allowSCTP bool) ([]string, error) {
+ protocols := make(map[string]struct{})
+ splitProto := strings.Split(protocol, ",")
+ // Don't error on duplicates - just deduplicate
+ for _, p := range splitProto {
+ switch p {
+ case protoTCP, "":
+ protocols[protoTCP] = struct{}{}
+ case protoUDP:
+ protocols[protoUDP] = struct{}{}
+ case protoSCTP:
+ if !allowSCTP {
+ return nil, errors.Errorf("protocol SCTP is not allowed for exposed ports")
+ }
+ protocols[protoSCTP] = struct{}{}
+ default:
+ return nil, errors.Errorf("unrecognized protocol %q in port mapping", p)
+ }
+ }
+
+ finalProto := []string{}
+ for p := range protocols {
+ finalProto = append(finalProto, p)
+ }
+
+ // This shouldn't be possible, but check anyways
+ if len(finalProto) == 0 {
+ return nil, errors.Errorf("no valid protocols specified for port mapping")
+ }
+
+ return finalProto, nil
+}
+
+// Find a random, open port on the host
+func getRandomPort() (int, error) {
+ l, err := net.Listen("tcp", ":0")
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to get free TCP port")
+ }
+ defer l.Close()
+ _, randomPort, err := net.SplitHostPort(l.Addr().String())
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to determine free port")
+ }
+ rp, err := strconv.Atoi(randomPort)
+ if err != nil {
+ return 0, errors.Wrapf(err, "unable to convert random port to int")
+ }
+ return rp, nil
+}
diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go
index 3f830014d..682f3f215 100644
--- a/pkg/specgen/podspecgen.go
+++ b/pkg/specgen/podspecgen.go
@@ -2,8 +2,6 @@ package specgen
import (
"net"
-
- "github.com/cri-o/ocicni/pkg/ocicni"
)
// PodBasicConfig contains basic configuration options for pods.
@@ -79,7 +77,7 @@ type PodNetworkConfig struct {
// container, this will forward the ports to the entire pod.
// Only available if NetNS is set to Bridge or Slirp.
// Optional.
- PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"`
+ PortMappings []PortMapping `json:"portmappings,omitempty"`
// CNINetworks is a list of CNI networks that the infra container will
// join. As, by default, containers share their network with the infra
// container, these networks will effectively be joined by the
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index 20c8f8800..4ad6dd6fb 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -6,7 +6,6 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/storage"
- "github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
@@ -141,10 +140,6 @@ type ContainerStorageConfig struct {
// Conflicts with Rootfs.
// At least one of Image or Rootfs must be specified.
Image string `json:"image"`
- // RawImageName is the unprocessed and not-normalized user-specified image
- // name. One use case for having this data at hand are auto-updates where
- // the _exact_ user input is needed in order to look-up the correct image.
- RawImageName string `json:"raw_image_name,omitempty"`
// Rootfs is the path to a directory that will be used as the
// container's root filesystem. No modification will be made to the
// directory, it will be directly mounted into the container as root.
@@ -306,11 +301,23 @@ type ContainerNetworkConfig struct {
// PortBindings is a set of ports to map into the container.
// Only available if NetNS is set to bridge or slirp.
// Optional.
- PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"`
- // PublishImagePorts will publish ports specified in the image to random
- // ports outside.
- // Requires Image to be set.
- PublishImagePorts bool `json:"publish_image_ports,omitempty"`
+ PortMappings []PortMapping `json:"portmappings,omitempty"`
+ // PublishExposedPorts will publish ports specified in the image to
+ // random unused ports (guaranteed to be above 1024) on the host.
+ // This is based on ports set in Expose below, and any ports specified
+ // by the Image (if one is given).
+ // Only available if NetNS is set to Bridge or Slirp.
+ PublishExposedPorts bool `json:"publish_image_ports,omitempty"`
+ // Expose is a number of ports that will be forwarded to the container
+ // if PublishExposedPorts is set.
+ // Expose is a map of uint16 (port number) to a string representing
+ // protocol. Allowed protocols are "tcp", "udp", and "sctp", or some
+ // combination of the three separated by commas.
+ // If protocol is set to "" we will assume TCP.
+ // Only available if NetNS is set to Bridge or Slirp, and
+ // PublishExposedPorts is set.
+ // Optional.
+ Expose map[uint16]string `json:"expose,omitempty"`
// CNINetworks is a list of CNI networks to join the container to.
// If this list is empty, the default CNI network will be joined
// instead. If at least one entry is present, we will not join the
@@ -410,6 +417,35 @@ type NamedVolume struct {
Options []string
}
+// PortMapping is one or more ports that will be mapped into the container.
+type PortMapping struct {
+ // HostIP is the IP that we will bind to on the host.
+ // If unset, assumed to be 0.0.0.0 (all interfaces).
+ HostIP string `json:"host_ip,omitempty"`
+ // ContainerPort is the port number that will be exposed from the
+ // container.
+ // Mandatory.
+ ContainerPort uint16 `json:"container_port"`
+ // HostPort is the port number that will be forwarded from the host into
+ // the container.
+ // If omitted, will be assumed to be identical to
+ HostPort uint16 `json:"host_port,omitempty"`
+ // Range is the number of ports that will be forwarded, starting at
+ // HostPort and ContainerPort and counting up.
+ // This is 1-indexed, so 1 is assumed to be a single port (only the
+ // Hostport:Containerport mapping will be added), 2 is two ports (both
+ // Hostport:Containerport and Hostport+1:Containerport+1), etc.
+ // If unset, assumed to be 1 (a single port).
+ // Both hostport + range and containerport + range must be less than
+ // 65536.
+ Range uint16 `json:"range,omitempty"`
+ // Protocol is the protocol forward.
+ // Must be either "tcp", "udp", and "sctp", or some combination of these
+ // separated by commas.
+ // If unset, assumed to be TCP.
+ Protocol string `json:"protocol,omitempty"`
+}
+
// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs
func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator {
csc := ContainerStorageConfig{}
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 8fba07c18..258cb8652 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -331,7 +331,7 @@ func (i *VarlinkAPI) GetContainerStats(call iopodman.VarlinkCall, name string) e
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
- containerStats, err := ctr.GetContainerStats(&libpod.ContainerStats{})
+ containerStats, err := ctr.GetContainerStats(&define.ContainerStats{})
if err != nil {
if errors.Cause(err) == define.ErrCtrStateInvalid {
return call.ReplyNoContainerRunning()
diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go
index 5a9360447..aeb3cdcb8 100644
--- a/pkg/varlinkapi/pods.go
+++ b/pkg/varlinkapi/pods.go
@@ -8,12 +8,12 @@ import (
"strconv"
"syscall"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ iopodman "github.com/containers/libpod/pkg/varlink"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
-
- "github.com/containers/libpod/libpod"
- iopodman "github.com/containers/libpod/pkg/varlink"
)
// CreatePod ...
@@ -263,7 +263,7 @@ func (i *VarlinkAPI) GetPodStats(call iopodman.VarlinkCall, name string) error {
if err != nil {
return call.ReplyPodNotFound(name, err.Error())
}
- prevStats := make(map[string]*libpod.ContainerStats)
+ prevStats := make(map[string]*define.ContainerStats)
podStats, err := pod.GetPodStats(prevStats)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
diff --git a/pkg/varlinkapi/remote_client.go b/pkg/varlinkapi/remote_client.go
index a16d11dec..88e410de6 100644
--- a/pkg/varlinkapi/remote_client.go
+++ b/pkg/varlinkapi/remote_client.go
@@ -3,14 +3,14 @@
package varlinkapi
import (
- "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
iopodman "github.com/containers/libpod/pkg/varlink"
)
// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod
// container stats
-func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats {
- cstats := libpod.ContainerStats{
+func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) define.ContainerStats {
+ cstats := define.ContainerStats{
ContainerID: stats.Id,
Name: stats.Name,
CPU: stats.Cpu,
diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go
index 10742a0e8..1041b30bb 100644
--- a/test/e2e/create_test.go
+++ b/test/e2e/create_test.go
@@ -342,4 +342,53 @@ var _ = Describe("Podman create", func() {
Expect(ok2).To(BeTrue())
Expect(val2).To(Equal("bar"))
})
+
+ It("podman create with --restart=on-failure:5 parses correctly", func() {
+ ctrName := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "--restart", "on-failure:5", "--name", ctrName, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ inspect := podmanTest.Podman([]string{"inspect", ctrName})
+ inspect.WaitWithDefaultTimeout()
+ data := inspect.InspectContainerToJSON()
+ Expect(len(data)).To(Equal(1))
+ Expect(data[0].HostConfig.RestartPolicy.Name).To(Equal("on-failure"))
+ Expect(data[0].HostConfig.RestartPolicy.MaximumRetryCount).To(Equal(uint(5)))
+ })
+
+ It("podman create with --restart-policy=always:5 fails", func() {
+ session := podmanTest.Podman([]string{"create", "-t", "--restart", "always:5", ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman create with -m 1000000 sets swap to 2000000", func() {
+ numMem := 1000000
+ ctrName := "testCtr"
+ session := podmanTest.Podman([]string{"create", "-t", "-m", fmt.Sprintf("%db", numMem), "--name", ctrName, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ inspect := podmanTest.Podman([]string{"inspect", ctrName})
+ inspect.WaitWithDefaultTimeout()
+ data := inspect.InspectContainerToJSON()
+ Expect(len(data)).To(Equal(1))
+ Expect(data[0].HostConfig.MemorySwap).To(Equal(int64(2 * numMem)))
+ })
+
+ It("podman create --cpus 5 sets nanocpus", func() {
+ numCpus := 5
+ nanoCPUs := numCpus * 1000000000
+ ctrName := "testCtr"
+ session := podmanTest.Podman([]string{"create", "-t", "--cpus", fmt.Sprintf("%d", numCpus), "--name", ctrName, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ inspect := podmanTest.Podman([]string{"inspect", ctrName})
+ inspect.WaitWithDefaultTimeout()
+ data := inspect.InspectContainerToJSON()
+ Expect(len(data)).To(Equal(1))
+ Expect(data[0].HostConfig.NanoCpus).To(Equal(int64(nanoCPUs)))
+ })
})
diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go
index e4f487634..389f2c822 100644
--- a/test/e2e/generate_kube_test.go
+++ b/test/e2e/generate_kube_test.go
@@ -21,7 +21,6 @@ var _ = Describe("Podman generate kube", func() {
)
BeforeEach(func() {
- Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go
index 9b5a24771..be6919bdc 100644
--- a/test/e2e/manifest_test.go
+++ b/test/e2e/manifest_test.go
@@ -98,4 +98,20 @@ var _ = Describe("Podman manifest", func() {
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring(`"os": "bar"`))
})
+
+ It("podman manifest annotate", func() {
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "add", "foo", imageListInstance})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "annotate", "--arch", "bar", "foo", imageListARM64InstanceDigest})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "inspect", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(`"architecture": "bar"`))
+ })
})
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 16f7af55e..9daf266b8 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -217,7 +217,6 @@ var _ = Describe("Podman generate kube", func() {
)
BeforeEach(func() {
- Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index 5946f3b7a..375930948 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -19,7 +19,6 @@ var _ = Describe("Podman run networking", func() {
)
BeforeEach(func() {
- Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
@@ -65,6 +64,110 @@ var _ = Describe("Podman run networking", func() {
Expect(results.OutputToString()).To(ContainSubstring("223"))
})
+ It("podman run -p 80", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "-p", "80", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ })
+
+ It("podman run -p 8080:80", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "-p", "8080:80", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ })
+
+ It("podman run -p 80/udp", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "-p", "80/udp", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ })
+
+ It("podman run -p 127.0.0.1:8080:80", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1:8080:80", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("127.0.0.1"))
+ })
+
+ It("podman run -p 127.0.0.1:8080:80/udp", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1:8080:80/udp", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("127.0.0.1"))
+ })
+
+ It("podman run --expose 80 -P", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "--expose", "80", "-P", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Not(Equal(int32(0))))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ })
+
+ It("podman run --expose 80/udp -P", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "--expose", "80/udp", "-P", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Not(Equal(int32(0))))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ })
+
+ It("podman run --expose 80 -p 80", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "--expose", "80", "-p", "80", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
+ Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal(""))
+ })
+
It("podman run network expose host port 80 to container port 8000", func() {
SkipIfRootless()
session := podmanTest.Podman([]string{"run", "-dt", "-p", "80:8000", ALPINE, "/bin/sh"})
diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go
index 32f7cc520..762417a17 100644
--- a/test/e2e/stats_test.go
+++ b/test/e2e/stats_test.go
@@ -21,7 +21,6 @@ var _ = Describe("Podman stats", func() {
)
BeforeEach(func() {
- Skip(v2fail)
cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
Expect(err).To(BeNil())
diff --git a/test/e2e/system_df_test.go b/test/e2e/system_df_test.go
index 5f261fcbf..bbbdf30b0 100644
--- a/test/e2e/system_df_test.go
+++ b/test/e2e/system_df_test.go
@@ -20,7 +20,6 @@ var _ = Describe("podman system df", func() {
)
BeforeEach(func() {
- Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/system_reset_test.go b/test/e2e/system_reset_test.go
index f17747648..e5ce69739 100644
--- a/test/e2e/system_reset_test.go
+++ b/test/e2e/system_reset_test.go
@@ -17,7 +17,6 @@ var _ = Describe("podman system reset", func() {
)
BeforeEach(func() {
- Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/systemd_test.go b/test/e2e/systemd_test.go
index c56fb00f2..1275670eb 100644
--- a/test/e2e/systemd_test.go
+++ b/test/e2e/systemd_test.go
@@ -23,7 +23,6 @@ var _ = Describe("Podman systemd", func() {
)
BeforeEach(func() {
- Skip(v2fail)
SkipIfRootless()
tempdir, err = CreateTempDirInTempDir()
if err != nil {
@@ -86,6 +85,7 @@ WantedBy=multi-user.target
cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
Expect(err).To(BeNil())
if cgroupsv2 {
+ // TODO: Find a way to enable this for v2
Skip("systemd test does not work in cgroups V2 mode yet")
}
diff --git a/test/system/015-help.bats b/test/system/015-help.bats
index 6c3d617dc..42d3bd3ec 100644
--- a/test/system/015-help.bats
+++ b/test/system/015-help.bats
@@ -28,13 +28,20 @@ function check_help() {
local subcommands_found=0
for cmd in $(podman_commands "$@"); do
- dprint "podman $@ $cmd --help"
+ # Human-readable podman command string, with multiple spaces collapsed
+ command_string="podman $* $cmd"
+ command_string=${command_string// / } # 'podman x' -> 'podman x'
+
+ dprint "$command_string --help"
run_podman "$@" $cmd --help
# The line immediately after 'Usage:' gives us a 1-line synopsis
usage=$(echo "$output" | grep -A1 '^Usage:' | tail -1)
[ -n "$usage" ] || die "podman $cmd: no Usage message found"
+ # e.g. 'podman ps' should not show 'podman container ps' in usage
+ is "$usage" " $command_string .*" "Usage string matches command"
+
# If usage ends in '[command]', recurse into subcommands
if expr "$usage" : '.*\[command\]$' >/dev/null; then
subcommands_found=$(expr $subcommands_found + 1)
@@ -46,10 +53,10 @@ function check_help() {
# Confirm that by running with 'invalid-arg' and expecting failure.
if expr "$usage" : '.*\[flags\]$' >/dev/null; then
if [ "$cmd" != "help" ]; then
- dprint "podman $@ $cmd invalid-arg"
+ dprint "$command_string invalid-arg"
run_podman 125 "$@" $cmd invalid-arg
is "$output" "Error: .* takes no arguments" \
- "'podman $@ $cmd' with extra (invalid) arguments"
+ "'$command_string' with extra (invalid) arguments"
fi
fi
@@ -69,10 +76,10 @@ function check_help() {
# The </dev/null protects us from 'podman login' which will
# try to read username/password from stdin.
- dprint "podman $@ $cmd (without required args)"
+ dprint "$command_string (without required args)"
run_podman 125 "$@" $cmd </dev/null
is "$output" "Error:.* \(require\|specif\|must\|provide\|need\|choose\|accepts\)" \
- "'podman $@ $cmd' without required arg"
+ "'$command_string' without required arg"
fi
count=$(expr $count + 1)
diff --git a/utils/utils.go b/utils/utils.go
index cf58ca3fb..27ce1821d 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -6,6 +6,7 @@ import (
"io"
"os"
"os/exec"
+ "strconv"
"strings"
"github.com/containers/storage/pkg/archive"
@@ -125,3 +126,21 @@ func Tar(source string) (io.ReadCloser, error) {
logrus.Debugf("creating tarball of %s", source)
return archive.Tar(source, archive.Uncompressed)
}
+
+// RemoveScientificNotationFromFloat returns a float without any
+// scientific notation if the number has any.
+// golang does not handle conversion of float64s that have scientific
+// notation in them and otherwise stinks. please replace this if you have
+// a better implementation.
+func RemoveScientificNotationFromFloat(x float64) (float64, error) {
+ bigNum := strconv.FormatFloat(x, 'g', -1, 64)
+ breakPoint := strings.IndexAny(bigNum, "Ee")
+ if breakPoint > 0 {
+ bigNum = bigNum[:breakPoint]
+ }
+ result, err := strconv.ParseFloat(bigNum, 64)
+ if err != nil {
+ return x, errors.Wrapf(err, "unable to remove scientific number from calculations")
+ }
+ return result, nil
+}