diff options
author | Daniel J Walsh <dwalsh@redhat.com> | 2017-12-15 16:58:36 -0500 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2017-12-18 16:46:05 +0000 |
commit | 5770dc2640c216525ab84031e3712fcc46b3b087 (patch) | |
tree | 8a1c5c4e4a6ce6a35a3767247623a62bfd698f77 /cmd/kpod | |
parent | de3468e120d489d046c08dad72ba2262e222ccb1 (diff) | |
download | podman-5770dc2640c216525ab84031e3712fcc46b3b087.tar.gz podman-5770dc2640c216525ab84031e3712fcc46b3b087.tar.bz2 podman-5770dc2640c216525ab84031e3712fcc46b3b087.zip |
Rename all references to kpod to podman
The decision is in, kpod is going to be named podman.
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
Closes: #145
Approved by: umohnani8
Diffstat (limited to 'cmd/kpod')
46 files changed, 0 insertions, 8191 deletions
diff --git a/cmd/kpod/README.md b/cmd/kpod/README.md deleted file mode 100644 index 7a79e4893..000000000 --- a/cmd/kpod/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# kpod - Simple debugging tool for pods and images -kpod is a simple client only tool to help with debugging issues when daemons such as CRI runtime and the kubelet are not responding or -failing. A shared API layer could be created to share code between the daemon and kpod. kpod does not require any daemon running. kpod -utilizes the same underlying components that crio uses i.e. containers/image, container/storage, oci-runtime-tool/generate, runc or -any other OCI compatible runtime. kpod shares state with crio and so has the capability to debug pods/images created by crio. - -## Use cases -1. List pods. -2. Launch simple pods (that require no daemon support). -3. Exec commands in a container in a pod. -4. Launch additional containers in a pod. -5. List images. -6. Remove images not in use. -7. Pull images. -8. Check image size. -9. Report pod disk resource usage. diff --git a/cmd/kpod/attach.go b/cmd/kpod/attach.go deleted file mode 100644 index 700023abe..000000000 --- a/cmd/kpod/attach.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "sync" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -var ( - attachFlags = []cli.Flag{ - cli.StringFlag{ - Name: "detach-keys", - Usage: "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _.", - }, - cli.BoolFlag{ - Name: "no-stdin", - Usage: "Do not attach STDIN. The default is false.", - }, - } - attachDescription = "The kpod attach command allows you to attach to a running container using the container's ID or name, either to view its ongoing output or to control it interactively." - attachCommand = cli.Command{ - Name: "attach", - Usage: "Attach to a running container", - Description: attachDescription, - Flags: attachFlags, - Action: attachCmd, - ArgsUsage: "", - } -) - -func attachCmd(c *cli.Context) error { - args := c.Args() - if err := validateFlags(c, attachFlags); err != nil { - return err - } - - if len(c.Args()) < 1 || len(c.Args()) > 1 { - return errors.Errorf("attach requires the name or id of one running container") - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.Shutdown(false) - - ctr, err := runtime.LookupContainer(args[0]) - - if err != nil { - return errors.Wrapf(err, "unable to exec into %s", args[0]) - } - - conState, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "unable to determine state of %s", args[0]) - } - if conState != libpod.ContainerStateRunning { - return errors.Errorf("you can only attach to running containers") - } - // Create a bool channel to track that the console socket attach - // is successful. - attached := make(chan bool) - // Create a waitgroup so we can sync and wait for all goroutines - // to finish before exiting main - var wg sync.WaitGroup - - // We increment the wg counter because we need to do the attach - wg.Add(1) - // Attach to the running container - go func() { - logrus.Debugf("trying to attach to the container %s", ctr.ID()) - defer wg.Done() - if err := ctr.Attach(c.Bool("no-stdin"), c.String("detach-keys"), attached); err != nil { - logrus.Errorf("unable to attach to container %s: %q", ctr.ID(), err) - } - }() - if !<-attached { - return errors.Errorf("unable to attach to container %s", ctr.ID()) - } - wg.Wait() - - return nil -} diff --git a/cmd/kpod/common.go b/cmd/kpod/common.go deleted file mode 100644 index 99685107b..000000000 --- a/cmd/kpod/common.go +++ /dev/null @@ -1,438 +0,0 @@ -package main - -import ( - "os" - "reflect" - "regexp" - "strings" - - "github.com/containers/storage" - "github.com/fatih/camelcase" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libkpod" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -var ( - stores = make(map[storage.Store]struct{}) -) - -const crioConfigPath = "/etc/crio/crio.conf" - -func getRuntime(c *cli.Context) (*libpod.Runtime, error) { - - config, err := getConfig(c) - if err != nil { - return nil, errors.Wrapf(err, "could not get config") - } - - options := storage.DefaultStoreOptions - options.GraphRoot = config.Root - options.RunRoot = config.RunRoot - options.GraphDriverName = config.Storage - options.GraphDriverOptions = config.StorageOptions - - return libpod.NewRuntime(libpod.WithStorageConfig(options), libpod.WithConmonPath(config.Conmon), libpod.WithOCIRuntime(config.Runtime), libpod.WithCNIConfigDir(config.NetworkDir)) -} - -func shutdownStores() { - for store := range stores { - if _, err := store.Shutdown(false); err != nil { - break - } - } -} - -func getConfig(c *cli.Context) (*libkpod.Config, error) { - config := libkpod.DefaultConfig() - var configFile string - if c.GlobalIsSet("config") { - configFile = c.GlobalString("config") - } else if _, err := os.Stat(crioConfigPath); err == nil { - configFile = crioConfigPath - } - // load and merge the configfile from the commandline or use - // the default crio config file - if configFile != "" { - err := config.UpdateFromFile(configFile) - if err != nil { - return config, err - } - } - if c.GlobalIsSet("root") { - config.Root = c.GlobalString("root") - } - if c.GlobalIsSet("runroot") { - config.RunRoot = c.GlobalString("runroot") - } - if c.GlobalIsSet("conmon") { - config.Conmon = c.GlobalString("conmon") - } - if c.GlobalIsSet("storage-driver") { - config.Storage = c.GlobalString("storage-driver") - } - if c.GlobalIsSet("storage-opt") { - opts := c.GlobalStringSlice("storage-opt") - if len(opts) > 0 { - config.StorageOptions = opts - } - } - if c.GlobalIsSet("runtime") { - config.Runtime = c.GlobalString("runtime") - } - if c.GlobalIsSet("cni-config-dir") { - config.NetworkDir = c.GlobalString("cni-config-dir") - } - return config, nil -} - -func splitCamelCase(src string) string { - entries := camelcase.Split(src) - return strings.Join(entries, " ") -} - -// validateFlags searches for StringFlags or StringSlice flags that never had -// a value set. This commonly occurs when the CLI mistakenly takes the next -// option and uses it as a value. -func validateFlags(c *cli.Context, flags []cli.Flag) error { - for _, flag := range flags { - switch reflect.TypeOf(flag).String() { - case "cli.StringSliceFlag": - { - f := flag.(cli.StringSliceFlag) - name := strings.Split(f.Name, ",") - val := c.StringSlice(name[0]) - for _, v := range val { - if ok, _ := regexp.MatchString("^-.+", v); ok { - return errors.Errorf("option --%s requires a value", name[0]) - } - } - } - case "cli.StringFlag": - { - f := flag.(cli.StringFlag) - name := strings.Split(f.Name, ",") - val := c.String(name[0]) - if ok, _ := regexp.MatchString("^-.+", val); ok { - return errors.Errorf("option --%s requires a value", name[0]) - } - } - } - } - return nil -} - -// Common flags shared between commands -var createFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "add-host", - Usage: "Add a custom host-to-IP mapping (host:ip) (default [])", - }, - cli.StringSliceFlag{ - Name: "attach, a", - Usage: "Attach to STDIN, STDOUT or STDERR (default [])", - }, - cli.StringFlag{ - Name: "blkio-weight", - Usage: "Block IO weight (relative weight) accepts a weight value between 10 and 1000.", - }, - cli.StringSliceFlag{ - Name: "blkio-weight-device", - Usage: "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)", - }, - cli.StringSliceFlag{ - Name: "cap-add", - Usage: "Add capabilities to the container", - }, - cli.StringSliceFlag{ - Name: "cap-drop", - Usage: "Drop capabilities from the container", - }, - cli.StringFlag{ - Name: "cgroup-parent", - Usage: "Optional parent cgroup for the container", - }, - cli.StringFlag{ - Name: "cidfile", - Usage: "Write the container ID to the file", - }, - cli.Uint64Flag{ - Name: "cpu-period", - Usage: "Limit the CPU CFS (Completely Fair Scheduler) period", - }, - cli.Int64Flag{ - Name: "cpu-quota", - Usage: "Limit the CPU CFS (Completely Fair Scheduler) quota", - }, - cli.Uint64Flag{ - Name: "cpu-rt-period", - Usage: "Limit the CPU real-time period in microseconds", - }, - cli.Int64Flag{ - Name: "cpu-rt-runtime", - Usage: "Limit the CPU real-time runtime in microseconds", - }, - cli.Uint64Flag{ - Name: "cpu-shares", - Usage: "CPU shares (relative weight)", - }, - cli.StringFlag{ - Name: "cpus", - Usage: "Number of CPUs. The default is 0.000 which means no limit", - }, - cli.StringFlag{ - Name: "cpuset-cpus", - Usage: "CPUs in which to allow execution (0-3, 0,1)", - }, - cli.StringFlag{ - Name: "cpuset-mems", - Usage: "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.", - }, - cli.BoolFlag{ - Name: "detach, d", - Usage: "Run container in background and print container ID", - }, - cli.StringFlag{ - Name: "detach-keys", - Usage: "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`", - }, - cli.StringSliceFlag{ - Name: "device", - Usage: "Add a host device to the container (default [])", - }, - cli.StringSliceFlag{ - Name: "device-read-bps", - Usage: "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)", - }, - cli.StringSliceFlag{ - Name: "device-read-iops", - Usage: "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", - }, - cli.StringSliceFlag{ - Name: "device-write-bps", - Usage: "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)", - }, - cli.StringSliceFlag{ - Name: "device-write-iops", - Usage: "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", - }, - cli.StringSliceFlag{ - Name: "dns", - Usage: "Set custom DNS servers", - }, - cli.StringSliceFlag{ - Name: "dns-opt", - Usage: "Set custom DNS options", - }, - cli.StringSliceFlag{ - Name: "dns-search", - Usage: "Set custom DNS search domains", - }, - cli.StringFlag{ - Name: "entrypoint", - Usage: "Overwrite the default ENTRYPOINT of the image", - }, - cli.StringSliceFlag{ - Name: "env, e", - Usage: "Set environment variables in container", - }, - cli.StringSliceFlag{ - Name: "env-file", - Usage: "Read in a file of environment variables", - }, - cli.StringSliceFlag{ - Name: "expose", - Usage: "Expose a port or a range of ports (default [])", - }, - cli.StringSliceFlag{ - Name: "group-add", - Usage: "Add additional groups to join (default [])", - }, - cli.StringFlag{ - Name: "hostname", - Usage: "Set container hostname", - }, - cli.BoolFlag{ - Name: "interactive, i", - Usage: "Keep STDIN open even if not attached", - }, - cli.StringFlag{ - Name: "ip", - Usage: "Container IPv4 address (e.g. 172.23.0.9)", - }, - cli.StringFlag{ - Name: "ip6", - Usage: "Container IPv6 address (e.g. 2001:db8::1b99)", - }, - cli.StringFlag{ - Name: "ipc", - Usage: "IPC Namespace to use", - }, - cli.StringFlag{ - Name: "kernel-memory", - Usage: "Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)", - }, - cli.StringSliceFlag{ - Name: "label", - Usage: "Set metadata on container (default [])", - }, - cli.StringSliceFlag{ - Name: "label-file", - Usage: "Read in a line delimited file of labels (default [])", - }, - cli.StringSliceFlag{ - Name: "link-local-ip", - Usage: "Container IPv4/IPv6 link-local addresses (default [])", - }, - cli.StringFlag{ - Name: "log-driver", - Usage: "Logging driver for the container", - }, - cli.StringSliceFlag{ - Name: "log-opt", - Usage: "Logging driver options (default [])", - }, - cli.StringFlag{ - Name: "mac-address", - Usage: "Container MAC address (e.g. 92:d0:c6:0a:29:33)", - }, - cli.StringFlag{ - Name: "memory, m", - Usage: "Memory limit (format: <number>[<unit>], where unit = b, k, m or g)", - }, - cli.StringFlag{ - Name: "memory-reservation", - Usage: "Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)", - }, - cli.StringFlag{ - Name: "memory-swap", - Usage: "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", - }, - cli.Int64Flag{ - Name: "memory-swappiness", - Usage: "Tune container memory swappiness (0 to 100) (default -1)", - Value: -1, - }, - cli.StringFlag{ - Name: "name", - Usage: "Assign a name to the container", - }, - cli.StringFlag{ - Name: "net", - Usage: "Setup the network namespace", - }, - cli.StringFlag{ - Name: "network", - Usage: "Connect a container to a network (default 'default')", - }, - cli.StringSliceFlag{ - Name: "network-alias", - Usage: "Add network-scoped alias for the container (default [])", - }, - cli.BoolFlag{ - Name: "oom-kill-disable", - Usage: "Disable OOM Killer", - }, - cli.StringFlag{ - Name: "oom-score-adj", - Usage: "Tune the host's OOM preferences (-1000 to 1000)", - }, - cli.StringFlag{ - Name: "pid", - Usage: "PID Namespace to use", - }, - cli.Int64Flag{ - Name: "pids-limit", - Usage: "Tune container pids limit (set -1 for unlimited)", - }, - cli.StringFlag{ - Name: "pod", - Usage: "Run container in an existing pod", - }, - cli.BoolFlag{ - Name: "privileged", - Usage: "Give extended privileges to container", - }, - cli.StringSliceFlag{ - Name: "publish, p", - Usage: "Publish a container's port, or a range of ports, to the host (default [])", - }, - cli.BoolFlag{ - Name: "publish-all, P", - Usage: "Publish all exposed ports to random ports on the host interface", - }, - cli.BoolFlag{ - Name: "read-only", - Usage: "Make containers root filesystem read-only", - }, - cli.BoolFlag{ - Name: "rm", - Usage: "Remove container (and pod if created) after exit", - }, - cli.StringSliceFlag{ - Name: "security-opt", - Usage: "Security Options (default [])", - }, - cli.StringFlag{ - Name: "shm-size", - Usage: "Size of `/dev/shm`. The format is `<number><unit>`. default is 64 MB", - }, - cli.BoolFlag{ - Name: "sig-proxy", - Usage: "Proxy received signals to the process (default true)", - }, - cli.StringFlag{ - Name: "stop-signal", - Usage: "Signal to stop a container. Default is SIGTERM", - }, - cli.IntFlag{ - Name: "stop-timeout", - Usage: "Timeout (in seconds) to stop a container. Default is 10", - }, - cli.StringSliceFlag{ - Name: "storage-opt", - Usage: "Storage driver options per container (default [])", - }, - cli.StringSliceFlag{ - Name: "sysctl", - Usage: "Sysctl options (default [])", - }, - cli.StringSliceFlag{ - Name: "tmpfs", - Usage: "Mount a temporary filesystem (`tmpfs`) into a container (default [])", - }, - cli.BoolFlag{ - Name: "tty, t", - Usage: "Allocate a pseudo-TTY for container", - }, - cli.StringSliceFlag{ - Name: "ulimit", - Usage: "Ulimit options (default [])", - }, - cli.StringFlag{ - Name: "user, u", - Usage: "Username or UID (format: <name|uid>[:<group|gid>])", - }, - cli.StringFlag{ - Name: "userns", - Usage: "User namespace to use", - }, - cli.StringFlag{ - Name: "uts", - Usage: "UTS namespace to use", - }, - cli.StringSliceFlag{ - Name: "volume, v", - Usage: "Bind mount a volume into the container (default [])", - }, - cli.StringSliceFlag{ - Name: "volumes-from", - Usage: "Mount volumes from the specified container(s) (default [])", - }, - cli.StringFlag{ - Name: "workdir, w", - Usage: "Working `directory inside the container", - Value: "/", - }, -} diff --git a/cmd/kpod/common_test.go b/cmd/kpod/common_test.go deleted file mode 100644 index 8a3bfd181..000000000 --- a/cmd/kpod/common_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "os/user" - "testing" - - "flag" - - "github.com/urfave/cli" -) - -func TestGetStore(t *testing.T) { - t.Skip("FIX THIS!") - - //cmd/kpod/common_test.go:27: cannot use c (type *cli.Context) as type *libkpod.Config in argument to getStore - - // Make sure the tests are running as root - skipTestIfNotRoot(t) - - set := flag.NewFlagSet("test", 0) - globalSet := flag.NewFlagSet("test", 0) - globalSet.String("root", "", "path to the root directory in which data, including images, is stored") - globalCtx := cli.NewContext(nil, globalSet, nil) - command := cli.Command{Name: "imagesCommand"} - c := cli.NewContext(nil, set, globalCtx) - c.Command = command - - //_, err := getStore(c) - //if err != nil { - //t.Error(err) - //} -} - -func skipTestIfNotRoot(t *testing.T) { - u, err := user.Current() - if err != nil { - t.Skip("Could not determine user. Running without root may cause tests to fail") - } else if u.Uid != "0" { - t.Skip("tests will fail unless run as root") - } -} diff --git a/cmd/kpod/create.go b/cmd/kpod/create.go deleted file mode 100644 index afed70eaa..000000000 --- a/cmd/kpod/create.go +++ /dev/null @@ -1,507 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "os" - "strconv" - "strings" - - "github.com/docker/docker/api/types/container" - "github.com/docker/go-units" - "github.com/opencontainers/selinux/go-selinux/label" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" - pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" -) - -type mountType string - -// Type constants -const ( - // TypeBind is the type for mounting host dir - TypeBind mountType = "bind" - // TypeVolume is the type for remote storage volumes - // TypeVolume mountType = "volume" // re-enable upon use - // TypeTmpfs is the type for mounting tmpfs - TypeTmpfs mountType = "tmpfs" -) - -var ( - defaultEnvVariables = map[string]string{ - "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "TERM": "xterm", - } -) - -type createResourceConfig struct { - BlkioWeight uint16 // blkio-weight - BlkioWeightDevice []string // blkio-weight-device - CPUPeriod uint64 // cpu-period - CPUQuota int64 // cpu-quota - CPURtPeriod uint64 // cpu-rt-period - CPURtRuntime int64 // cpu-rt-runtime - CPUShares uint64 // cpu-shares - CPUs string // cpus - CPUsetCPUs string - CPUsetMems string // cpuset-mems - DeviceReadBps []string // device-read-bps - DeviceReadIOps []string // device-read-iops - DeviceWriteBps []string // device-write-bps - DeviceWriteIOps []string // device-write-iops - DisableOomKiller bool // oom-kill-disable - KernelMemory int64 // kernel-memory - Memory int64 //memory - MemoryReservation int64 // memory-reservation - MemorySwap int64 //memory-swap - MemorySwappiness int // memory-swappiness - OomScoreAdj int //oom-score-adj - PidsLimit int64 // pids-limit - ShmSize string - Ulimit []string //ulimit -} - -type createConfig struct { - Runtime *libpod.Runtime - Args []string - CapAdd []string // cap-add - CapDrop []string // cap-drop - CidFile string - CgroupParent string // cgroup-parent - Command []string - Detach bool // detach - Devices []*pb.Device // device - DNSOpt []string //dns-opt - DNSSearch []string //dns-search - DNSServers []string //dns - Entrypoint string //entrypoint - Env map[string]string //env - Expose []string //expose - GroupAdd []uint32 // group-add - Hostname string //hostname - Image string - Interactive bool //interactive - IpcMode container.IpcMode //ipc - IP6Address string //ipv6 - IPAddress string //ip - Labels map[string]string //label - LinkLocalIP []string // link-local-ip - LogDriver string // log-driver - LogDriverOpt []string // log-opt - MacAddress string //mac-address - Name string //name - NetMode container.NetworkMode //net - Network string //network - NetworkAlias []string //network-alias - PidMode container.PidMode //pid - NsUser string - Pod string //pod - Privileged bool //privileged - Publish []string //publish - PublishAll bool //publish-all - ReadOnlyRootfs bool //read-only - Resources createResourceConfig - Rm bool //rm - ShmDir string - SigProxy bool //sig-proxy - StopSignal string // stop-signal - StopTimeout int64 // stop-timeout - StorageOpts []string //storage-opt - Sysctl map[string]string //sysctl - Tmpfs []string // tmpfs - Tty bool //tty - User uint32 //user - Group uint32 // group - UtsMode container.UTSMode //uts - Volumes []string //volume - WorkDir string //workdir - MountLabel string //SecurityOpts - ProcessLabel string //SecurityOpts - NoNewPrivileges bool //SecurityOpts - ApparmorProfile string //SecurityOpts - SeccompProfilePath string //SecurityOpts - SecurityOpts []string -} - -var createDescription = "Creates a new container from the given image or" + - " storage and prepares it for running the specified command. The" + - " container ID is then printed to stdout. You can then start it at" + - " any time with the kpod start <container_id> command. The container" + - " will be created with the initial state 'created'." - -var createCommand = cli.Command{ - Name: "create", - Usage: "create but do not start a container", - Description: createDescription, - Flags: createFlags, - Action: createCmd, - ArgsUsage: "IMAGE [COMMAND [ARG...]]", - SkipArgReorder: true, - UseShortOptionHandling: true, -} - -func createCmd(c *cli.Context) error { - // TODO should allow user to create based off a directory on the host not just image - // Need CLI support for this - var imageName string - if err := validateFlags(c, createFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.Shutdown(false) - - createConfig, err := parseCreateOpts(c, runtime) - if err != nil { - return err - } - - // Deal with the image after all the args have been checked - createImage := runtime.NewImage(createConfig.Image) - createImage.LocalName, _ = createImage.GetLocalImageName() - if createImage.LocalName == "" { - // The image wasnt found by the user input'd name or its fqname - // Pull the image - fmt.Printf("Trying to pull %s...", createImage.PullName) - createImage.Pull() - } - - runtimeSpec, err := createConfigToOCISpec(createConfig) - if err != nil { - return err - } - if createImage.LocalName != "" { - nameIsID, err := runtime.IsImageID(createImage.LocalName) - if err != nil { - return err - } - if nameIsID { - // If the input from the user is an ID, then we need to get the image - // name for cstorage - createImage.LocalName, err = createImage.GetNameByID() - if err != nil { - return err - } - } - imageName = createImage.LocalName - } else { - imageName, err = createImage.GetFQName() - } - if err != nil { - return err - } - imageID, err := createImage.GetImageID() - if err != nil { - return err - } - options, err := createConfig.GetContainerCreateOptions() - if err != nil { - return errors.Wrapf(err, "unable to parse new container options") - } - // Gather up the options for NewContainer which consist of With... funcs - options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false)) - options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel)) - options = append(options, libpod.WithShmDir(createConfig.ShmDir)) - ctr, err := runtime.NewContainer(runtimeSpec, options...) - if err != nil { - return err - } - - createConfigJSON, err := json.Marshal(createConfig) - if err != nil { - return err - } - if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { - return err - } - - logrus.Debug("new container created ", ctr.ID()) - - if c.String("cidfile") != "" { - libpod.WriteFile(ctr.ID(), c.String("cidfile")) - } else { - fmt.Printf("%s\n", ctr.ID()) - } - - return nil -} - -const seccompDefaultPath = "/etc/crio/seccomp.json" - -func parseSecurityOpt(config *createConfig, securityOpts []string) error { - var ( - labelOpts []string - err error - ) - - if config.PidMode.IsHost() { - labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if config.PidMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(config.PidMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", config.PidMode.Container()) - } - labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) - } - - if config.IpcMode.IsHost() { - labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if config.IpcMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(config.IpcMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", config.IpcMode.Container()) - } - labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) - } - - for _, opt := range securityOpts { - if opt == "no-new-privileges" { - config.NoNewPrivileges = true - } else { - con := strings.SplitN(opt, "=", 2) - if len(con) != 2 { - return fmt.Errorf("Invalid --security-opt 1: %q", opt) - } - - switch con[0] { - case "label": - labelOpts = append(labelOpts, con[1]) - case "apparmor": - config.ApparmorProfile = con[1] - case "seccomp": - config.SeccompProfilePath = con[1] - default: - return fmt.Errorf("Invalid --security-opt 2: %q", opt) - } - } - } - - if config.SeccompProfilePath == "" { - if _, err := os.Stat(seccompDefaultPath); err != nil { - if !os.IsNotExist(err) { - return errors.Wrapf(err, "can't check if %q exists", seccompDefaultPath) - } - } else { - config.SeccompProfilePath = seccompDefaultPath - } - } - config.ProcessLabel, config.MountLabel, err = label.InitLabels(labelOpts) - return err -} - -// Parses CLI options related to container creation into a config which can be -// parsed into an OCI runtime spec -func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, error) { - var command []string - var memoryLimit, memoryReservation, memorySwap, memoryKernel int64 - var blkioWeight uint16 - var uid, gid uint32 - - if len(c.Args()) < 1 { - return nil, errors.Errorf("image name or ID is required") - } - image := c.Args()[0] - - if len(c.Args()) > 1 { - command = c.Args()[1:] - } - - // LABEL VARIABLES - labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("labels")) - if err != nil { - return &createConfig{}, errors.Wrapf(err, "unable to process labels") - } - // ENVIRONMENT VARIABLES - env := defaultEnvVariables - if err := readKVStrings(env, c.StringSlice("env-file"), c.StringSlice("env")); err != nil { - return &createConfig{}, errors.Wrapf(err, "unable to process environment variables") - } - - sysctl, err := convertStringSliceToMap(c.StringSlice("sysctl"), "=") - if err != nil { - return &createConfig{}, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE") - } - - groupAdd, err := stringSlicetoUint32Slice(c.StringSlice("group-add")) - if err != nil { - return &createConfig{}, errors.Wrapf(err, "invalid value for groups provided") - } - - if c.String("user") != "" { - // TODO - // We need to mount the imagefs and get the uid/gid - // For now, user zeros - uid = 0 - gid = 0 - } - - if c.String("memory") != "" { - memoryLimit, err = units.RAMInBytes(c.String("memory")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for memory") - } - } - if c.String("memory-reservation") != "" { - memoryReservation, err = units.RAMInBytes(c.String("memory-reservation")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for memory-reservation") - } - } - if c.String("memory-swap") != "" { - memorySwap, err = units.RAMInBytes(c.String("memory-swap")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for memory-swap") - } - } - if c.String("kernel-memory") != "" { - memoryKernel, err = units.RAMInBytes(c.String("kernel-memory")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for kernel-memory") - } - } - if c.String("blkio-weight") != "" { - u, err := strconv.ParseUint(c.String("blkio-weight"), 10, 16) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for blkio-weight") - } - blkioWeight = uint16(u) - } - - if err = parseVolumes(c.StringSlice("volume")); err != nil { - return nil, err - } - - // Because we cannot do a non-terminal attach, we need to set tty to true - // if detach is not false - // TODO Allow non-terminal attach - tty := c.Bool("tty") - if !c.Bool("detach") && !tty { - tty = true - } - - pidMode := container.PidMode(c.String("pid")) - if !pidMode.Valid() { - return nil, errors.Errorf("--pid %q is not valid", c.String("pid")) - } - - if c.Bool("detach") && c.Bool("rm") { - return nil, errors.Errorf("--rm and --detach can not be specified together") - } - - utsMode := container.UTSMode(c.String("uts")) - if !utsMode.Valid() { - return nil, errors.Errorf("--uts %q is not valid", c.String("uts")) - } - ipcMode := container.IpcMode(c.String("ipc")) - if !ipcMode.Valid() { - return nil, errors.Errorf("--ipc %q is not valid", ipcMode) - } - shmDir := "" - if ipcMode.IsHost() { - shmDir = "/dev/shm" - } else if ipcMode.IsContainer() { - ctr, err := runtime.LookupContainer(ipcMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", ipcMode.Container()) - } - shmDir = ctr.ShmDir() - } - - config := &createConfig{ - Runtime: runtime, - CapAdd: c.StringSlice("cap-add"), - CapDrop: c.StringSlice("cap-drop"), - CgroupParent: c.String("cgroup-parent"), - Command: command, - Detach: c.Bool("detach"), - DNSOpt: c.StringSlice("dns-opt"), - DNSSearch: c.StringSlice("dns-search"), - DNSServers: c.StringSlice("dns"), - Entrypoint: c.String("entrypoint"), - Env: env, - Expose: c.StringSlice("expose"), - GroupAdd: groupAdd, - Hostname: c.String("hostname"), - Image: image, - Interactive: c.Bool("interactive"), - IP6Address: c.String("ipv6"), - IPAddress: c.String("ip"), - Labels: labels, - LinkLocalIP: c.StringSlice("link-local-ip"), - LogDriver: c.String("log-driver"), - LogDriverOpt: c.StringSlice("log-opt"), - MacAddress: c.String("mac-address"), - Name: c.String("name"), - Network: c.String("network"), - NetworkAlias: c.StringSlice("network-alias"), - IpcMode: ipcMode, - NetMode: container.NetworkMode(c.String("network")), - UtsMode: utsMode, - PidMode: pidMode, - Pod: c.String("pod"), - Privileged: c.Bool("privileged"), - Publish: c.StringSlice("publish"), - PublishAll: c.Bool("publish-all"), - ReadOnlyRootfs: c.Bool("read-only"), - Resources: createResourceConfig{ - BlkioWeight: blkioWeight, - BlkioWeightDevice: c.StringSlice("blkio-weight-device"), - CPUShares: c.Uint64("cpu-shares"), - CPUPeriod: c.Uint64("cpu-period"), - CPUsetCPUs: c.String("cpu-period"), - CPUsetMems: c.String("cpuset-mems"), - CPUQuota: c.Int64("cpu-quota"), - CPURtPeriod: c.Uint64("cpu-rt-period"), - CPURtRuntime: c.Int64("cpu-rt-runtime"), - CPUs: c.String("cpus"), - DeviceReadBps: c.StringSlice("device-read-bps"), - DeviceReadIOps: c.StringSlice("device-read-iops"), - DeviceWriteBps: c.StringSlice("device-write-bps"), - DeviceWriteIOps: c.StringSlice("device-write-iops"), - DisableOomKiller: c.Bool("oom-kill-disable"), - ShmSize: c.String("shm-size"), - Memory: memoryLimit, - MemoryReservation: memoryReservation, - MemorySwap: memorySwap, - MemorySwappiness: c.Int("memory-swappiness"), - KernelMemory: memoryKernel, - OomScoreAdj: c.Int("oom-score-adj"), - - PidsLimit: c.Int64("pids-limit"), - Ulimit: c.StringSlice("ulimit"), - }, - Rm: c.Bool("rm"), - ShmDir: shmDir, - SigProxy: c.Bool("sig-proxy"), - StopSignal: c.String("stop-signal"), - StopTimeout: c.Int64("stop-timeout"), - StorageOpts: c.StringSlice("storage-opt"), - Sysctl: sysctl, - Tmpfs: c.StringSlice("tmpfs"), - Tty: tty, - User: uid, - Group: gid, - Volumes: c.StringSlice("volume"), - WorkDir: c.String("workdir"), - } - - if !config.Privileged { - if err := parseSecurityOpt(config, c.StringSlice("security-opt")); err != nil { - return nil, err - } - } - config.SecurityOpts = c.StringSlice("security-opt") - warnings, err := verifyContainerResources(config, false) - if err != nil { - return nil, err - } - for _, warning := range warnings { - fmt.Fprintln(os.Stderr, warning) - } - return config, nil -} diff --git a/cmd/kpod/create_cli.go b/cmd/kpod/create_cli.go deleted file mode 100644 index 0cc265e92..000000000 --- a/cmd/kpod/create_cli.go +++ /dev/null @@ -1,242 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/docker/docker/pkg/sysinfo" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -const ( - // It's not kernel limit, we want this 4M limit to supply a reasonable functional container - linuxMinMemory = 4194304 -) - -func getAllLabels(labelFile, inputLabels []string) (map[string]string, error) { - labels := make(map[string]string) - labelErr := readKVStrings(labels, labelFile, inputLabels) - if labelErr != nil { - return labels, errors.Wrapf(labelErr, "unable to process labels from --label and label-file") - } - return labels, nil -} - -func convertStringSliceToMap(strSlice []string, delimiter string) (map[string]string, error) { - sysctl := make(map[string]string) - for _, inputSysctl := range strSlice { - values := strings.Split(inputSysctl, delimiter) - if len(values) < 2 { - return sysctl, errors.Errorf("%s in an invalid sysctl value", inputSysctl) - } - sysctl[values[0]] = values[1] - } - return sysctl, nil -} - -func addWarning(warnings []string, msg string) []string { - logrus.Warn(msg) - return append(warnings, msg) -} - -func parseVolumes(volumes []string) error { - if len(volumes) == 0 { - return nil - } - for _, volume := range volumes { - arr := strings.SplitN(volume, ":", 3) - if len(arr) < 2 { - return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir:[option]", volume) - } - if err := validateVolumeHostDir(arr[0]); err != nil { - return err - } - if err := validateVolumeCtrDir(arr[1]); err != nil { - return err - } - if len(arr) > 2 { - if err := validateVolumeOpts(arr[2]); err != nil { - return err - } - } - } - return nil -} - -func validateVolumeHostDir(hostDir string) error { - if _, err := os.Stat(hostDir); err != nil { - return errors.Wrapf(err, "error checking path %q", hostDir) - } - return nil -} - -func validateVolumeCtrDir(ctrDir string) error { - if ctrDir[0] != '/' { - return errors.Errorf("invalid container directory path %q", ctrDir) - } - return nil -} - -func validateVolumeOpts(option string) error { - var foundRootPropagation, foundRWRO, foundLabelChange int - options := strings.Split(option, ",") - for _, opt := range options { - switch opt { - case "rw", "ro": - if foundRWRO > 1 { - return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", option) - } - foundRWRO++ - case "z", "Z": - if foundLabelChange > 1 { - return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", option) - } - foundLabelChange++ - case "private", "rprivate", "shared", "rshared", "slave", "rslave": - if foundRootPropagation > 1 { - return errors.Errorf("invalid options %q, can only specify 1 '[r]shared', '[r]private' or '[r]slave' option", option) - } - foundRootPropagation++ - default: - return errors.Errorf("invalid option type %q", option) - } - } - return nil -} - -func verifyContainerResources(config *createConfig, update bool) ([]string, error) { - warnings := []string{} - sysInfo := sysinfo.New(true) - - // memory subsystem checks and adjustments - if config.Resources.Memory != 0 && config.Resources.Memory < linuxMinMemory { - return warnings, fmt.Errorf("minimum memory limit allowed is 4MB") - } - if config.Resources.Memory > 0 && !sysInfo.MemoryLimit { - warnings = addWarning(warnings, "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.") - config.Resources.Memory = 0 - config.Resources.MemorySwap = -1 - } - if config.Resources.Memory > 0 && config.Resources.MemorySwap != -1 && !sysInfo.SwapLimit { - warnings = addWarning(warnings, "Your kernel does not support swap limit capabilities,or the cgroup is not mounted. Memory limited without swap.") - config.Resources.MemorySwap = -1 - } - if config.Resources.Memory > 0 && config.Resources.MemorySwap > 0 && config.Resources.MemorySwap < config.Resources.Memory { - return warnings, fmt.Errorf("minimum memoryswap limit should be larger than memory limit, see usage") - } - if config.Resources.Memory == 0 && config.Resources.MemorySwap > 0 && !update { - return warnings, fmt.Errorf("you should always set the memory limit when using memoryswap limit, see usage") - } - if config.Resources.MemorySwappiness != -1 { - if !sysInfo.MemorySwappiness { - msg := "Your kernel does not support memory swappiness capabilities, or the cgroup is not mounted. Memory swappiness discarded." - warnings = addWarning(warnings, msg) - config.Resources.MemorySwappiness = -1 - } else { - swappiness := config.Resources.MemorySwappiness - if swappiness < -1 || swappiness > 100 { - return warnings, fmt.Errorf("invalid value: %v, valid memory swappiness range is 0-100", swappiness) - } - } - } - if config.Resources.MemoryReservation > 0 && !sysInfo.MemoryReservation { - warnings = addWarning(warnings, "Your kernel does not support memory soft limit capabilities or the cgroup is not mounted. Limitation discarded.") - config.Resources.MemoryReservation = 0 - } - if config.Resources.MemoryReservation > 0 && config.Resources.MemoryReservation < linuxMinMemory { - return warnings, fmt.Errorf("minimum memory reservation allowed is 4MB") - } - if config.Resources.Memory > 0 && config.Resources.MemoryReservation > 0 && config.Resources.Memory < config.Resources.MemoryReservation { - return warnings, fmt.Errorf("minimum memory limit can not be less than memory reservation limit, see usage") - } - if config.Resources.KernelMemory > 0 && !sysInfo.KernelMemory { - warnings = addWarning(warnings, "Your kernel does not support kernel memory limit capabilities or the cgroup is not mounted. Limitation discarded.") - config.Resources.KernelMemory = 0 - } - if config.Resources.KernelMemory > 0 && config.Resources.KernelMemory < linuxMinMemory { - return warnings, fmt.Errorf("minimum kernel memory limit allowed is 4MB") - } - if config.Resources.DisableOomKiller == true && !sysInfo.OomKillDisable { - // only produce warnings if the setting wasn't to *disable* the OOM Kill; no point - // warning the caller if they already wanted the feature to be off - warnings = addWarning(warnings, "Your kernel does not support OomKillDisable. OomKillDisable discarded.") - config.Resources.DisableOomKiller = false - } - - if config.Resources.PidsLimit != 0 && !sysInfo.PidsLimit { - warnings = addWarning(warnings, "Your kernel does not support pids limit capabilities or the cgroup is not mounted. PIDs limit discarded.") - config.Resources.PidsLimit = 0 - } - - if config.Resources.CPUShares > 0 && !sysInfo.CPUShares { - warnings = addWarning(warnings, "Your kernel does not support CPU shares or the cgroup is not mounted. Shares discarded.") - config.Resources.CPUShares = 0 - } - if config.Resources.CPUPeriod > 0 && !sysInfo.CPUCfsPeriod { - warnings = addWarning(warnings, "Your kernel does not support CPU cfs period or the cgroup is not mounted. Period discarded.") - config.Resources.CPUPeriod = 0 - } - if config.Resources.CPUPeriod != 0 && (config.Resources.CPUPeriod < 1000 || config.Resources.CPUPeriod > 1000000) { - return warnings, fmt.Errorf("CPU cfs period can not be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000)") - } - if config.Resources.CPUQuota > 0 && !sysInfo.CPUCfsQuota { - warnings = addWarning(warnings, "Your kernel does not support CPU cfs quota or the cgroup is not mounted. Quota discarded.") - config.Resources.CPUQuota = 0 - } - if config.Resources.CPUQuota > 0 && config.Resources.CPUQuota < 1000 { - return warnings, fmt.Errorf("CPU cfs quota can not be less than 1ms (i.e. 1000)") - } - // cpuset subsystem checks and adjustments - if (config.Resources.CPUsetCPUs != "" || config.Resources.CPUsetMems != "") && !sysInfo.Cpuset { - warnings = addWarning(warnings, "Your kernel does not support cpuset or the cgroup is not mounted. CPUset discarded.") - config.Resources.CPUsetCPUs = "" - config.Resources.CPUsetMems = "" - } - cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(config.Resources.CPUsetCPUs) - if err != nil { - return warnings, fmt.Errorf("invalid value %s for cpuset cpus", config.Resources.CPUsetCPUs) - } - if !cpusAvailable { - return warnings, fmt.Errorf("requested CPUs are not available - requested %s, available: %s", config.Resources.CPUsetCPUs, sysInfo.Cpus) - } - memsAvailable, err := sysInfo.IsCpusetMemsAvailable(config.Resources.CPUsetMems) - if err != nil { - return warnings, fmt.Errorf("invalid value %s for cpuset mems", config.Resources.CPUsetMems) - } - if !memsAvailable { - return warnings, fmt.Errorf("requested memory nodes are not available - requested %s, available: %s", config.Resources.CPUsetMems, sysInfo.Mems) - } - - // blkio subsystem checks and adjustments - if config.Resources.BlkioWeight > 0 && !sysInfo.BlkioWeight { - warnings = addWarning(warnings, "Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.") - config.Resources.BlkioWeight = 0 - } - if config.Resources.BlkioWeight > 0 && (config.Resources.BlkioWeight < 10 || config.Resources.BlkioWeight > 1000) { - return warnings, fmt.Errorf("range of blkio weight is from 10 to 1000") - } - if len(config.Resources.BlkioWeightDevice) > 0 && !sysInfo.BlkioWeightDevice { - warnings = addWarning(warnings, "Your kernel does not support Block I/O weight_device or the cgroup is not mounted. Weight-device discarded.") - config.Resources.BlkioWeightDevice = []string{} - } - if len(config.Resources.DeviceReadBps) > 0 && !sysInfo.BlkioReadBpsDevice { - warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O read limit or the cgroup is not mounted. Block I/O BPS read limit discarded") - config.Resources.DeviceReadBps = []string{} - } - if len(config.Resources.DeviceWriteBps) > 0 && !sysInfo.BlkioWriteBpsDevice { - warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded.") - config.Resources.DeviceWriteBps = []string{} - } - if len(config.Resources.DeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice { - warnings = addWarning(warnings, "Your kernel does not support IOPS Block read limit or the cgroup is not mounted. Block I/O IOPS read limit discarded.") - config.Resources.DeviceReadIOps = []string{} - } - if len(config.Resources.DeviceWriteIOps) > 0 && !sysInfo.BlkioWriteIOpsDevice { - warnings = addWarning(warnings, "Your kernel does not support IOPS Block I/O write limit or the cgroup is not mounted. Block I/O IOPS write limit discarded.") - config.Resources.DeviceWriteIOps = []string{} - } - - return warnings, nil -} diff --git a/cmd/kpod/create_cli_test.go b/cmd/kpod/create_cli_test.go deleted file mode 100644 index 63a1e5dd3..000000000 --- a/cmd/kpod/create_cli_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "io/ioutil" - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - Var1 = []string{"ONE=1", "TWO=2"} -) - -func createTmpFile(content []byte) (string, error) { - tmpfile, err := ioutil.TempFile(os.TempDir(), "unittest") - if err != nil { - return "", err - } - - if _, err := tmpfile.Write(content); err != nil { - return "", err - - } - if err := tmpfile.Close(); err != nil { - return "", err - } - return tmpfile.Name(), nil -} - -func TestConvertStringSliceToMap(t *testing.T) { - strSlice := []string{"BLAU=BLUE", "GELB=YELLOW"} - result, _ := convertStringSliceToMap(strSlice, "=") - assert.Equal(t, result["BLAU"], "BLUE") -} - -func TestConvertStringSliceToMapBadData(t *testing.T) { - strSlice := []string{"BLAU=BLUE", "GELB^YELLOW"} - _, err := convertStringSliceToMap(strSlice, "=") - assert.Error(t, err) -} - -func TestGetAllLabels(t *testing.T) { - fileLabels := []string{} - labels, _ := getAllLabels(fileLabels, Var1) - assert.Equal(t, len(labels), 2) -} - -func TestGetAllLabelsBadKeyValue(t *testing.T) { - inLabels := []string{"ONE1", "TWO=2"} - fileLabels := []string{} - _, err := getAllLabels(fileLabels, inLabels) - assert.Error(t, err, assert.AnError) -} - -func TestGetAllLabelsBadLabelFile(t *testing.T) { - fileLabels := []string{"/foobar5001/be"} - _, err := getAllLabels(fileLabels, Var1) - assert.Error(t, err, assert.AnError) -} - -func TestGetAllLabelsFile(t *testing.T) { - content := []byte("THREE=3") - tFile, err := createTmpFile(content) - defer os.Remove(tFile) - assert.NoError(t, err) - fileLabels := []string{tFile} - result, _ := getAllLabels(fileLabels, Var1) - assert.Equal(t, len(result), 3) -} diff --git a/cmd/kpod/diff.go b/cmd/kpod/diff.go deleted file mode 100644 index 3a3b82e15..000000000 --- a/cmd/kpod/diff.go +++ /dev/null @@ -1,128 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/storage/pkg/archive" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/urfave/cli" -) - -type diffJSONOutput struct { - Changed []string `json:"changed,omitempty"` - Added []string `json:"added,omitempty"` - Deleted []string `json:"deleted,omitempty"` -} - -type diffOutputParams struct { - Change archive.ChangeType - Path string -} - -type stdoutStruct struct { - output []diffOutputParams -} - -func (so stdoutStruct) Out() error { - for _, d := range so.output { - fmt.Printf("%s %s\n", d.Change, d.Path) - } - return nil -} - -var ( - diffFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "archive", - Usage: "Save the diff as a tar archive", - Hidden: true, - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format.", - }, - } - diffDescription = fmt.Sprint(`Displays changes on a container or image's filesystem. The - container or image will be compared to its parent layer`) - - diffCommand = cli.Command{ - Name: "diff", - Usage: "Inspect changes on container's file systems", - Description: diffDescription, - Flags: diffFlags, - Action: diffCmd, - ArgsUsage: "ID-NAME", - } -) - -func formatJSON(output []diffOutputParams) (diffJSONOutput, error) { - jsonStruct := diffJSONOutput{} - for _, output := range output { - switch output.Change { - case archive.ChangeModify: - jsonStruct.Changed = append(jsonStruct.Changed, output.Path) - case archive.ChangeAdd: - jsonStruct.Added = append(jsonStruct.Added, output.Path) - case archive.ChangeDelete: - jsonStruct.Deleted = append(jsonStruct.Deleted, output.Path) - default: - return jsonStruct, errors.Errorf("output kind %q not recognized", output.Change.String()) - } - } - return jsonStruct, nil -} - -func diffCmd(c *cli.Context) error { - if err := validateFlags(c, diffFlags); err != nil { - return err - } - - if len(c.Args()) != 1 { - return errors.Errorf("container, image, or layer name must be specified: kpod diff [options [...]] ID-NAME") - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - to := c.Args().Get(0) - changes, err := runtime.GetDiff("", to) - if err != nil { - return errors.Wrapf(err, "could not get changes for %q", to) - } - - diffOutput := []diffOutputParams{} - outputFormat := c.String("format") - - for _, change := range changes { - - params := diffOutputParams{ - Change: change.Kind, - Path: change.Path, - } - diffOutput = append(diffOutput, params) - } - - var out formats.Writer - - if outputFormat != "" { - switch outputFormat { - case formats.JSONString: - data, err := formatJSON(diffOutput) - if err != nil { - return err - } - out = formats.JSONStruct{Output: data} - default: - return errors.New("only valid format for diff is 'json'") - } - } else { - out = stdoutStruct{output: diffOutput} - } - formats.Writer(out).Out() - - return nil -} diff --git a/cmd/kpod/docker/types.go b/cmd/kpod/docker/types.go deleted file mode 100644 index a7e456554..000000000 --- a/cmd/kpod/docker/types.go +++ /dev/null @@ -1,271 +0,0 @@ -package docker - -// -// Types extracted from Docker -// - -import ( - "time" - - "github.com/containers/image/pkg/strslice" - "github.com/opencontainers/go-digest" -) - -// TypeLayers github.com/docker/docker/image/rootfs.go -const TypeLayers = "layers" - -// V2S2MediaTypeManifest github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeManifest = "application/vnd.docker.distribution.manifest.v2+json" - -// V2S2MediaTypeImageConfig github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeImageConfig = "application/vnd.docker.container.image.v1+json" - -// V2S2MediaTypeLayer github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeLayer = "application/vnd.docker.image.rootfs.diff.tar.gzip" - -// V2S2MediaTypeUncompressedLayer github.com/docker/distribution/manifest/schema2/manifest.go -const V2S2MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar" - -// V2S2RootFS describes images root filesystem -// This is currently a placeholder that only supports layers. In the future -// this can be made into an interface that supports different implementations. -// github.com/docker/docker/image/rootfs.go -type V2S2RootFS struct { - Type string `json:"type"` - DiffIDs []digest.Digest `json:"diff_ids,omitempty"` -} - -// V2S2History stores build commands that were used to create an image -// github.com/docker/docker/image/image.go -type V2S2History struct { - // Created is the timestamp at which the image was created - Created time.Time `json:"created"` - // Author is the name of the author that was specified when committing the image - Author string `json:"author,omitempty"` - // CreatedBy keeps the Dockerfile command used while building the image - CreatedBy string `json:"created_by,omitempty"` - // Comment is the commit message that was set when committing the image - Comment string `json:"comment,omitempty"` - // EmptyLayer is set to true if this history item did not generate a - // layer. Otherwise, the history item is associated with the next - // layer in the RootFS section. - EmptyLayer bool `json:"empty_layer,omitempty"` -} - -// ID is the content-addressable ID of an image. -// github.com/docker/docker/image/image.go -type ID digest.Digest - -// HealthConfig holds configuration settings for the HEALTHCHECK feature. -// github.com/docker/docker/api/types/container/config.go -type HealthConfig struct { - // Test is the test to perform to check that the container is healthy. - // An empty slice means to inherit the default. - // The options are: - // {} : inherit healthcheck - // {"NONE"} : disable healthcheck - // {"CMD", args...} : exec arguments directly - // {"CMD-SHELL", command} : run command with system's default shell - Test []string `json:",omitempty"` - - // Zero means to inherit. Durations are expressed as integer nanoseconds. - Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks. - Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung. - - // Retries is the number of consecutive failures needed to consider a container as unhealthy. - // Zero means inherit. - Retries int `json:",omitempty"` -} - -// PortSet is a collection of structs indexed by Port -// github.com/docker/go-connections/nat/nat.go -type PortSet map[Port]struct{} - -// Port is a string containing port number and protocol in the format "80/tcp" -// github.com/docker/go-connections/nat/nat.go -type Port string - -// Config contains the configuration data about a container. -// It should hold only portable information about the container. -// Here, "portable" means "independent from the host we are running on". -// Non-portable information *should* appear in HostConfig. -// All fields added to this struct must be marked `omitempty` to keep getting -// predictable hashes from the old `v1Compatibility` configuration. -// github.com/docker/docker/api/types/container/config.go -type Config struct { - Hostname string // Hostname - Domainname string // Domainname - User string // User that will run the command(s) inside the container, also support user:group - AttachStdin bool // Attach the standard input, makes possible user interaction - AttachStdout bool // Attach the standard output - AttachStderr bool // Attach the standard error - ExposedPorts PortSet `json:",omitempty"` // List of exposed ports - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin - StdinOnce bool // If true, close stdin after the 1 attached client disconnects. - Env []string // List of environment variable to set in the container - Cmd strslice.StrSlice // Command to run when starting the container - Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy - ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific) - Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) - Volumes map[string]struct{} // List of volumes (mounts) used for the container - WorkingDir string // Current directory (PWD) in the command will be launched - Entrypoint strslice.StrSlice // Entrypoint to run when starting the container - NetworkDisabled bool `json:",omitempty"` // Is network disabled - MacAddress string `json:",omitempty"` // Mac Address of the container - OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile - Labels map[string]string // List of labels set to this container - StopSignal string `json:",omitempty"` // Signal to stop a container - StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container - Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT -} - -// V1Compatibility - For non-top-level layers, create fake V1Compatibility -// strings that fit the format and don't collide with anything else, but -// don't result in runnable images on their own. -// github.com/docker/distribution/manifest/schema1/config_builder.go -type V1Compatibility struct { - ID string `json:"id"` - Parent string `json:"parent,omitempty"` - Comment string `json:"comment,omitempty"` - Created time.Time `json:"created"` - ContainerConfig struct { - Cmd []string - } `json:"container_config,omitempty"` - Author string `json:"author,omitempty"` - ThrowAway bool `json:"throwaway,omitempty"` -} - -// V1Image stores the V1 image configuration. -// github.com/docker/docker/image/image.go -type V1Image struct { - // ID is a unique 64 character identifier of the image - ID string `json:"id,omitempty"` - // Parent is the ID of the parent image - Parent string `json:"parent,omitempty"` - // Comment is the commit message that was set when committing the image - Comment string `json:"comment,omitempty"` - // Created is the timestamp at which the image was created - Created time.Time `json:"created"` - // Container is the id of the container used to commit - Container string `json:"container,omitempty"` - // ContainerConfig is the configuration of the container that is committed into the image - ContainerConfig Config `json:"container_config,omitempty"` - // DockerVersion specifies the version of Docker that was used to build the image - DockerVersion string `json:"docker_version,omitempty"` - // Author is the name of the author that was specified when committing the image - Author string `json:"author,omitempty"` - // Config is the configuration of the container received from the client - Config *Config `json:"config,omitempty"` - // Architecture is the hardware that the image is build and runs on - Architecture string `json:"architecture,omitempty"` - // OS is the operating system used to build and run the image - OS string `json:"os,omitempty"` - // Size is the total size of the image including all layers it is composed of - Size int64 `json:",omitempty"` -} - -// V2Image stores the image configuration -// github.com/docker/docker/image/image.go -type V2Image struct { - V1Image - Parent ID `json:"parent,omitempty"` - RootFS *V2S2RootFS `json:"rootfs,omitempty"` - History []V2S2History `json:"history,omitempty"` - OSVersion string `json:"os.version,omitempty"` - OSFeatures []string `json:"os.features,omitempty"` - - // rawJSON caches the immutable JSON associated with this image. - //rawJSON []byte - - // computedID is the ID computed from the hash of the image config. - // Not to be confused with the legacy V1 ID in V1Image. - //computedID ID -} - -// V2Versioned provides a struct with the manifest schemaVersion and mediaType. -// Incoming content with unknown schema version can be decoded against this -// struct to check the version. -// github.com/docker/distribution/manifest/versioned.go -type V2Versioned struct { - // SchemaVersion is the image manifest schema that this image follows - SchemaVersion int `json:"schemaVersion"` - - // MediaType is the media type of this schema. - MediaType string `json:"mediaType,omitempty"` -} - -// V2S1FSLayer is a container struct for BlobSums defined in an image manifest -// github.com/docker/distribution/manifest/schema1/manifest.go -type V2S1FSLayer struct { - // BlobSum is the tarsum of the referenced filesystem image layer - BlobSum digest.Digest `json:"blobSum"` -} - -// V2S1History stores unstructured v1 compatibility information -// github.com/docker/distribution/manifest/schema1/manifest.go -type V2S1History struct { - // V1Compatibility is the raw v1 compatibility information - V1Compatibility string `json:"v1Compatibility"` -} - -// V2S1Manifest provides the base accessible fields for working with V2 image -// format in the registry. -// github.com/docker/distribution/manifest/schema1/manifest.go -type V2S1Manifest struct { - V2Versioned - - // Name is the name of the image's repository - Name string `json:"name"` - - // Tag is the tag of the image specified by this manifest - Tag string `json:"tag"` - - // Architecture is the host architecture on which this image is intended to - // run - Architecture string `json:"architecture"` - - // FSLayers is a list of filesystem layer blobSums contained in this image - FSLayers []V2S1FSLayer `json:"fsLayers"` - - // History is a list of unstructured historical data for v1 compatibility - History []V2S1History `json:"history"` -} - -// V2S2Descriptor describes targeted content. Used in conjunction with a blob -// store, a descriptor can be used to fetch, store and target any kind of -// blob. The struct also describes the wire protocol format. Fields should -// only be added but never changed. -// github.com/docker/distribution/blobs.go -type V2S2Descriptor struct { - // MediaType describe the type of the content. All text based formats are - // encoded as utf-8. - MediaType string `json:"mediaType,omitempty"` - - // Size in bytes of content. - Size int64 `json:"size,omitempty"` - - // Digest uniquely identifies the content. A byte stream can be verified - // against against this digest. - Digest digest.Digest `json:"digest,omitempty"` - - // URLs contains the source URLs of this content. - URLs []string `json:"urls,omitempty"` - - // NOTE: Before adding a field here, please ensure that all - // other options have been exhausted. Much of the type relationships - // depend on the simplicity of this type. -} - -// V2S2Manifest defines a schema2 manifest. -// github.com/docker/distribution/manifest/schema2/manifest.go -type V2S2Manifest struct { - V2Versioned - - // Config references the image configuration as a blob. - Config V2S2Descriptor `json:"config"` - - // Layers lists descriptors for the layers referenced by the - // configuration. - Layers []V2S2Descriptor `json:"layers"` -} diff --git a/cmd/kpod/exec.go b/cmd/kpod/exec.go deleted file mode 100644 index f76983810..000000000 --- a/cmd/kpod/exec.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -var ( - execFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "env, e", - Usage: "Set environment variables", - }, - cli.BoolFlag{ - Name: "privileged", - Usage: "Give the process extended Linux capabilities inside the container. The default is false", - }, - cli.BoolFlag{ - Name: "tty, t", - Usage: "Allocate a pseudo-TTY. The default is false", - }, - cli.StringFlag{ - Name: "user, u", - Usage: "Sets the username or UID used and optionally the groupname or GID for the specified command", - }, - } - execDescription = ` - kpod exec - - Run a command in a running container -` - - execCommand = cli.Command{ - Name: "exec", - Usage: "Run a process in a running container", - Description: execDescription, - Flags: execFlags, - Action: execCmd, - ArgsUsage: "CONTAINER-NAME", - } -) - -func execCmd(c *cli.Context) error { - var envs []string - args := c.Args() - if len(args) < 1 { - return errors.Errorf("you must provide one container name or id") - } - if len(args) < 2 { - return errors.Errorf("you must provide a command to exec") - } - cmd := args[1:] - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.Shutdown(false) - - ctr, err := runtime.LookupContainer(args[0]) - if err != nil { - return errors.Wrapf(err, "unable to exec into %s", args[0]) - } - // Create a list of keys provided by the user - var userEnvKeys []string - for _, env := range c.StringSlice("env") { - splitEnv := strings.Split(env, "=") - userEnvKeys = append(userEnvKeys, splitEnv[0]) - } - - envs = append(envs, c.StringSlice("env")...) - - // if the default key isnt in the user-provided list, add the default - // key and value to the environment variables. this is needed to set - // PATH for example. - for k, v := range defaultEnvVariables { - if !libpod.StringInSlice(k, userEnvKeys) { - envs = append(envs, fmt.Sprintf("%s=%s", k, v)) - } - } - - return ctr.Exec(c.Bool("tty"), c.Bool("privileged"), envs, cmd, c.String("user")) -} diff --git a/cmd/kpod/export.go b/cmd/kpod/export.go deleted file mode 100644 index 9b498562e..000000000 --- a/cmd/kpod/export.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "os" - - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -var ( - exportFlags = []cli.Flag{ - cli.StringFlag{ - Name: "output, o", - Usage: "Write to a file, default is STDOUT", - Value: "/dev/stdout", - }, - } - exportDescription = "Exports container's filesystem contents as a tar archive" + - " and saves it on the local machine." - exportCommand = cli.Command{ - Name: "export", - Usage: "Export container's filesystem contents as a tar archive", - Description: exportDescription, - Flags: exportFlags, - Action: exportCmd, - ArgsUsage: "CONTAINER", - } -) - -// exportCmd saves a container to a tarball on disk -func exportCmd(c *cli.Context) error { - if err := validateFlags(c, exportFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - args := c.Args() - if len(args) == 0 { - return errors.Errorf("container id must be specified") - } - if len(args) > 1 { - return errors.Errorf("too many arguments given, need 1 at most.") - } - - output := c.String("output") - if output == "/dev/stdout" { - file := os.Stdout - if logrus.IsTerminal(file) { - return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") - } - } - - ctr, err := runtime.LookupContainer(args[0]) - if err != nil { - return errors.Wrapf(err, "error looking up container %q", args[0]) - } - - return ctr.Export(output) -} diff --git a/cmd/kpod/formats/formats.go b/cmd/kpod/formats/formats.go deleted file mode 100644 index 4b6527b30..000000000 --- a/cmd/kpod/formats/formats.go +++ /dev/null @@ -1,143 +0,0 @@ -package formats - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "strings" - "text/tabwriter" - "text/template" - - "github.com/ghodss/yaml" - "github.com/pkg/errors" -) - -const ( - // JSONString const to save on duplicate variable names - JSONString = "json" - // IDString const to save on duplicates for Go templates - IDString = "{{.ID}}" -) - -// Writer interface for outputs -type Writer interface { - Out() error -} - -// JSONStructArray for JSON output -type JSONStructArray struct { - Output []interface{} -} - -// StdoutTemplateArray for Go template output -type StdoutTemplateArray struct { - Output []interface{} - Template string - Fields map[string]string -} - -// JSONStruct for JSON output -type JSONStruct struct { - Output interface{} -} - -// StdoutTemplate for Go template output -type StdoutTemplate struct { - Output interface{} - Template string - Fields map[string]string -} - -// YAMLStruct for YAML output -type YAMLStruct struct { - Output interface{} -} - -// Out method for JSON Arrays -func (j JSONStructArray) Out() error { - data, err := json.MarshalIndent(j.Output, "", " ") - if err != nil { - return err - } - - // JSON returns a byte array with a literal null [110 117 108 108] in it - // if it is passed empty data. We used bytes.Compare to see if that is - // the case. - if diff := bytes.Compare(data, []byte("null")); diff == 0 { - data = []byte("[]") - } - - // If the we did get NULL back, we should spit out {} which is - // at least valid JSON for the consumer. - fmt.Printf("%s\n", data) - return nil -} - -// Out method for Go templates -func (t StdoutTemplateArray) Out() error { - w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) - if strings.HasPrefix(t.Template, "table") { - // replace any spaces with tabs in template so that tabwriter can align it - t.Template = strings.Replace(strings.TrimSpace(t.Template[5:]), " ", "\t", -1) - headerTmpl, err := template.New("header").Funcs(headerFunctions).Parse(t.Template) - if err != nil { - return errors.Wrapf(err, "Template parsing error") - } - err = headerTmpl.Execute(w, t.Fields) - if err != nil { - return err - } - fmt.Fprintln(w, "") - } - t.Template = strings.Replace(t.Template, " ", "\t", -1) - tmpl, err := template.New("image").Funcs(basicFunctions).Parse(t.Template) - if err != nil { - return errors.Wrapf(err, "Template parsing error") - } - for _, img := range t.Output { - basicTmpl := tmpl.Funcs(basicFunctions) - err = basicTmpl.Execute(w, img) - if err != nil { - return err - } - fmt.Fprintln(w, "") - } - return w.Flush() -} - -// Out method for JSON struct -func (j JSONStruct) Out() error { - data, err := json.MarshalIndent(j.Output, "", " ") - if err != nil { - return err - } - fmt.Printf("%s\n", data) - return nil -} - -//Out method for Go templates -func (t StdoutTemplate) Out() error { - tmpl, err := template.New("image").Parse(t.Template) - if err != nil { - return errors.Wrapf(err, "template parsing error") - } - err = tmpl.Execute(os.Stdout, t.Output) - if err != nil { - return err - } - fmt.Println() - return nil -} - -// Out method for YAML -func (y YAMLStruct) Out() error { - var buf []byte - var err error - buf, err = yaml.Marshal(y.Output) - if err != nil { - return err - } - fmt.Println(string(buf)) - return nil -} diff --git a/cmd/kpod/formats/templates.go b/cmd/kpod/formats/templates.go deleted file mode 100644 index c2582552a..000000000 --- a/cmd/kpod/formats/templates.go +++ /dev/null @@ -1,78 +0,0 @@ -package formats - -import ( - "bytes" - "encoding/json" - "strings" - "text/template" -) - -// basicFunctions are the set of initial -// functions provided to every template. -var basicFunctions = template.FuncMap{ - "json": func(v interface{}) string { - buf := &bytes.Buffer{} - enc := json.NewEncoder(buf) - enc.SetEscapeHTML(false) - _ = enc.Encode(v) - // Remove the trailing new line added by the encoder - return strings.TrimSpace(buf.String()) - }, - "split": strings.Split, - "join": strings.Join, - "title": strings.Title, - "lower": strings.ToLower, - "upper": strings.ToUpper, - "pad": padWithSpace, - "truncate": truncateWithLength, -} - -// HeaderFunctions are used to created headers of a table. -// This is a replacement of basicFunctions for header generation -// because we want the header to remain intact. -// Some functions like `split` are irrelevant so not added. -var headerFunctions = template.FuncMap{ - "json": func(v string) string { - return v - }, - "title": func(v string) string { - return v - }, - "lower": func(v string) string { - return v - }, - "upper": func(v string) string { - return v - }, - "truncate": func(v string, l int) string { - return v - }, -} - -// Parse creates a new anonymous template with the basic functions -// and parses the given format. -func Parse(format string) (*template.Template, error) { - return NewParse("", format) -} - -// NewParse creates a new tagged template with the basic functions -// and parses the given format. -func NewParse(tag, format string) (*template.Template, error) { - return template.New(tag).Funcs(basicFunctions).Parse(format) -} - -// padWithSpace adds whitespace to the input if the input is non-empty -func padWithSpace(source string, prefix, suffix int) string { - if source == "" { - return source - } - return strings.Repeat(" ", prefix) + source + strings.Repeat(" ", suffix) -} - -// truncateWithLength truncates the source string up to the length provided by the input -func truncateWithLength(source string, length int) string { - if len(source) < length { - return source - } - return source[:length] -} diff --git a/cmd/kpod/history.go b/cmd/kpod/history.go deleted file mode 100644 index 20422b7c3..000000000 --- a/cmd/kpod/history.go +++ /dev/null @@ -1,246 +0,0 @@ -package main - -import ( - "reflect" - "strconv" - "strings" - "time" - - "github.com/containers/image/types" - units "github.com/docker/go-units" - "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/urfave/cli" -) - -const ( - createdByTruncLength = 45 - idTruncLength = 12 -) - -// historyTemplateParams stores info about each layer -type historyTemplateParams struct { - ID string - Created string - CreatedBy string - Size string - Comment string -} - -// historyJSONParams is only used when the JSON format is specified, -// and is better for data processing from JSON. -// historyJSONParams will be populated by data from v1.History and types.BlobInfo, -// the members of the struct are the sama data types as their sources. -type historyJSONParams struct { - ID string `json:"id"` - Created *time.Time `json:"created"` - CreatedBy string `json:"createdBy"` - Size int64 `json:"size"` - Comment string `json:"comment"` -} - -// historyOptions stores cli flag values -type historyOptions struct { - human bool - noTrunc bool - quiet bool - format string -} - -var ( - historyFlags = []cli.Flag{ - cli.BoolTFlag{ - Name: "human, H", - Usage: "Display sizes and dates in human readable format", - }, - cli.BoolFlag{ - Name: "no-trunc, notruncate", - Usage: "Do not truncate the output", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Display the numeric IDs only", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output to JSON or a Go template", - }, - } - - historyDescription = "Displays the history of an image. The information can be printed out in an easy to read, " + - "or user specified format, and can be truncated." - historyCommand = cli.Command{ - Name: "history", - Usage: "Show history of a specified image", - Description: historyDescription, - Flags: historyFlags, - Action: historyCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - } -) - -func historyCmd(c *cli.Context) error { - if err := validateFlags(c, historyFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - format := genHistoryFormat(c.String("format"), c.Bool("quiet")) - - args := c.Args() - if len(args) == 0 { - return errors.Errorf("an image name must be specified") - } - if len(args) > 1 { - return errors.Errorf("Kpod history takes at most 1 argument") - } - imgName := args[0] - - opts := historyOptions{ - human: c.BoolT("human"), - noTrunc: c.Bool("no-trunc"), - quiet: c.Bool("quiet"), - format: format, - } - - history, layers, imageID, err := runtime.GetHistory(imgName) - if err != nil { - return errors.Wrapf(err, "error getting history of image %q", imgName) - } - - return generateHistoryOutput(history, layers, imageID, opts) -} - -func genHistoryFormat(format string, quiet bool) string { - if format != "" { - // "\t" from the command line is not being recognized as a tab - // replacing the string "\t" to a tab character if the user passes in "\t" - return strings.Replace(format, `\t`, "\t", -1) - } - if quiet { - return formats.IDString - } - return "table {{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\t" -} - -// historyToGeneric makes an empty array of interfaces for output -func historyToGeneric(templParams []historyTemplateParams, JSONParams []historyJSONParams) (genericParams []interface{}) { - if len(templParams) > 0 { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return - } - for _, v := range JSONParams { - genericParams = append(genericParams, interface{}(v)) - } - return -} - -// generate the header based on the template provided -func (h *historyTemplateParams) headerMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(h)) - values := make(map[string]string) - for h := 0; h < v.NumField(); h++ { - key := v.Type().Field(h).Name - value := key - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -// getHistorytemplateOutput gets the modified history information to be printed in human readable format -func getHistoryTemplateOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) (historyOutput []historyTemplateParams) { - var ( - outputSize string - createdTime string - createdBy string - count = 1 - ) - for i := len(history) - 1; i >= 0; i-- { - if i != len(history)-1 { - imageID = "<missing>" - } - if !opts.noTrunc && i == len(history)-1 { - imageID = imageID[:idTruncLength] - } - - var size int64 - if !history[i].EmptyLayer { - size = layers[len(layers)-count].Size - count++ - } - - if opts.human { - createdTime = units.HumanDuration(time.Since((*history[i].Created))) + " ago" - outputSize = units.HumanSize(float64(size)) - } else { - createdTime = (history[i].Created).Format(time.RFC3339) - outputSize = strconv.FormatInt(size, 10) - } - - createdBy = strings.Join(strings.Fields(history[i].CreatedBy), " ") - if !opts.noTrunc && len(createdBy) > createdByTruncLength { - createdBy = createdBy[:createdByTruncLength-3] + "..." - } - - params := historyTemplateParams{ - ID: imageID, - Created: createdTime, - CreatedBy: createdBy, - Size: outputSize, - Comment: history[i].Comment, - } - historyOutput = append(historyOutput, params) - } - return -} - -// getHistoryJSONOutput returns the history information in its raw form -func getHistoryJSONOutput(history []v1.History, layers []types.BlobInfo, imageID string) (historyOutput []historyJSONParams) { - count := 1 - for i := len(history) - 1; i >= 0; i-- { - var size int64 - if !history[i].EmptyLayer { - size = layers[len(layers)-count].Size - count++ - } - - params := historyJSONParams{ - ID: imageID, - Created: history[i].Created, - CreatedBy: history[i].CreatedBy, - Size: size, - Comment: history[i].Comment, - } - historyOutput = append(historyOutput, params) - } - return -} - -// generateHistoryOutput generates the history based on the format given -func generateHistoryOutput(history []v1.History, layers []types.BlobInfo, imageID string, opts historyOptions) error { - if len(history) == 0 { - return nil - } - - var out formats.Writer - - switch opts.format { - case formats.JSONString: - historyOutput := getHistoryJSONOutput(history, layers, imageID) - out = formats.JSONStructArray{Output: historyToGeneric([]historyTemplateParams{}, historyOutput)} - default: - historyOutput := getHistoryTemplateOutput(history, layers, imageID, opts) - out = formats.StdoutTemplateArray{Output: historyToGeneric(historyOutput, []historyJSONParams{}), Template: opts.format, Fields: historyOutput[0].headerMap()} - } - - return formats.Writer(out).Out() -} diff --git a/cmd/kpod/images.go b/cmd/kpod/images.go deleted file mode 100644 index 2b1003ebd..000000000 --- a/cmd/kpod/images.go +++ /dev/null @@ -1,337 +0,0 @@ -package main - -import ( - "fmt" - "reflect" - "strings" - "time" - - "github.com/containers/storage" - "github.com/docker/go-units" - digest "github.com/opencontainers/go-digest" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/projectatomic/libpod/libpod" - "github.com/projectatomic/libpod/libpod/common" - "github.com/urfave/cli" -) - -type imagesTemplateParams struct { - Repository string - Tag string - ID string - Digest digest.Digest - Created string - Size string -} - -type imagesJSONParams struct { - ID string `json:"id"` - Name []string `json:"names"` - Digest digest.Digest `json:"digest"` - Created time.Time `json:"created"` - Size int64 `json:"size"` -} - -type imagesOptions struct { - quiet bool - noHeading bool - noTrunc bool - digests bool - format string -} - -var ( - imagesFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "quiet, q", - Usage: "display only image IDs", - }, - cli.BoolFlag{ - Name: "noheading, n", - Usage: "do not print column headings", - }, - cli.BoolFlag{ - Name: "no-trunc, notruncate", - Usage: "do not truncate output", - }, - cli.BoolFlag{ - Name: "digests", - Usage: "show digests", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format to JSON or a Go template", - }, - cli.StringFlag{ - Name: "filter, f", - Usage: "filter output based on conditions provided (default [])", - }, - } - - imagesDescription = "lists locally stored images." - imagesCommand = cli.Command{ - Name: "images", - Usage: "list images in local storage", - Description: imagesDescription, - Flags: imagesFlags, - Action: imagesCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - } -) - -func imagesCmd(c *cli.Context) error { - if err := validateFlags(c, imagesFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "Could not get runtime") - } - defer runtime.Shutdown(false) - - format := genImagesFormat(c.String("format"), c.Bool("quiet"), c.Bool("noheading"), c.Bool("digests")) - - opts := imagesOptions{ - quiet: c.Bool("quiet"), - noHeading: c.Bool("noheading"), - noTrunc: c.Bool("no-trunc"), - digests: c.Bool("digests"), - format: format, - } - - var imageInput string - if len(c.Args()) == 1 { - imageInput = c.Args().Get(0) - } - if len(c.Args()) > 1 { - return errors.New("'kpod images' requires at most 1 argument") - } - - params, err := runtime.ParseImageFilter(imageInput, c.String("filter")) - if err != nil { - return errors.Wrapf(err, "error parsing filter") - } - - // generate the different filters - labelFilter := generateImagesFilter(params, "label") - beforeImageFilter := generateImagesFilter(params, "before-image") - sinceImageFilter := generateImagesFilter(params, "since-image") - danglingFilter := generateImagesFilter(params, "dangling") - referenceFilter := generateImagesFilter(params, "reference") - imageInputFilter := generateImagesFilter(params, "image-input") - - images, err := runtime.GetImages(params, labelFilter, beforeImageFilter, sinceImageFilter, danglingFilter, referenceFilter, imageInputFilter) - if err != nil { - return errors.Wrapf(err, "could not get list of images matching filter") - } - - return generateImagesOutput(runtime, images, opts) -} - -func genImagesFormat(format string, quiet, noHeading, digests bool) string { - if format != "" { - // "\t" from the command line is not being recognized as a tab - // replacing the string "\t" to a tab character if the user passes in "\t" - return strings.Replace(format, `\t`, "\t", -1) - } - if quiet { - return formats.IDString - } - format = "table {{.Repository}}\t{{.Tag}}\t" - if noHeading { - format = "{{.Repository}}\t{{.Tag}}\t" - } - if digests { - format += "{{.Digest}}\t" - } - format += "{{.ID}}\t{{.Created}}\t{{.Size}}\t" - return format -} - -// imagesToGeneric creates an empty array of interfaces for output -func imagesToGeneric(templParams []imagesTemplateParams, JSONParams []imagesJSONParams) (genericParams []interface{}) { - if len(templParams) > 0 { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return - } - for _, v := range JSONParams { - genericParams = append(genericParams, interface{}(v)) - } - return -} - -// generate the header based on the template provided -func (i *imagesTemplateParams) headerMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(i)) - values := make(map[string]string) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - if value == "ID" { - value = "Image" + value - } - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -// getImagesTemplateOutput returns the images information to be printed in human readable format -func getImagesTemplateOutput(runtime *libpod.Runtime, images []*storage.Image, opts imagesOptions) (imagesOutput []imagesTemplateParams) { - var ( - lastID string - ) - for _, img := range images { - if opts.quiet && lastID == img.ID { - continue // quiet should not show the same ID multiple times - } - createdTime := img.Created - - imageID := "sha256:" + img.ID - if !opts.noTrunc { - imageID = img.ID[:idTruncLength] - } - - repository := "<none>" - tag := "<none>" - if len(img.Names) > 0 { - arr := strings.Split(img.Names[0], ":") - repository = arr[0] - if len(arr) == 2 { - tag = arr[1] - } - } - - imgData, _ := runtime.GetImageInspectInfo(*img) - if imgData != nil { - createdTime = *imgData.Created - } - - params := imagesTemplateParams{ - Repository: repository, - Tag: tag, - ID: imageID, - Digest: imgData.Digest, - Created: units.HumanDuration(time.Since((createdTime))) + " ago", - Size: units.HumanSizeWithPrecision(float64(imgData.Size), 3), - } - imagesOutput = append(imagesOutput, params) - } - return -} - -// getImagesJSONOutput returns the images information in its raw form -func getImagesJSONOutput(runtime *libpod.Runtime, images []*storage.Image) (imagesOutput []imagesJSONParams) { - for _, img := range images { - createdTime := img.Created - - imgData, _ := runtime.GetImageInspectInfo(*img) - if imgData != nil { - createdTime = *imgData.Created - } - - params := imagesJSONParams{ - ID: img.ID, - Name: img.Names, - Digest: imgData.Digest, - Created: createdTime, - Size: imgData.Size, - } - imagesOutput = append(imagesOutput, params) - } - return -} - -// generateImagesOutput generates the images based on the format provided -func generateImagesOutput(runtime *libpod.Runtime, images []*storage.Image, opts imagesOptions) error { - if len(images) == 0 { - return nil - } - - var out formats.Writer - - switch opts.format { - case formats.JSONString: - imagesOutput := getImagesJSONOutput(runtime, images) - out = formats.JSONStructArray{Output: imagesToGeneric([]imagesTemplateParams{}, imagesOutput)} - default: - imagesOutput := getImagesTemplateOutput(runtime, images, opts) - out = formats.StdoutTemplateArray{Output: imagesToGeneric(imagesOutput, []imagesJSONParams{}), Template: opts.format, Fields: imagesOutput[0].headerMap()} - - } - - return formats.Writer(out).Out() -} - -// generateImagesFilter returns an ImageFilter based on filterType -// to add more filters, define a new case and write what the ImageFilter function should do -func generateImagesFilter(params *libpod.ImageFilterParams, filterType string) libpod.ImageFilter { - switch filterType { - case "label": - return func(image *storage.Image, info *libpod.ImageData) bool { - if params == nil || params.Label == "" { - return true - } - - pair := strings.SplitN(params.Label, "=", 2) - if val, ok := info.Labels[pair[0]]; ok { - if len(pair) == 2 && val == pair[1] { - return true - } - if len(pair) == 1 { - return true - } - } - return false - } - case "before-image": - return func(image *storage.Image, info *libpod.ImageData) bool { - if params == nil || params.BeforeImage.IsZero() { - return true - } - return info.Created.Before(params.BeforeImage) - } - case "since-image": - return func(image *storage.Image, info *libpod.ImageData) bool { - if params == nil || params.SinceImage.IsZero() { - return true - } - return info.Created.After(params.SinceImage) - } - case "dangling": - return func(image *storage.Image, info *libpod.ImageData) bool { - if params == nil || params.Dangling == "" { - return true - } - if common.IsFalse(params.Dangling) && params.ImageName != "<none>" { - return true - } - if common.IsTrue(params.Dangling) && params.ImageName == "<none>" { - return true - } - return false - } - case "reference": - return func(image *storage.Image, info *libpod.ImageData) bool { - if params == nil || params.ReferencePattern == "" { - return true - } - return libpod.MatchesReference(params.ImageName, params.ReferencePattern) - } - case "image-input": - return func(image *storage.Image, info *libpod.ImageData) bool { - if params == nil || params.ImageInput == "" { - return true - } - return libpod.MatchesReference(params.ImageName, params.ImageInput) - } - default: - fmt.Println("invalid filter type", filterType) - return nil - } -} diff --git a/cmd/kpod/import.go b/cmd/kpod/import.go deleted file mode 100644 index 2e8702c3d..000000000 --- a/cmd/kpod/import.go +++ /dev/null @@ -1,190 +0,0 @@ -package main - -import ( - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "strings" - - "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -var ( - importFlags = []cli.Flag{ - cli.StringSliceFlag{ - Name: "change, c", - Usage: "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR", - }, - cli.StringFlag{ - Name: "message, m", - Usage: "Set commit message for imported image", - }, - } - importDescription = `Create a container image from the contents of the specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz). - Note remote tar balls can be specified, via web address. - Optionally tag the image. You can specify the Dockerfile instructions using the --change option. - ` - importCommand = cli.Command{ - Name: "import", - Usage: "Import a tarball to create a filesystem image", - Description: importDescription, - Flags: importFlags, - Action: importCmd, - ArgsUsage: "TARBALL [REFERENCE]", - } -) - -func importCmd(c *cli.Context) error { - if err := validateFlags(c, importFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - var opts libpod.CopyOptions - var source string - args := c.Args() - switch len(args) { - case 0: - return errors.Errorf("need to give the path to the tarball, or must specify a tarball of '-' for stdin") - case 1: - source = args[0] - case 2: - source = args[0] - opts.Reference = args[1] - default: - return errors.Errorf("too many arguments. Usage TARBALL [REFERENCE]") - } - - changes := v1.ImageConfig{} - if c.IsSet("change") { - changes, err = getImageConfig(c.StringSlice("change")) - if err != nil { - return errors.Wrapf(err, "error adding config changes to image %q", source) - } - } - - history := []v1.History{ - {Comment: c.String("message")}, - } - - config := v1.Image{ - Config: changes, - History: history, - } - - opts.ImageConfig = config - - // if source is a url, download it and save to a temp file - u, err := url.ParseRequestURI(source) - if err == nil && u.Scheme != "" { - file, err := downloadFromURL(source) - if err != nil { - return err - } - defer os.Remove(file) - source = file - } - - return runtime.ImportImage(source, opts) -} - -// donwloadFromURL downloads an image in the format "https:/example.com/myimage.tar" -// and tempoarily saves in it /var/tmp/importxyz, which is deleted after the image is imported -func downloadFromURL(source string) (string, error) { - fmt.Printf("Downloading from %q\n", source) - - outFile, err := ioutil.TempFile("/var/tmp", "import") - if err != nil { - return "", errors.Wrap(err, "error creating file") - } - defer outFile.Close() - - response, err := http.Get(source) - if err != nil { - return "", errors.Wrapf(err, "error downloading %q", source) - } - defer response.Body.Close() - - _, err = io.Copy(outFile, response.Body) - if err != nil { - return "", errors.Wrapf(err, "error saving %q to %q", source, outFile) - } - - return outFile.Name(), nil -} - -// getImageConfig converts the --change flag values in the format "CMD=/bin/bash USER=example" -// to a type v1.ImageConfig -func getImageConfig(changes []string) (v1.ImageConfig, error) { - // USER=value | EXPOSE=value | ENV=value | ENTRYPOINT=value | - // CMD=value | VOLUME=value | WORKDIR=value | LABEL=key=value | STOPSIGNAL=value - - var ( - user string - env []string - entrypoint []string - cmd []string - workingDir string - stopSignal string - ) - - exposedPorts := make(map[string]struct{}) - volumes := make(map[string]struct{}) - labels := make(map[string]string) - - for _, ch := range changes { - pair := strings.Split(ch, "=") - if len(pair) == 1 { - return v1.ImageConfig{}, errors.Errorf("no value given for instruction %q", ch) - } - switch pair[0] { - case "USER": - user = pair[1] - case "EXPOSE": - var st struct{} - exposedPorts[pair[1]] = st - case "ENV": - env = append(env, pair[1]) - case "ENTRYPOINT": - entrypoint = append(entrypoint, pair[1]) - case "CMD": - cmd = append(cmd, pair[1]) - case "VOLUME": - var st struct{} - volumes[pair[1]] = st - case "WORKDIR": - workingDir = pair[1] - case "LABEL": - if len(pair) == 3 { - labels[pair[1]] = pair[2] - } else { - labels[pair[1]] = "" - } - case "STOPSIGNAL": - stopSignal = pair[1] - } - } - - return v1.ImageConfig{ - User: user, - ExposedPorts: exposedPorts, - Env: env, - Entrypoint: entrypoint, - Cmd: cmd, - Volumes: volumes, - WorkingDir: workingDir, - Labels: labels, - StopSignal: stopSignal, - }, nil -} diff --git a/cmd/kpod/info.go b/cmd/kpod/info.go deleted file mode 100644 index c491b4585..000000000 --- a/cmd/kpod/info.go +++ /dev/null @@ -1,84 +0,0 @@ -package main - -import ( - "runtime" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -var ( - infoDescription = "display system information" - infoCommand = cli.Command{ - Name: "info", - Usage: infoDescription, - Description: `Information display here pertain to the host, current storage stats, and build of kpod. Useful for the user and when reporting issues.`, - Flags: infoFlags, - Action: infoCmd, - ArgsUsage: "", - } - infoFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "debug, D", - Usage: "display additional debug information", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format to JSON or a Go template", - }, - } -) - -func infoCmd(c *cli.Context) error { - if err := validateFlags(c, infoFlags); err != nil { - return err - } - info := map[string]interface{}{} - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - infoArr, err := runtime.Info() - if err != nil { - return errors.Wrapf(err, "error getting info") - } - - if c.Bool("debug") { - debugInfo := debugInfo(c) - infoArr = append(infoArr, libpod.InfoData{Type: "debug", Data: debugInfo}) - } - - for _, currInfo := range infoArr { - info[currInfo.Type] = currInfo.Data - } - - var out formats.Writer - infoOutputFormat := c.String("format") - switch infoOutputFormat { - case formats.JSONString: - out = formats.JSONStruct{Output: info} - case "": - out = formats.YAMLStruct{Output: info} - default: - out = formats.StdoutTemplate{Output: info, Template: infoOutputFormat} - } - - formats.Writer(out).Out() - - return nil -} - -// top-level "debug" info -func debugInfo(c *cli.Context) map[string]interface{} { - info := map[string]interface{}{} - info["compiler"] = runtime.Compiler - info["go version"] = runtime.Version() - info["kpod version"] = c.App.Version - info["git commit"] = gitCommit - return info -} diff --git a/cmd/kpod/inspect.go b/cmd/kpod/inspect.go deleted file mode 100644 index 3f9da8d83..000000000 --- a/cmd/kpod/inspect.go +++ /dev/null @@ -1,364 +0,0 @@ -package main - -import ( - "encoding/json" - - specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/projectatomic/libpod/libpod" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -const ( - inspectTypeContainer = "container" - inspectTypeImage = "image" - inspectAll = "all" -) - -var ( - inspectFlags = []cli.Flag{ - cli.StringFlag{ - Name: "type, t", - Value: inspectAll, - Usage: "Return JSON for specified type, (e.g image, container or task)", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "Change the output format to a Go template", - }, - cli.BoolFlag{ - Name: "size", - Usage: "Display total file size if the type is container", - }, - } - inspectDescription = "This displays the low-level information on containers and images identified by name or ID. By default, this will render all results in a JSON array. If the container and image have the same name, this will return container JSON for unspecified type." - inspectCommand = cli.Command{ - Name: "inspect", - Usage: "Displays the configuration of a container or image", - Description: inspectDescription, - Flags: inspectFlags, - Action: inspectCmd, - ArgsUsage: "CONTAINER-OR-IMAGE", - } -) - -func inspectCmd(c *cli.Context) error { - args := c.Args() - if len(args) == 0 { - return errors.Errorf("container or image name must be specified: kpod inspect [options [...]] name") - } - if len(args) > 1 { - return errors.Errorf("too many arguments specified") - } - if err := validateFlags(c, inspectFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.Shutdown(false) - - if c.String("type") != inspectTypeContainer && c.String("type") != inspectTypeImage && c.String("type") != inspectAll { - return errors.Errorf("the only recognized types are %q, %q, and %q", inspectTypeContainer, inspectTypeImage, inspectAll) - } - - name := args[0] - - outputFormat := c.String("format") - var data interface{} - switch c.String("type") { - case inspectTypeContainer: - ctr, err := runtime.LookupContainer(name) - if err != nil { - return errors.Wrapf(err, "error looking up container %q", name) - } - libpodInspectData, err := ctr.Inspect(c.Bool("size")) - if err != nil { - return errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) - } - data, err = getCtrInspectInfo(ctr, libpodInspectData) - if err != nil { - return errors.Wrapf(err, "error parsing container data %q", ctr.ID()) - } - case inspectTypeImage: - image, err := runtime.GetImage(name) - if err != nil { - return errors.Wrapf(err, "error getting image %q", name) - } - data, err = runtime.GetImageInspectInfo(*image) - if err != nil { - return errors.Wrapf(err, "error parsing image data %q", image.ID) - } - case inspectAll: - ctr, err := runtime.LookupContainer(name) - if err != nil { - image, err := runtime.GetImage(name) - if err != nil { - return errors.Wrapf(err, "error getting image %q", name) - } - data, err = runtime.GetImageInspectInfo(*image) - if err != nil { - return errors.Wrapf(err, "error parsing image data %q", image.ID) - } - } else { - libpodInspectData, err := ctr.Inspect(c.Bool("size")) - if err != nil { - return errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) - } - data, err = getCtrInspectInfo(ctr, libpodInspectData) - if err != nil { - return errors.Wrapf(err, "error parsing container data %q", ctr.ID) - } - } - } - - var out formats.Writer - if outputFormat != "" && outputFormat != formats.JSONString { - //template - out = formats.StdoutTemplate{Output: data, Template: outputFormat} - } else { - // default is json output - out = formats.JSONStruct{Output: data} - } - - formats.Writer(out).Out() - return nil -} - -func getCtrInspectInfo(ctr *libpod.Container, ctrInspectData *libpod.ContainerInspectData) (*ContainerData, error) { - config := ctr.Config() - spec := config.Spec - - cpus, mems, period, quota, realtimePeriod, realtimeRuntime, shares := getCPUInfo(spec) - blkioWeight, blkioWeightDevice, blkioReadBps, blkioWriteBps, blkioReadIOPS, blkioeWriteIOPS := getBLKIOInfo(spec) - memKernel, memReservation, memSwap, memSwappiness, memDisableOOMKiller := getMemoryInfo(spec) - pidsLimit := getPidsInfo(spec) - cgroup := getCgroup(spec) - - var createArtifact createConfig - artifact, err := ctr.GetArtifact("create-config") - if err == nil { - if err := json.Unmarshal(artifact, &createArtifact); err != nil { - return nil, err - } - } else { - logrus.Errorf("couldn't get some inspect information, error getting artifact %q: %v", ctr.ID(), err) - } - - data := &ContainerData{ - CtrInspectData: ctrInspectData, - HostConfig: &HostConfig{ - ConsoleSize: spec.Process.ConsoleSize, - OomScoreAdj: spec.Process.OOMScoreAdj, - CPUShares: shares, - BlkioWeight: blkioWeight, - BlkioWeightDevice: blkioWeightDevice, - BlkioDeviceReadBps: blkioReadBps, - BlkioDeviceWriteBps: blkioWriteBps, - BlkioDeviceReadIOps: blkioReadIOPS, - BlkioDeviceWriteIOps: blkioeWriteIOPS, - CPUPeriod: period, - CPUQuota: quota, - CPURealtimePeriod: realtimePeriod, - CPURealtimeRuntime: realtimeRuntime, - CPUSetCPUs: cpus, - CPUSetMems: mems, - Devices: spec.Linux.Devices, - KernelMemory: memKernel, - MemoryReservation: memReservation, - MemorySwap: memSwap, - MemorySwappiness: memSwappiness, - OomKillDisable: memDisableOOMKiller, - PidsLimit: pidsLimit, - Privileged: spec.Process.NoNewPrivileges, - ReadonlyRootfs: spec.Root.Readonly, - Runtime: ctr.RuntimeName(), - NetworkMode: string(createArtifact.NetMode), - IpcMode: string(createArtifact.IpcMode), - Cgroup: cgroup, - UTSMode: string(createArtifact.UtsMode), - UsernsMode: createArtifact.NsUser, - GroupAdd: spec.Process.User.AdditionalGids, - ContainerIDFile: createArtifact.CidFile, - AutoRemove: createArtifact.Rm, - CapAdd: createArtifact.CapAdd, - CapDrop: createArtifact.CapDrop, - DNS: createArtifact.DNSServers, - DNSOptions: createArtifact.DNSOpt, - DNSSearch: createArtifact.DNSSearch, - PidMode: string(createArtifact.PidMode), - CgroupParent: createArtifact.CgroupParent, - ShmSize: createArtifact.Resources.ShmSize, - Memory: createArtifact.Resources.Memory, - Ulimits: createArtifact.Resources.Ulimit, - SecurityOpt: createArtifact.SecurityOpts, - }, - Config: &CtrConfig{ - Hostname: spec.Hostname, - User: spec.Process.User, - Env: spec.Process.Env, - Image: config.RootfsImageName, - WorkingDir: spec.Process.Cwd, - Labels: config.Labels, - Annotations: spec.Annotations, - Tty: spec.Process.Terminal, - OpenStdin: config.Stdin, - StopSignal: config.StopSignal, - Cmd: config.Spec.Process.Args, - Entrypoint: createArtifact.Entrypoint, - }, - } - return data, nil -} - -func getCPUInfo(spec *specs.Spec) (string, string, *uint64, *int64, *uint64, *int64, *uint64) { - if spec.Linux.Resources == nil { - return "", "", nil, nil, nil, nil, nil - } - cpu := spec.Linux.Resources.CPU - if cpu == nil { - return "", "", nil, nil, nil, nil, nil - } - return cpu.Cpus, cpu.Mems, cpu.Period, cpu.Quota, cpu.RealtimePeriod, cpu.RealtimeRuntime, cpu.Shares -} - -func getBLKIOInfo(spec *specs.Spec) (*uint16, []specs.LinuxWeightDevice, []specs.LinuxThrottleDevice, []specs.LinuxThrottleDevice, []specs.LinuxThrottleDevice, []specs.LinuxThrottleDevice) { - if spec.Linux.Resources == nil { - return nil, nil, nil, nil, nil, nil - } - blkio := spec.Linux.Resources.BlockIO - if blkio == nil { - return nil, nil, nil, nil, nil, nil - } - return blkio.Weight, blkio.WeightDevice, blkio.ThrottleReadBpsDevice, blkio.ThrottleWriteBpsDevice, blkio.ThrottleReadIOPSDevice, blkio.ThrottleWriteIOPSDevice -} - -func getMemoryInfo(spec *specs.Spec) (*int64, *int64, *int64, *uint64, *bool) { - if spec.Linux.Resources == nil { - return nil, nil, nil, nil, nil - } - memory := spec.Linux.Resources.Memory - if memory == nil { - return nil, nil, nil, nil, nil - } - return memory.Kernel, memory.Reservation, memory.Swap, memory.Swappiness, memory.DisableOOMKiller -} - -func getPidsInfo(spec *specs.Spec) *int64 { - if spec.Linux.Resources == nil { - return nil - } - pids := spec.Linux.Resources.Pids - if pids == nil { - return nil - } - return &pids.Limit -} - -func getCgroup(spec *specs.Spec) string { - cgroup := "host" - for _, ns := range spec.Linux.Namespaces { - if ns.Type == specs.CgroupNamespace && ns.Path != "" { - cgroup = "container" - } - } - return cgroup -} - -// ContainerData holds the kpod inspect data for a container -type ContainerData struct { - CtrInspectData *libpod.ContainerInspectData `json:"CtrInspectData"` - HostConfig *HostConfig `json:"HostConfig"` - Config *CtrConfig `json:"Config"` -} - -// LogConfig holds the log information for a container -type LogConfig struct { - Type string `json:"Type"` // TODO - Config map[string]string `json:"Config"` //idk type, TODO -} - -// HostConfig represents the host configuration for the container -type HostConfig struct { - ContainerIDFile string `json:"ContainerIDFile"` - LogConfig *LogConfig `json:"LogConfig"` //TODO - NetworkMode string `json:"NetworkMode"` - PortBindings map[string]struct{} `json:"PortBindings"` //TODO - AutoRemove bool `json:"AutoRemove"` - CapAdd []string `json:"CapAdd"` - CapDrop []string `json:"CapDrop"` - DNS []string `json:"DNS"` - DNSOptions []string `json:"DNSOptions"` - DNSSearch []string `json:"DNSSearch"` - ExtraHosts []string `json:"ExtraHosts"` - GroupAdd []uint32 `json:"GroupAdd"` - IpcMode string `json:"IpcMode"` - Cgroup string `json:"Cgroup"` - OomScoreAdj *int `json:"OomScoreAdj"` - PidMode string `json:"PidMode"` - Privileged bool `json:"Privileged"` - PublishAllPorts bool `json:"PublishAllPorts"` //TODO - ReadonlyRootfs bool `json:"ReadonlyRootfs"` - SecurityOpt []string `json:"SecurityOpt"` - UTSMode string `json:"UTSMode"` - UsernsMode string `json:"UsernsMode"` - ShmSize string `json:"ShmSize"` - Runtime string `json:"Runtime"` - ConsoleSize *specs.Box `json:"ConsoleSize"` - Isolation string `json:"Isolation"` //TODO - CPUShares *uint64 `json:"CPUSShares"` - Memory int64 `json:"Memory"` - NanoCPUs int `json:"NanoCPUs"` //check type, TODO - CgroupParent string `json:"CgroupParent"` - BlkioWeight *uint16 `json:"BlkioWeight"` - BlkioWeightDevice []specs.LinuxWeightDevice `json:"BlkioWeightDevice"` - BlkioDeviceReadBps []specs.LinuxThrottleDevice `json:"BlkioDeviceReadBps"` - BlkioDeviceWriteBps []specs.LinuxThrottleDevice `json:"BlkioDeviceWriteBps"` - BlkioDeviceReadIOps []specs.LinuxThrottleDevice `json:"BlkioDeviceReadIOps"` - BlkioDeviceWriteIOps []specs.LinuxThrottleDevice `json:"BlkioDeviceWriteIOps"` - CPUPeriod *uint64 `json:"CPUPeriod"` - CPUQuota *int64 `json:"CPUQuota"` - CPURealtimePeriod *uint64 `json:"CPURealtimePeriod"` - CPURealtimeRuntime *int64 `json:"CPURealtimeRuntime"` - CPUSetCPUs string `json:"CPUSetCPUs"` - CPUSetMems string `json:"CPUSetMems"` - Devices []specs.LinuxDevice `json:"Devices"` - DiskQuota int `json:"DiskQuota"` //check type, TODO - KernelMemory *int64 `json:"KernelMemory"` - MemoryReservation *int64 `json:"MemoryReservation"` - MemorySwap *int64 `json:"MemorySwap"` - MemorySwappiness *uint64 `json:"MemorySwappiness"` - OomKillDisable *bool `json:"OomKillDisable"` - PidsLimit *int64 `json:"PidsLimit"` - Ulimits []string `json:"Ulimits"` - CPUCount int `json:"CPUCount"` //check type, TODO - CPUPercent int `json:"CPUPercent"` //check type, TODO - IOMaximumIOps int `json:"IOMaximumIOps"` //check type, TODO - IOMaximumBandwidth int `json:"IOMaximumBandwidth"` //check type, TODO -} - -// CtrConfig holds information about the container configuration -type CtrConfig struct { - Hostname string `json:"Hostname"` - DomainName string `json:"Domainname"` //TODO - User specs.User `json:"User"` - AttachStdin bool `json:"AttachStdin"` //TODO - AttachStdout bool `json:"AttachStdout"` //TODO - AttachStderr bool `json:"AttachStderr"` //TODO - Tty bool `json:"Tty"` - OpenStdin bool `json:"OpenStdin"` - StdinOnce bool `json:"StdinOnce"` //TODO - Env []string `json:"Env"` - Cmd []string `json:"Cmd"` - Image string `json:"Image"` - Volumes map[string]struct{} `json:"Volumes"` - WorkingDir string `json:"WorkingDir"` - Entrypoint string `json:"Entrypoint"` - Labels map[string]string `json:"Labels"` - Annotations map[string]string `json:"Annotations"` - StopSignal uint `json:"StopSignal"` -} diff --git a/cmd/kpod/kill.go b/cmd/kpod/kill.go deleted file mode 100644 index 776c7ef20..000000000 --- a/cmd/kpod/kill.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "fmt" - "os" - "syscall" - - "github.com/docker/docker/pkg/signal" - "github.com/pkg/errors" - "github.com/urfave/cli" -) - -var ( - killFlags = []cli.Flag{ - cli.StringFlag{ - Name: "signal, s", - Usage: "Signal to send to the container", - Value: "KILL", - }, - } - killDescription = "The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal." - killCommand = cli.Command{ - Name: "kill", - Usage: "Kill one or more running containers with a specific signal", - Description: killDescription, - Flags: killFlags, - Action: killCmd, - ArgsUsage: "[CONTAINER_NAME_OR_ID]", - } -) - -// killCmd kills one or more containers with a signal -func killCmd(c *cli.Context) error { - args := c.Args() - if len(args) == 0 { - return errors.Errorf("specify one or more containers to kill") - } - if err := validateFlags(c, killFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - var killSignal uint = uint(syscall.SIGTERM) - if c.String("signal") != "" { - // Check if the signalString provided by the user is valid - // Invalid signals will return err - sysSignal, err := signal.ParseSignal(c.String("signal")) - if err != nil { - return err - } - killSignal = uint(sysSignal) - } - - var lastError error - for _, container := range c.Args() { - ctr, err := runtime.LookupContainer(container) - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to find container %v", container) - continue - } - - if err := ctr.Kill(killSignal); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to find container %v", container) - } else { - fmt.Println(ctr.ID()) - } - } - return lastError -} diff --git a/cmd/kpod/load.go b/cmd/kpod/load.go deleted file mode 100644 index 5ae75a7a2..000000000 --- a/cmd/kpod/load.go +++ /dev/null @@ -1,123 +0,0 @@ -package main - -import ( - "fmt" - "io" - "io/ioutil" - "os" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -var ( - loadFlags = []cli.Flag{ - cli.StringFlag{ - Name: "input, i", - Usage: "Read from archive file, default is STDIN", - Value: "/dev/stdin", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress the output", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`pathname` of signature policy file (not usually used)", - }, - } - loadDescription = "Loads the image from docker-archive stored on the local machine." - loadCommand = cli.Command{ - Name: "load", - Usage: "load an image from docker archive", - Description: loadDescription, - Flags: loadFlags, - Action: loadCmd, - ArgsUsage: "", - } -) - -// loadCmd gets the image/file to be loaded from the command line -// and calls loadImage to load the image to containers-storage -func loadCmd(c *cli.Context) error { - - args := c.Args() - var image string - if len(args) == 1 { - image = args[0] - } - if len(args) > 1 { - return errors.New("too many arguments. Requires exactly 1") - } - if err := validateFlags(c, loadFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - input := c.String("input") - - if input == "/dev/stdin" { - fi, err := os.Stdin.Stat() - if err != nil { - return err - } - // checking if loading from pipe - if !fi.Mode().IsRegular() { - outFile, err := ioutil.TempFile("/var/tmp", "kpod") - if err != nil { - return errors.Errorf("error creating file %v", err) - } - defer outFile.Close() - defer os.Remove(outFile.Name()) - - inFile, err := os.OpenFile(input, 0, 0666) - if err != nil { - return errors.Errorf("error reading file %v", err) - } - defer inFile.Close() - - _, err = io.Copy(outFile, inFile) - if err != nil { - return errors.Errorf("error copying file %v", err) - } - - input = outFile.Name() - } - } - - var writer io.Writer - if !c.Bool("quiet") { - writer = os.Stdout - } - - options := libpod.CopyOptions{ - SignaturePolicyPath: c.String("signature-policy"), - Writer: writer, - } - - src := libpod.DockerArchive + ":" + input - imgName, err := runtime.PullImage(src, options) - if err != nil { - // generate full src name with specified image:tag - fullSrc := libpod.OCIArchive + ":" + input - if image != "" { - fullSrc = fullSrc + ":" + image - } - imgName, err = runtime.PullImage(fullSrc, options) - if err != nil { - src = libpod.DirTransport + ":" + input - imgName, err = runtime.PullImage(src, options) - if err != nil { - return errors.Wrapf(err, "error pulling %q", src) - } - } - } - fmt.Println("Loaded image: ", imgName) - return nil -} diff --git a/cmd/kpod/login.go b/cmd/kpod/login.go deleted file mode 100644 index 8984d069c..000000000 --- a/cmd/kpod/login.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "bufio" - "context" - "fmt" - "os" - "strings" - - "github.com/containers/image/docker" - "github.com/containers/image/pkg/docker/config" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod/common" - "github.com/urfave/cli" - "golang.org/x/crypto/ssh/terminal" -) - -var ( - loginFlags = []cli.Flag{ - cli.StringFlag{ - Name: "password, p", - Usage: "Password for registry", - }, - cli.StringFlag{ - Name: "username, u", - Usage: "Username for registry", - }, - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", - }, - } - loginDescription = "Login to a container registry on a specified server." - loginCommand = cli.Command{ - Name: "login", - Usage: "login to a container registry", - Description: loginDescription, - Flags: loginFlags, - Action: loginCmd, - ArgsUsage: "REGISTRY", - } -) - -// loginCmd uses the authentication package to store a user's authenticated credentials -// in an auth.json file for future use -func loginCmd(c *cli.Context) error { - args := c.Args() - if len(args) > 1 { - return errors.Errorf("too many arguments, login takes only 1 argument") - } - if len(args) == 0 { - return errors.Errorf("registry must be given") - } - var server string - if len(args) == 1 { - server = args[0] - } - - sc := common.GetSystemContext("", c.String("authfile"), false) - - // username of user logged in to server (if one exists) - userFromAuthFile := config.GetUserLoggedIn(sc, server) - username, password, err := getUserAndPass(c.String("username"), c.String("password"), userFromAuthFile) - if err != nil { - return errors.Wrapf(err, "error getting username and password") - } - - if err = docker.CheckAuth(context.TODO(), sc, username, password, server); err == nil { - if err := config.SetAuthentication(sc, server, username, password); err != nil { - return err - } - } - switch err { - case nil: - fmt.Println("Login Succeeded!") - return nil - case docker.ErrUnauthorizedForCredentials: - return errors.Errorf("error logging into %q: invalid username/password\n", server) - default: - return errors.Wrapf(err, "error authenticating creds for %q", server) - } -} - -// getUserAndPass gets the username and password from STDIN if not given -// using the -u and -p flags -func getUserAndPass(username, password, userFromAuthFile string) (string, string, error) { - var err error - reader := bufio.NewReader(os.Stdin) - if username == "" { - if userFromAuthFile != "" { - fmt.Printf("Username (%s): ", userFromAuthFile) - } else { - fmt.Print("Username: ") - } - username, err = reader.ReadString('\n') - if err != nil { - return "", "", errors.Wrapf(err, "error reading username") - } - } - if password == "" { - fmt.Print("Password: ") - pass, err := terminal.ReadPassword(0) - if err != nil { - return "", "", errors.Wrapf(err, "error reading password") - } - password = string(pass) - fmt.Println() - } - return strings.TrimSpace(username), password, err -} diff --git a/cmd/kpod/logout.go b/cmd/kpod/logout.go deleted file mode 100644 index cae8ddfb2..000000000 --- a/cmd/kpod/logout.go +++ /dev/null @@ -1,69 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/containers/image/pkg/docker/config" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod/common" - "github.com/urfave/cli" -) - -var ( - logoutFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove the cached credentials for all registries in the auth file", - }, - } - logoutDescription = "Remove the cached username and password for the registry." - logoutCommand = cli.Command{ - Name: "logout", - Usage: "logout of a container registry", - Description: logoutDescription, - Flags: logoutFlags, - Action: logoutCmd, - ArgsUsage: "REGISTRY", - } -) - -// logoutCmd uses the authentication package to remove the authenticated of a registry -// stored in the auth.json file -func logoutCmd(c *cli.Context) error { - args := c.Args() - if len(args) > 1 { - return errors.Errorf("too many arguments, logout takes only 1 argument") - } - if len(args) == 0 { - return errors.Errorf("registry must be given") - } - var server string - if len(args) == 1 { - server = args[0] - } - - sc := common.GetSystemContext("", c.String("authfile"), false) - - if c.Bool("all") { - if err := config.RemoveAllAuthentication(sc); err != nil { - return err - } - fmt.Println("Remove login credentials for all registries") - return nil - } - - err := config.RemoveAuthentication(sc, server) - switch err { - case nil: - fmt.Printf("Remove login credentials for %s\n", server) - return nil - case config.ErrNotLoggedIn: - return errors.Errorf("Not logged into %s\n", server) - default: - return errors.Wrapf(err, "error logging out of %q", server) - } -} diff --git a/cmd/kpod/logs.go b/cmd/kpod/logs.go deleted file mode 100644 index 726ba4a65..000000000 --- a/cmd/kpod/logs.go +++ /dev/null @@ -1,153 +0,0 @@ -package main - -import ( - "fmt" - "strings" - "time" - - "github.com/hpcloud/tail" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -type logOptions struct { - details bool - follow bool - sinceTime time.Time - tail uint64 -} - -var ( - logsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "details", - Usage: "Show extra details provided to the logs", - Hidden: true, - }, - cli.BoolFlag{ - Name: "follow, f", - Usage: "Follow log output. The default is false", - }, - cli.StringFlag{ - Name: "since", - Usage: "Show logs since TIMESTAMP", - }, - cli.Uint64Flag{ - Name: "tail", - Usage: "Output the specified number of LINES at the end of the logs. Defaults to 0, which prints all lines", - }, - } - logsDescription = "The kpod logs command batch-retrieves whatever logs are present for a container at the time of execution. This does not guarantee execution" + - "order when combined with kpod run (i.e. your run may not have generated any logs at the time you execute kpod logs" - logsCommand = cli.Command{ - Name: "logs", - Usage: "Fetch the logs of a container", - Description: logsDescription, - Flags: logsFlags, - Action: logsCmd, - ArgsUsage: "CONTAINER", - } -) - -func logsCmd(c *cli.Context) error { - if err := validateFlags(c, logsFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - args := c.Args() - if len(args) != 1 { - return errors.Errorf("'kpod logs' requires exactly one container name/ID") - } - - sinceTime := time.Time{} - if c.IsSet("since") { - // parse time, error out if something is wrong - since, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", c.String("since")) - if err != nil { - return errors.Wrapf(err, "could not parse time: %q", c.String("since")) - } - sinceTime = since - } - - opts := logOptions{ - details: c.Bool("details"), - follow: c.Bool("follow"), - sinceTime: sinceTime, - tail: c.Uint64("tail"), - } - - ctr, err := runtime.LookupContainer(args[0]) - if err != nil { - return err - } - - logs := make(chan string) - go func() { - err = getLogs(ctr, logs, opts) - }() - printLogs(logs) - return err -} - -// getLogs returns the logs of a container from the log file -// log file is created when the container is started/ran -func getLogs(container *libpod.Container, logChan chan string, opts logOptions) error { - defer close(logChan) - - seekInfo := &tail.SeekInfo{Offset: 0, Whence: 0} - if opts.tail > 0 { - // seek to correct position in log files - seekInfo.Offset = int64(opts.tail) - seekInfo.Whence = 2 - } - - t, err := tail.TailFile(container.LogPath(), tail.Config{Follow: opts.follow, ReOpen: false, Location: seekInfo}) - for line := range t.Lines { - if since, err := logSinceTime(opts.sinceTime, line.Text); err != nil || !since { - continue - } - logMessage := line.Text[secondSpaceIndex(line.Text):] - logChan <- logMessage - } - return err -} - -func printLogs(logs chan string) { - for line := range logs { - fmt.Println(line) - } -} - -// returns true if the time stamps of the logs are equal to or after the -// timestamp comparing to -func logSinceTime(sinceTime time.Time, logStr string) (bool, error) { - timestamp := strings.Split(logStr, " ")[0] - logTime, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", timestamp) - if err != nil { - return false, err - } - return logTime.After(sinceTime) || logTime.Equal(sinceTime), nil -} - -// secondSpaceIndex returns the index of the second space in a string -// In a line of the logs, the first two tokens are a timestamp and stdout/stderr, -// followed by the message itself. This allows us to get the index of the message -// and avoid sending the other information back to the caller of GetLogs() -func secondSpaceIndex(line string) int { - index := strings.Index(line, " ") - if index == -1 { - return 0 - } - index = strings.Index(line[index:], " ") - if index == -1 { - return 0 - } - return index -} diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go deleted file mode 100644 index a2a81c8b6..000000000 --- a/cmd/kpod/main.go +++ /dev/null @@ -1,161 +0,0 @@ -package main - -import ( - "fmt" - "os" - "runtime/pprof" - - "github.com/containers/storage/pkg/reexec" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -// This is populated by the Makefile from the VERSION file -// in the repository -var kpodVersion = "" - -func main() { - debug := false - cpuProfile := false - - if reexec.Init() { - return - } - - app := cli.NewApp() - app.Name = "kpod" - app.Usage = "manage pods and images" - - var v string - if kpodVersion != "" { - v = kpodVersion - } - app.Version = v - - app.Commands = []cli.Command{ - attachCommand, - createCommand, - diffCommand, - execCommand, - exportCommand, - historyCommand, - imagesCommand, - importCommand, - infoCommand, - inspectCommand, - killCommand, - loadCommand, - loginCommand, - logoutCommand, - logsCommand, - mountCommand, - pauseCommand, - psCommand, - pullCommand, - pushCommand, - rmCommand, - rmiCommand, - runCommand, - saveCommand, - startCommand, - statsCommand, - stopCommand, - tagCommand, - topCommand, - umountCommand, - unpauseCommand, - versionCommand, - waitCommand, - } - app.Before = func(c *cli.Context) error { - logLevel := c.GlobalString("log-level") - if logLevel != "" { - level, err := logrus.ParseLevel(logLevel) - if err != nil { - return err - } - - logrus.SetLevel(level) - } - - if logLevel == "debug" { - debug = true - - } - if c.GlobalIsSet("cpu-profile") { - f, err := os.Create(c.GlobalString("cpu-profile")) - if err != nil { - return errors.Wrapf(err, "unable to create cpu profiling file %s", - c.GlobalString("cpu-profile")) - } - cpuProfile = true - pprof.StartCPUProfile(f) - } - return nil - } - app.After = func(*cli.Context) error { - // called by Run() when the command handler succeeds - shutdownStores() - if cpuProfile { - pprof.StopCPUProfile() - } - return nil - } - cli.OsExiter = func(code int) { - // called by Run() when the command fails, bypassing After() - shutdownStores() - os.Exit(code) - } - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "cni-config-dir", - Usage: "path of the configuration directory for CNI networks", - }, - cli.StringFlag{ - Name: "config, c", - Usage: "path of a config file detailing container server configuration options", - }, - cli.StringFlag{ - Name: "conmon", - Usage: "path of the conmon binary", - }, - cli.StringFlag{ - Name: "cpu-profile", - Usage: "path for the cpu profiling results", - }, - cli.StringFlag{ - Name: "log-level", - Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic", - Value: "error", - }, - cli.StringFlag{ - Name: "root", - Usage: "path to the root directory in which data, including images, is stored", - }, - cli.StringFlag{ - Name: "runroot", - Usage: "path to the 'run directory' where all state information is stored", - }, - cli.StringFlag{ - Name: "runtime", - Usage: "path to the OCI-compatible binary used to run containers, default is /usr/bin/runc", - }, - cli.StringFlag{ - Name: "storage-driver, s", - Usage: "select which storage driver is used to manage storage of images and containers (default is overlay)", - }, - cli.StringSliceFlag{ - Name: "storage-opt", - Usage: "used to pass an option to the storage driver", - }, - } - if err := app.Run(os.Args); err != nil { - if debug { - logrus.Errorf(err.Error()) - } else { - fmt.Fprintln(os.Stderr, err.Error()) - } - cli.OsExiter(1) - } -} diff --git a/cmd/kpod/mount.go b/cmd/kpod/mount.go deleted file mode 100644 index e90f54537..000000000 --- a/cmd/kpod/mount.go +++ /dev/null @@ -1,125 +0,0 @@ -package main - -import ( - js "encoding/json" - "fmt" - - "github.com/pkg/errors" - of "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/urfave/cli" -) - -var ( - mountDescription = ` - kpod mount - Lists all mounted containers mount points - - kpod mount CONTAINER-NAME-OR-ID - Mounts the specified container and outputs the mountpoint -` - - mountFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "notruncate", - Usage: "do not truncate output", - }, - cli.StringFlag{ - Name: "label", - Usage: "SELinux label for the mount point", - }, - cli.StringFlag{ - Name: "format", - Usage: "Change the output format to Go template", - }, - } - mountCommand = cli.Command{ - Name: "mount", - Usage: "Mount a working container's root filesystem", - Description: mountDescription, - Action: mountCmd, - ArgsUsage: "[CONTAINER-NAME-OR-ID]", - Flags: mountFlags, - } -) - -// jsonMountPoint stores info about each container -type jsonMountPoint struct { - ID string `json:"id"` - Names []string `json:"names"` - MountPoint string `json:"mountpoint"` -} - -func mountCmd(c *cli.Context) error { - if err := validateFlags(c, mountFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - formats := map[string]bool{ - "": true, - of.JSONString: true, - } - - args := c.Args() - json := c.String("format") == of.JSONString - if !formats[c.String("format")] { - return errors.Errorf("%q is not a supported format", c.String("format")) - } - - if len(args) > 1 { - return errors.Errorf("too many arguments specified") - } - - if len(args) == 1 { - if json { - return errors.Wrapf(err, "json option can not be used with a container id") - } - ctr, err := runtime.LookupContainer(args[0]) - if err != nil { - return errors.Wrapf(err, "error looking up container %q", args[0]) - } - mountPoint, err := ctr.Mount(c.String("label")) - if err != nil { - return errors.Wrapf(err, "error mounting container %q", ctr.ID()) - } - fmt.Printf("%s\n", mountPoint) - } else { - jsonMountPoints := []jsonMountPoint{} - containers, err2 := runtime.GetContainers() - if err2 != nil { - return errors.Wrapf(err2, "error reading list of all containers") - } - for _, container := range containers { - mountPoint, err := container.MountPoint() - if err != nil { - return errors.Wrapf(err, "error getting mountpoint for %q", container.ID()) - } - if mountPoint == "" { - continue - } - if json { - jsonMountPoints = append(jsonMountPoints, jsonMountPoint{ID: container.ID(), Names: []string{container.Name()}, MountPoint: mountPoint}) - continue - } - - if c.Bool("notruncate") { - fmt.Printf("%-64s %s\n", container.ID(), mountPoint) - } else { - fmt.Printf("%-12.12s %s\n", container.ID(), mountPoint) - } - } - if json { - data, err := js.MarshalIndent(jsonMountPoints, "", " ") - if err != nil { - return err - } - fmt.Printf("%s\n", data) - } - } - return nil -} diff --git a/cmd/kpod/parse.go b/cmd/kpod/parse.go deleted file mode 100644 index 53d49c36c..000000000 --- a/cmd/kpod/parse.go +++ /dev/null @@ -1,863 +0,0 @@ -//nolint -// most of these validate and parse functions have been taken from projectatomic/docker -// and modified for cri-o -package main - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net" - "os" - "os/user" - "path" - "regexp" - "strconv" - "strings" - - units "github.com/docker/go-units" - specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" -) - -// Note: for flags that are in the form <number><unit>, use the RAMInBytes function -// from the units package in docker/go-units/size.go - -var ( - whiteSpaces = " \t" - alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) - domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) -) - -// validateExtraHost validates that the specified string is a valid extrahost and returns it. -// ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6). -// for add-host flag -func validateExtraHost(val string) (string, error) { //nolint - // allow for IPv6 addresses in extra hosts by only splitting on first ":" - arr := strings.SplitN(val, ":", 2) - if len(arr) != 2 || len(arr[0]) == 0 { - return "", fmt.Errorf("bad format for add-host: %q", val) - } - if _, err := validateIPAddress(arr[1]); err != nil { - return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) - } - return val, nil -} - -// validateIPAddress validates an Ip address. -// for dns, ip, and ip6 flags also -func validateIPAddress(val string) (string, error) { - var ip = net.ParseIP(strings.TrimSpace(val)) - if ip != nil { - return ip.String(), nil - } - return "", fmt.Errorf("%s is not an ip address", val) -} - -// validateAttach validates that the specified string is a valid attach option. -// for attach flag -func validateAttach(val string) (string, error) { //nolint - s := strings.ToLower(val) - for _, str := range []string{"stdin", "stdout", "stderr"} { - if s == str { - return s, nil - } - } - return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR") -} - -// validate the blkioWeight falls in the range of 10 to 1000 -// for blkio-weight flag -func validateBlkioWeight(val int64) (int64, error) { //nolint - if val >= 10 && val <= 1000 { - return val, nil - } - return -1, errors.Errorf("invalid blkio weight %q, should be between 10 and 1000", val) -} - -// weightDevice is a structure that holds device:weight pair -type weightDevice struct { - path string - weight uint16 -} - -func (w *weightDevice) String() string { - return fmt.Sprintf("%s:%d", w.path, w.weight) -} - -// validateweightDevice validates that the specified string has a valid device-weight format -// for blkio-weight-device flag -func validateweightDevice(val string) (*weightDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - weight, err := strconv.ParseUint(split[1], 10, 0) - if err != nil { - return nil, fmt.Errorf("invalid weight for device: %s", val) - } - if weight > 0 && (weight < 10 || weight > 1000) { - return nil, fmt.Errorf("invalid weight for device: %s", val) - } - - return &weightDevice{ - path: split[0], - weight: uint16(weight), - }, nil -} - -// parseDevice parses a device mapping string to a container.DeviceMapping struct -// for device flag -func parseDevice(device string) (*pb.Device, error) { //nolint - _, err := validateDevice(device) - if err != nil { - return nil, errors.Wrapf(err, "device string not valid %q", device) - } - - src := "" - dst := "" - permissions := "rwm" - arr := strings.Split(device, ":") - switch len(arr) { - case 3: - permissions = arr[2] - fallthrough - case 2: - if validDeviceMode(arr[1]) { - permissions = arr[1] - } else { - dst = arr[1] - } - fallthrough - case 1: - src = arr[0] - default: - return nil, fmt.Errorf("invalid device specification: %s", device) - } - - if dst == "" { - dst = src - } - - deviceMapping := &pb.Device{ - ContainerPath: dst, - HostPath: src, - Permissions: permissions, - } - return deviceMapping, nil -} - -// validDeviceMode checks if the mode for device is valid or not. -// Valid mode is a composition of r (read), w (write), and m (mknod). -func validDeviceMode(mode string) bool { - var legalDeviceMode = map[rune]bool{ - 'r': true, - 'w': true, - 'm': true, - } - if mode == "" { - return false - } - for _, c := range mode { - if !legalDeviceMode[c] { - return false - } - legalDeviceMode[c] = false - } - return true -} - -// validateDevice validates a path for devices -// It will make sure 'val' is in the form: -// [host-dir:]container-path[:mode] -// It also validates the device mode. -func validateDevice(val string) (string, error) { - return validatePath(val, validDeviceMode) -} - -func validatePath(val string, validator func(string) bool) (string, error) { - var containerPath string - var mode string - - if strings.Count(val, ":") > 2 { - return val, fmt.Errorf("bad format for path: %s", val) - } - - split := strings.SplitN(val, ":", 3) - if split[0] == "" { - return val, fmt.Errorf("bad format for path: %s", val) - } - switch len(split) { - case 1: - containerPath = split[0] - val = path.Clean(containerPath) - case 2: - if isValid := validator(split[1]); isValid { - containerPath = split[0] - mode = split[1] - val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode) - } else { - containerPath = split[1] - val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath)) - } - case 3: - containerPath = split[1] - mode = split[2] - if isValid := validator(split[2]); !isValid { - return val, fmt.Errorf("bad mode specified: %s", mode) - } - val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode) - } - - if !path.IsAbs(containerPath) { - return val, fmt.Errorf("%s is not an absolute path", containerPath) - } - return val, nil -} - -// throttleDevice is a structure that holds device:rate_per_second pair -type throttleDevice struct { - path string - rate uint64 -} - -func (t *throttleDevice) String() string { - return fmt.Sprintf("%s:%d", t.path, t.rate) -} - -// validateBpsDevice validates that the specified string has a valid device-rate format -// for device-read-bps and device-write-bps flags -func validateBpsDevice(val string) (*throttleDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - rate, err := units.RAMInBytes(split[1]) - if err != nil { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) - } - if rate < 0 { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) - } - - return &throttleDevice{ - path: split[0], - rate: uint64(rate), - }, nil -} - -// validateIOpsDevice validates that the specified string has a valid device-rate format -// for device-write-iops and device-read-iops flags -func validateIOpsDevice(val string) (*throttleDevice, error) { //nolint - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - rate, err := strconv.ParseUint(split[1], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) - } - if rate < 0 { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) - } - - return &throttleDevice{ - path: split[0], - rate: uint64(rate), - }, nil -} - -// validateDNSSearch validates domain for resolvconf search configuration. -// A zero length domain is represented by a dot (.). -// for dns-search flag -func validateDNSSearch(val string) (string, error) { //nolint - if val = strings.Trim(val, " "); val == "." { - return val, nil - } - return validateDomain(val) -} - -func validateDomain(val string) (string, error) { - if alphaRegexp.FindString(val) == "" { - return "", fmt.Errorf("%s is not a valid domain", val) - } - ns := domainRegexp.FindSubmatch([]byte(val)) - if len(ns) > 0 && len(ns[1]) < 255 { - return string(ns[1]), nil - } - return "", fmt.Errorf("%s is not a valid domain", val) -} - -// validateEnv validates an environment variable and returns it. -// If no value is specified, it returns the current value using os.Getenv. -// for env flag -func validateEnv(val string) (string, error) { //nolint - arr := strings.Split(val, "=") - if len(arr) > 1 { - return val, nil - } - if !doesEnvExist(val) { - return val, nil - } - return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil -} - -func doesEnvExist(name string) bool { - for _, entry := range os.Environ() { - parts := strings.SplitN(entry, "=", 2) - if parts[0] == name { - return true - } - } - return false -} - -// reads a file of line terminated key=value pairs, and overrides any keys -// present in the file with additional pairs specified in the override parameter -// for env-file and labels-file flags -func readKVStrings(env map[string]string, files []string, override []string) error { - for _, ef := range files { - if err := parseEnvFile(env, ef); err != nil { - return err - } - } - for _, line := range override { - if err := parseEnv(env, line); err != nil { - return err - } - } - return nil -} - -func parseEnv(env map[string]string, line string) error { - data := strings.SplitN(line, "=", 2) - - // trim the front of a variable, but nothing else - name := strings.TrimLeft(data[0], whiteSpaces) - if strings.ContainsAny(name, whiteSpaces) { - return errors.Errorf("name %q has white spaces, poorly formatted name", name) - } - - if len(data) > 1 { - env[name] = data[1] - } else { - // if only a pass-through variable is given, clean it up. - val, exists := os.LookupEnv(name) - if !exists { - return errors.Errorf("environment variable %q does not exist", name) - } - env[name] = val - } - return nil -} - -// parseEnvFile reads a file with environment variables enumerated by lines -func parseEnvFile(env map[string]string, filename string) error { - fh, err := os.Open(filename) - if err != nil { - return err - } - defer fh.Close() - - scanner := bufio.NewScanner(fh) - for scanner.Scan() { - // trim the line from all leading whitespace first - line := strings.TrimLeft(scanner.Text(), whiteSpaces) - // line is not empty, and not starting with '#' - if len(line) > 0 && !strings.HasPrefix(line, "#") { - if err := parseEnv(env, line); err != nil { - return err - } - } - } - return scanner.Err() -} - -// NsIpc represents the container ipc stack. -// for ipc flag -type NsIpc string - -// IsPrivate indicates whether the container uses its private ipc stack. -func (n NsIpc) IsPrivate() bool { - return !(n.IsHost() || n.IsContainer()) -} - -// IsHost indicates whether the container uses the host's ipc stack. -func (n NsIpc) IsHost() bool { - return n == "host" -} - -// IsContainer indicates whether the container uses a container's ipc stack. -func (n NsIpc) IsContainer() bool { - parts := strings.SplitN(string(n), ":", 2) - return len(parts) > 1 && parts[0] == "container" -} - -// Valid indicates whether the ipc stack is valid. -func (n NsIpc) Valid() bool { - parts := strings.Split(string(n), ":") - switch mode := parts[0]; mode { - case "", "host": - case "container": - if len(parts) != 2 || parts[1] == "" { - return false - } - default: - return false - } - return true -} - -// Container returns the name of the container ipc stack is going to be used. -func (n NsIpc) Container() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 { - return parts[1] - } - return "" -} - -// validateLabel validates that the specified string is a valid label, and returns it. -// Labels are in the form on key=value. -// for label flag -func validateLabel(val string) (string, error) { //nolint - if strings.Count(val, "=") < 1 { - return "", fmt.Errorf("bad attribute format: %s", val) - } - return val, nil -} - -// validateMACAddress validates a MAC address. -// for mac-address flag -func validateMACAddress(val string) (string, error) { //nolint - _, err := net.ParseMAC(strings.TrimSpace(val)) - if err != nil { - return "", err - } - return val, nil -} - -// parseLoggingOpts validates the logDriver and logDriverOpts -// for log-opt and log-driver flags -func parseLoggingOpts(logDriver string, logDriverOpt []string) (map[string]string, error) { //nolint - logOptsMap := convertKVStringsToMap(logDriverOpt) - if logDriver == "none" && len(logDriverOpt) > 0 { - return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", logDriver) - } - return logOptsMap, nil -} - -// NsPid represents the pid namespace of the container. -//for pid flag -type NsPid string - -// IsPrivate indicates whether the container uses its own new pid namespace. -func (n NsPid) IsPrivate() bool { - return !(n.IsHost() || n.IsContainer()) -} - -// IsHost indicates whether the container uses the host's pid namespace. -func (n NsPid) IsHost() bool { - return n == "host" -} - -// IsContainer indicates whether the container uses a container's pid namespace. -func (n NsPid) IsContainer() bool { - parts := strings.SplitN(string(n), ":", 2) - return len(parts) > 1 && parts[0] == "container" -} - -// Valid indicates whether the pid namespace is valid. -func (n NsPid) Valid() bool { - parts := strings.Split(string(n), ":") - switch mode := parts[0]; mode { - case "", "host": - case "container": - if len(parts) != 2 || parts[1] == "" { - return false - } - default: - return false - } - return true -} - -// Container returns the name of the container whose pid namespace is going to be used. -func (n NsPid) Container() string { - parts := strings.SplitN(string(n), ":", 2) - if len(parts) > 1 { - return parts[1] - } - return "" -} - -// parsePortSpecs receives port specs in the format of ip:public:private/proto and parses -// these in to the internal types -// for publish, publish-all, and expose flags -func parsePortSpecs(ports []string) ([]*pb.PortMapping, error) { //nolint - var portMappings []*pb.PortMapping - for _, rawPort := range ports { - portMapping, err := parsePortSpec(rawPort) - if err != nil { - return nil, err - } - - portMappings = append(portMappings, portMapping...) - } - return portMappings, nil -} - -func validateProto(proto string) bool { - for _, availableProto := range []string{"tcp", "udp"} { - if availableProto == proto { - return true - } - } - return false -} - -// parsePortSpec parses a port specification string into a slice of PortMappings -func parsePortSpec(rawPort string) ([]*pb.PortMapping, error) { - var proto string - rawIP, hostPort, containerPort := splitParts(rawPort) - proto, containerPort = splitProtoPort(containerPort) - - // Strip [] from IPV6 addresses - ip, _, err := net.SplitHostPort(rawIP + ":") - if err != nil { - return nil, fmt.Errorf("Invalid ip address %v: %s", rawIP, err) - } - if ip != "" && net.ParseIP(ip) == nil { - return nil, fmt.Errorf("Invalid ip address: %s", ip) - } - if containerPort == "" { - return nil, fmt.Errorf("No port specified: %s<empty>", rawPort) - } - - startPort, endPort, err := parsePortRange(containerPort) - if err != nil { - return nil, fmt.Errorf("Invalid containerPort: %s", containerPort) - } - - var startHostPort, endHostPort uint64 = 0, 0 - if len(hostPort) > 0 { - startHostPort, endHostPort, err = parsePortRange(hostPort) - if err != nil { - return nil, fmt.Errorf("Invalid hostPort: %s", hostPort) - } - } - - if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { - // Allow host port range iff containerPort is not a range. - // In this case, use the host port range as the dynamic - // host port range to allocate into. - if endPort != startPort { - return nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) - } - } - - if !validateProto(strings.ToLower(proto)) { - return nil, fmt.Errorf("invalid proto: %s", proto) - } - - protocol := pb.Protocol_TCP - if strings.ToLower(proto) == "udp" { - protocol = pb.Protocol_UDP - } - - var ports []*pb.PortMapping - for i := uint64(0); i <= (endPort - startPort); i++ { - containerPort = strconv.FormatUint(startPort+i, 10) - if len(hostPort) > 0 { - hostPort = strconv.FormatUint(startHostPort+i, 10) - } - // Set hostPort to a range only if there is a single container port - // and a dynamic host port. - if startPort == endPort && startHostPort != endHostPort { - hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) - } - - ctrPort, err := strconv.ParseInt(containerPort, 10, 32) - if err != nil { - return nil, err - } - hPort, err := strconv.ParseInt(hostPort, 10, 32) - if err != nil { - return nil, err - } - - port := &pb.PortMapping{ - Protocol: protocol, - ContainerPort: int32(ctrPort), - HostPort: int32(hPort), - HostIp: ip, - } - - ports = append(ports, port) - } - return ports, nil -} - -// parsePortRange parses and validates the specified string as a port-range (8000-9000) -func parsePortRange(ports string) (uint64, uint64, error) { - if ports == "" { - return 0, 0, fmt.Errorf("empty string specified for ports") - } - if !strings.Contains(ports, "-") { - start, err := strconv.ParseUint(ports, 10, 16) - end := start - return start, end, err - } - - parts := strings.Split(ports, "-") - start, err := strconv.ParseUint(parts[0], 10, 16) - if err != nil { - return 0, 0, err - } - end, err := strconv.ParseUint(parts[1], 10, 16) - if err != nil { - return 0, 0, err - } - if end < start { - return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports) - } - return start, end, nil -} - -// splitParts separates the different parts of rawPort -func splitParts(rawport string) (string, string, string) { - parts := strings.Split(rawport, ":") - n := len(parts) - containerport := parts[n-1] - - switch n { - case 1: - return "", "", containerport - case 2: - return "", parts[0], containerport - case 3: - return parts[0], parts[1], containerport - default: - return strings.Join(parts[:n-2], ":"), parts[n-2], containerport - } -} - -// splitProtoPort splits a port in the format of port/proto -func splitProtoPort(rawPort string) (string, string) { - parts := strings.Split(rawPort, "/") - l := len(parts) - if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 { - return "", "" - } - if l == 1 { - return "tcp", rawPort - } - if len(parts[1]) == 0 { - return "tcp", parts[0] - } - return parts[1], parts[0] -} - -// takes a local seccomp file and reads its file contents -// for security-opt flag -func parseSecurityOpts(securityOpts []string) ([]string, error) { //nolint - for key, opt := range securityOpts { - con := strings.SplitN(opt, "=", 2) - if len(con) == 1 && con[0] != "no-new-privileges" { - if strings.Index(opt, ":") != -1 { - con = strings.SplitN(opt, ":", 2) - } else { - return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt) - } - } - if con[0] == "seccomp" && con[1] != "unconfined" { - f, err := ioutil.ReadFile(con[1]) - if err != nil { - return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err) - } - b := bytes.NewBuffer(nil) - if err := json.Compact(b, f); err != nil { - return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err) - } - securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes()) - } - } - - return securityOpts, nil -} - -// parses storage options per container into a map -// for storage-opt flag -func parseStorageOpts(storageOpts []string) (map[string]string, error) { //nolint - m := make(map[string]string) - for _, option := range storageOpts { - if strings.Contains(option, "=") { - opt := strings.SplitN(option, "=", 2) - m[opt[0]] = opt[1] - } else { - return nil, errors.Errorf("invalid storage option %q", option) - } - } - return m, nil -} - -// parseUser parses the the uid and gid in the format <name|uid>[:<group|gid>] -// for user flag -// FIXME: Issue from https://github.com/projectatomic/buildah/issues/66 -func parseUser(rootdir, userspec string) (specs.User, error) { //nolint - var gid64 uint64 - var gerr error = user.UnknownGroupError("error looking up group") - - spec := strings.SplitN(userspec, ":", 2) - userspec = spec[0] - groupspec := "" - if userspec == "" { - return specs.User{}, nil - } - if len(spec) > 1 { - groupspec = spec[1] - } - - uid64, uerr := strconv.ParseUint(userspec, 10, 32) - if uerr == nil && groupspec == "" { - // We parsed the user name as a number, and there's no group - // component, so we need to look up the user's primary GID. - var name string - name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64) - if gerr == nil { - userspec = name - } else { - if userrec, err := user.LookupId(userspec); err == nil { - gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32) - userspec = userrec.Name - } - } - } - if uerr != nil { - uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec) - gerr = uerr - } - if uerr != nil { - if userrec, err := user.Lookup(userspec); err == nil { - uid64, uerr = strconv.ParseUint(userrec.Uid, 10, 32) - gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32) - } - } - - if groupspec != "" { - gid64, gerr = strconv.ParseUint(groupspec, 10, 32) - if gerr != nil { - gid64, gerr = lookupGroupInContainer(rootdir, groupspec) - } - if gerr != nil { - if group, err := user.LookupGroup(groupspec); err == nil { - gid64, gerr = strconv.ParseUint(group.Gid, 10, 32) - } - } - } - - if uerr == nil && gerr == nil { - u := specs.User{ - UID: uint32(uid64), - GID: uint32(gid64), - Username: userspec, - } - return u, nil - } - - err := errors.Wrapf(uerr, "error determining run uid") - if uerr == nil { - err = errors.Wrapf(gerr, "error determining run gid") - } - return specs.User{}, err -} - -// convertKVStringsToMap converts ["key=value"] to {"key":"value"} -func convertKVStringsToMap(values []string) map[string]string { - result := make(map[string]string, len(values)) - for _, value := range values { - kv := strings.SplitN(value, "=", 2) - if len(kv) == 1 { - result[kv[0]] = "" - } else { - result[kv[0]] = kv[1] - } - } - - return result -} - -// NsUser represents userns mode in the container. -// for userns flag -type NsUser string - -// IsHost indicates whether the container uses the host's userns. -func (n NsUser) IsHost() bool { - return n == "host" -} - -// IsPrivate indicates whether the container uses the a private userns. -func (n NsUser) IsPrivate() bool { - return !(n.IsHost()) -} - -// Valid indicates whether the userns is valid. -func (n NsUser) Valid() bool { - parts := strings.Split(string(n), ":") - switch mode := parts[0]; mode { - case "", "host": - default: - return false - } - return true -} - -// NsUts represents the UTS namespace of the container. -// for uts flag -type NsUts string - -// IsPrivate indicates whether the container uses its private UTS namespace. -func (n NsUts) IsPrivate() bool { - return !(n.IsHost()) -} - -// IsHost indicates whether the container uses the host's UTS namespace. -func (n NsUts) IsHost() bool { - return n == "host" -} - -// Valid indicates whether the UTS namespace is valid. -func (n NsUts) Valid() bool { - parts := strings.Split(string(n), ":") - switch mode := parts[0]; mode { - case "", "host": - default: - return false - } - return true -} - -// Takes a stringslice and converts to a uint32slice -func stringSlicetoUint32Slice(inputSlice []string) ([]uint32, error) { - var outputSlice []uint32 - for _, v := range inputSlice { - u, err := strconv.ParseUint(v, 10, 32) - if err != nil { - return outputSlice, err - } - outputSlice = append(outputSlice, uint32(u)) - } - return outputSlice, nil -} diff --git a/cmd/kpod/pause.go b/cmd/kpod/pause.go deleted file mode 100644 index dede89443..000000000 --- a/cmd/kpod/pause.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/pkg/errors" - "github.com/urfave/cli" -) - -var ( - pauseDescription = ` - kpod pause - - Pauses one or more running containers. The container name or ID can be used. -` - pauseCommand = cli.Command{ - Name: "pause", - Usage: "Pauses all the processes in one or more containers", - Description: pauseDescription, - Action: pauseCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - } -) - -func pauseCmd(c *cli.Context) error { - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - args := c.Args() - if len(args) < 1 { - return errors.Errorf("you must provide at least one container name or id") - } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue - } - if err = ctr.Pause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to pause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) - } - } - return lastError -} diff --git a/cmd/kpod/ps.go b/cmd/kpod/ps.go deleted file mode 100644 index 6b807671e..000000000 --- a/cmd/kpod/ps.go +++ /dev/null @@ -1,606 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "reflect" - "regexp" - "strconv" - "strings" - "time" - - "github.com/docker/go-units" - specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" - "k8s.io/apimachinery/pkg/fields" -) - -type psOptions struct { - all bool - filter string - format string - last int - latest bool - noTrunc bool - quiet bool - size bool - label string - namespace bool -} - -type psTemplateParams struct { - ID string - Image string - Command string - CreatedAt string - RunningFor string - Status string - Ports string - Size string - Names string - Labels string - Mounts string - PID int - Cgroup string - IPC string - MNT string - NET string - PIDNS string - User string - UTS string -} - -// psJSONParams is only used when the JSON format is specified, -// and is better for data processing from JSON. -// psJSONParams will be populated by data from libpod.Container, -// the members of the struct are the sama data types as their sources. -type psJSONParams struct { - ID string `json:"id"` - Image string `json:"image"` - ImageID string `json:"image_id"` - Command string `json:"command"` - CreatedAt time.Time `json:"createdAt"` - RunningFor time.Duration `json:"runningFor"` - Status string `json:"status"` - Ports map[string]struct{} `json:"ports"` - Size uint `json:"size"` - Names string `json:"names"` - Labels fields.Set `json:"labels"` - Mounts []specs.Mount `json:"mounts"` - ContainerRunning bool `json:"ctrRunning"` - Namespaces *namespace `json:"namespace,omitempty"` -} - -type namespace struct { - PID string `json:"pid,omitempty"` - Cgroup string `json:"cgroup,omitempty"` - IPC string `json:"ipc,omitempty"` - MNT string `json:"mnt,omitempty"` - NET string `json:"net,omitempty"` - PIDNS string `json:"pidns,omitempty"` - User string `json:"user,omitempty"` - UTS string `json:"uts,omitempty"` -} - -var ( - psFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "Show all the containers, default is only running containers", - }, - cli.StringFlag{ - Name: "filter, f", - Usage: "Filter output based on conditions given", - }, - cli.StringFlag{ - Name: "format", - Usage: "Pretty-print containers to JSON or using a Go template", - }, - cli.IntFlag{ - Name: "last, n", - Usage: "Print the n last created containers (all states)", - Value: -1, - }, - cli.BoolFlag{ - Name: "latest, l", - Usage: "Show the latest container created (all states)", - }, - cli.BoolFlag{ - Name: "no-trunc", - Usage: "Display the extended information", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Print the numeric IDs of the containers only", - }, - cli.BoolFlag{ - Name: "size, s", - Usage: "Display the total file sizes", - }, - cli.BoolFlag{ - Name: "namespace, ns", - Usage: "Display namespace information", - }, - } - psDescription = "Prints out information about the containers" - psCommand = cli.Command{ - Name: "ps", - Usage: "List containers", - Description: psDescription, - Flags: psFlags, - Action: psCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - } -) - -func psCmd(c *cli.Context) error { - if err := validateFlags(c, psFlags); err != nil { - return err - } - - if err := checkFlagsPassed(c); err != nil { - return errors.Wrapf(err, "error with flags passed") - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - - defer runtime.Shutdown(false) - - if len(c.Args()) > 0 { - return errors.Errorf("too many arguments, ps takes no arguments") - } - - format := genPsFormat(c.String("format"), c.Bool("quiet"), c.Bool("size"), c.Bool("namespace")) - - opts := psOptions{ - all: c.Bool("all"), - filter: c.String("filter"), - format: format, - last: c.Int("last"), - latest: c.Bool("latest"), - noTrunc: c.Bool("no-trunc"), - quiet: c.Bool("quiet"), - size: c.Bool("size"), - namespace: c.Bool("namespace"), - } - - var filterFuncs []libpod.ContainerFilter - // When we are dealing with latest or last=n, we need to - // get all containers. - if !opts.all && !opts.latest && opts.last < 1 { - // only get running containers - filterFuncs = append(filterFuncs, func(c *libpod.Container) bool { - state, _ := c.State() - return state == libpod.ContainerStateRunning - }) - } - - if opts.filter != "" { - filters := strings.Split(opts.filter, ",") - for _, f := range filters { - filterSplit := strings.Split(f, "=") - if len(filterSplit) < 2 { - return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) - } - generatedFunc, err := generateContainerFilterFuncs(filterSplit[0], filterSplit[1], runtime) - if err != nil { - return errors.Wrapf(err, "invalid filter") - } - filterFuncs = append(filterFuncs, generatedFunc) - } - } - - containers, err := runtime.GetContainers(filterFuncs...) - var outputContainers []*libpod.Container - if opts.latest && len(containers) > 0 { - outputContainers = append(outputContainers, containers[0]) - } else if opts.last > 0 && opts.last <= len(containers) { - outputContainers = append(outputContainers, containers[:opts.last]...) - } else { - outputContainers = containers - } - - return generatePsOutput(outputContainers, opts) -} - -// checkFlagsPassed checks if mutually exclusive flags are passed together -func checkFlagsPassed(c *cli.Context) error { - // latest, and last are mutually exclusive. - if c.Int("last") >= 0 && c.Bool("latest") { - return errors.Errorf("last and latest are mutually exclusive") - } - // quiet, size, namespace, and format with Go template are mutually exclusive - flags := 0 - if c.Bool("quiet") { - flags++ - } - if c.Bool("size") { - flags++ - } - if c.Bool("namespace") { - flags++ - } - if c.IsSet("format") && c.String("format") != formats.JSONString { - flags++ - } - if flags > 1 { - return errors.Errorf("quiet, size, namespace, and format with Go template are mutually exclusive") - } - return nil -} - -func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(container *libpod.Container) bool, error) { - switch filter { - case "id": - return func(c *libpod.Container) bool { - return c.ID() == filterValue - }, nil - case "label": - return func(c *libpod.Container) bool { - for _, label := range c.Labels() { - if label == filterValue { - return true - } - } - return false - }, nil - case "name": - return func(c *libpod.Container) bool { - return c.Name() == filterValue - }, nil - case "exited": - exitCode, err := strconv.ParseInt(filterValue, 10, 32) - if err != nil { - return nil, errors.Wrapf(err, "exited code out of range %q", filterValue) - } - return func(c *libpod.Container) bool { - ec, err := c.ExitCode() - if ec == int32(exitCode) && err == nil { - return true - } - return false - }, nil - case "status": - if !libpod.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) { - return nil, errors.Errorf("%s is not a valid status", filterValue) - } - return func(c *libpod.Container) bool { - status, err := c.State() - if err != nil { - return false - } - return status.String() == filterValue - }, nil - case "ancestor": - // This needs to refine to match docker - // - ancestor=(<image-name>[:tag]|<image-id>| ⟨image@digest⟩) - containers created from an image or a descendant. - return func(c *libpod.Container) bool { - containerConfig := c.Config() - if containerConfig.RootfsImageID == filterValue || containerConfig.RootfsImageName == filterValue { - return true - } - return false - }, nil - case "before": - ctr, err := runtime.LookupContainer(filterValue) - if err != nil { - return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) - } - containerConfig := ctr.Config() - createTime := containerConfig.CreatedTime - return func(c *libpod.Container) bool { - cc := c.Config() - return createTime.After(cc.CreatedTime) - }, nil - case "since": - ctr, err := runtime.LookupContainer(filterValue) - if err != nil { - return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) - } - containerConfig := ctr.Config() - createTime := containerConfig.CreatedTime - return func(c *libpod.Container) bool { - cc := c.Config() - return createTime.Before(cc.CreatedTime) - }, nil - case "volume": - //- volume=(<volume-name>|<mount-point-destination>) - return func(c *libpod.Container) bool { - containerConfig := c.Config() - //TODO We need to still lookup against volumes too - return containerConfig.MountLabel == filterValue - }, nil - } - return nil, errors.Errorf("%s is an invalid filter", filter) -} - -// generate the template based on conditions given -func genPsFormat(format string, quiet, size, namespace bool) string { - if format != "" { - // "\t" from the command line is not being recognized as a tab - // replacing the string "\t" to a tab character if the user passes in "\t" - return strings.Replace(format, `\t`, "\t", -1) - } - if quiet { - return formats.IDString - } - if namespace { - return "table {{.ID}}\t{{.Names}}\t{{.PID}}\t{{.Cgroup}}\t{{.IPC}}\t{{.MNT}}\t{{.NET}}\t{{.PIDNS}}\t{{.User}}\t{{.UTS}}\t" - } - format = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}\t" - if size { - format += "{{.Size}}\t" - } - return format -} - -func psToGeneric(templParams []psTemplateParams, JSONParams []psJSONParams) (genericParams []interface{}) { - if len(templParams) > 0 { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return - } - for _, v := range JSONParams { - genericParams = append(genericParams, interface{}(v)) - } - return -} - -// generate the accurate header based on template given -func (p *psTemplateParams) headerMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(p)) - values := make(map[string]string) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - if value == "ID" { - value = "Container" + value - } - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -// getTemplateOutput returns the modified container information -func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemplateParams, error) { - var psOutput []psTemplateParams - var status string - for _, ctr := range containers { - ctrID := ctr.ID() - conConfig := ctr.Config() - conState, err := ctr.State() - if err != nil { - return psOutput, errors.Wrapf(err, "unable to obtain container state") - } - exitCode, err := ctr.ExitCode() - if err != nil { - return psOutput, errors.Wrapf(err, "unable to obtain container exit code") - } - pid, err := ctr.PID() - if err != nil { - return psOutput, errors.Wrapf(err, "unable to obtain container pid") - } - runningFor := units.HumanDuration(time.Since(conConfig.CreatedTime)) - createdAt := runningFor + " ago" - imageName := conConfig.RootfsImageName - - // TODO We currently dont have the ability to get many of - // these data items. Uncomment as progress is made - - //command := getStrFromSquareBrackets(ctr.ImageCreatedBy) - command := strings.Join(ctr.Spec().Process.Args, " ") - //mounts := getMounts(ctr.Mounts, opts.noTrunc) - //ports := getPorts(ctr.Config.ExposedPorts) - //size := units.HumanSize(float64(ctr.SizeRootFs)) - labels := formatLabels(ctr.Labels()) - ns := getNamespaces(pid) - - switch conState { - case libpod.ContainerStateStopped: - status = fmt.Sprintf("Exited (%d) %s ago", exitCode, runningFor) - case libpod.ContainerStateRunning: - status = "Up " + runningFor + " ago" - case libpod.ContainerStatePaused: - status = "Paused" - case libpod.ContainerStateCreated: - status = "Created" - default: - status = "Dead" - } - - if !opts.noTrunc { - ctrID = ctr.ID()[:idTruncLength] - imageName = conConfig.RootfsImageName - } - - // TODO We currently dont have the ability to get many of - // these data items. Uncomment as progress is made - - params := psTemplateParams{ - ID: ctrID, - Image: imageName, - Command: command, - CreatedAt: createdAt, - RunningFor: runningFor, - Status: status, - //Ports: ports, - //Size: size, - Names: ctr.Name(), - Labels: labels, - //Mounts: mounts, - PID: pid, - Cgroup: ns.Cgroup, - IPC: ns.IPC, - MNT: ns.MNT, - NET: ns.NET, - PIDNS: ns.PID, - User: ns.User, - UTS: ns.UTS, - } - psOutput = append(psOutput, params) - } - return psOutput, nil -} - -func getNamespaces(pid int) *namespace { - ctrPID := strconv.Itoa(pid) - cgroup, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) - ipc, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc")) - mnt, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt")) - net, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net")) - pidns, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid")) - user, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user")) - uts, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts")) - - return &namespace{ - PID: ctrPID, - Cgroup: cgroup, - IPC: ipc, - MNT: mnt, - NET: net, - PIDNS: pidns, - User: user, - UTS: uts, - } -} - -func getNamespaceInfo(path string) (string, error) { - val, err := os.Readlink(path) - if err != nil { - return "", errors.Wrapf(err, "error getting info from %q", path) - } - return getStrFromSquareBrackets(val), nil -} - -// getJSONOutput returns the container info in its raw form -func getJSONOutput(containers []*libpod.Container, nSpace bool) ([]psJSONParams, error) { - var psOutput []psJSONParams - var ns *namespace - for _, ctr := range containers { - pid, err := ctr.PID() - if err != nil { - return psOutput, errors.Wrapf(err, "unable to obtain container pid") - } - if nSpace { - ns = getNamespaces(pid) - } - cc := ctr.Config() - conState, err := ctr.State() - if err != nil { - return psOutput, errors.Wrapf(err, "unable to obtain container state for JSON output") - } - params := psJSONParams{ - // TODO When we have ability to obtain the commented out data, we need - // TODO to add it - ID: ctr.ID(), - Image: cc.RootfsImageName, - ImageID: cc.RootfsImageID, - //Command: getStrFromSquareBrackets(ctr.ImageCreatedBy), - Command: strings.Join(ctr.Spec().Process.Args, " "), - CreatedAt: cc.CreatedTime, - RunningFor: time.Since(cc.CreatedTime), - Status: conState.String(), - //Ports: cc.Spec.Linux.Resources.Network. - //Size: ctr.SizeRootFs, - Names: cc.Name, - Labels: cc.Labels, - Mounts: cc.Spec.Mounts, - ContainerRunning: conState == libpod.ContainerStateRunning, - Namespaces: ns, - } - psOutput = append(psOutput, params) - } - return psOutput, nil -} - -func generatePsOutput(containers []*libpod.Container, opts psOptions) error { - if len(containers) == 0 && opts.format != formats.JSONString { - return nil - } - var out formats.Writer - - switch opts.format { - case formats.JSONString: - psOutput, err := getJSONOutput(containers, opts.namespace) - if err != nil { - return errors.Wrapf(err, "unable to create JSON for output") - } - out = formats.JSONStructArray{Output: psToGeneric([]psTemplateParams{}, psOutput)} - default: - psOutput, err := getTemplateOutput(containers, opts) - if err != nil { - return errors.Wrapf(err, "unable to create output") - } - out = formats.StdoutTemplateArray{Output: psToGeneric(psOutput, []psJSONParams{}), Template: opts.format, Fields: psOutput[0].headerMap()} - } - - return formats.Writer(out).Out() -} - -// getStrFromSquareBrackets gets the string inside [] from a string -func getStrFromSquareBrackets(cmd string) string { - reg, err := regexp.Compile(".*\\[|\\].*") - if err != nil { - return "" - } - arr := strings.Split(reg.ReplaceAllLiteralString(cmd, ""), ",") - return strings.Join(arr, ",") -} - -// getLabels converts the labels to a string of the form "key=value, key2=value2" -func formatLabels(labels map[string]string) string { - var arr []string - if len(labels) > 0 { - for key, val := range labels { - temp := key + "=" + val - arr = append(arr, temp) - } - return strings.Join(arr, ",") - } - return "" -} - -/* -// getMounts converts the volumes mounted to a string of the form "mount1, mount2" -// it truncates it if noTrunc is false -func getMounts(mounts []specs.Mount, noTrunc bool) string { - var arr []string - if len(mounts) == 0 { - return "" - } - for _, mount := range mounts { - if noTrunc { - arr = append(arr, mount.Source) - continue - } - tempArr := strings.SplitAfter(mount.Source, "/") - if len(tempArr) >= 3 { - arr = append(arr, strings.Join(tempArr[:3], "")) - } else { - arr = append(arr, mount.Source) - } - } - return strings.Join(arr, ",") -} -// getPorts converts the ports used to a string of the from "port1, port2" -func getPorts(ports map[string]struct{}) string { - var arr []string - if len(ports) == 0 { - return "" - } - for key := range ports { - arr = append(arr, key) - } - return strings.Join(arr, ",") -} -*/ diff --git a/cmd/kpod/pull.go b/cmd/kpod/pull.go deleted file mode 100644 index 5726b20f1..000000000 --- a/cmd/kpod/pull.go +++ /dev/null @@ -1,120 +0,0 @@ -package main - -import ( - "fmt" - "io" - "os" - - "golang.org/x/crypto/ssh/terminal" - - "github.com/containers/image/types" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/projectatomic/libpod/libpod/common" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -var ( - pullFlags = []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`pathname` of a directory containing TLS certificates and keys", - }, - cli.StringFlag{ - Name: "creds", - Usage: "`credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress output information when pulling images", - }, - cli.StringFlag{ - Name: "signature-policy", - Usage: "`pathname` of signature policy file (not usually used)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "require HTTPS and verify certificates when contacting registries (default: true)", - }, - } - - pullDescription = "Pulls an image from a registry and stores it locally.\n" + - "An image can be pulled using its tag or digest. If a tag is not\n" + - "specified, the image with the 'latest' tag (if it exists) is pulled." - pullCommand = cli.Command{ - Name: "pull", - Usage: "pull an image from a registry", - Description: pullDescription, - Flags: pullFlags, - Action: pullCmd, - ArgsUsage: "", - } -) - -// pullCmd gets the data from the command line and calls pullImage -// to copy an image from a registry to a local machine -func pullCmd(c *cli.Context) error { - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - args := c.Args() - if len(args) == 0 { - logrus.Errorf("an image name must be specified") - return nil - } - if len(args) > 1 { - logrus.Errorf("too many arguments. Requires exactly 1") - return nil - } - if err := validateFlags(c, pullFlags); err != nil { - return err - } - image := args[0] - - var registryCreds *types.DockerAuthConfig - if c.String("creds") != "" { - creds, err := common.ParseRegistryCreds(c.String("creds")) - if err != nil { - if err == common.ErrNoPassword { - fmt.Print("Password: ") - password, err := terminal.ReadPassword(0) - if err != nil { - return errors.Wrapf(err, "could not read password from terminal") - } - creds.Password = string(password) - } else { - return err - } - } - registryCreds = creds - } - - var writer io.Writer - if !c.Bool("quiet") { - writer = os.Stdout - } - - options := libpod.CopyOptions{ - SignaturePolicyPath: c.String("signature-policy"), - AuthFile: c.String("authfile"), - DockerRegistryOptions: common.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: c.String("cert-dir"), - DockerInsecureSkipTLSVerify: !c.BoolT("tls-verify"), - }, - Writer: writer, - } - - if _, err := runtime.PullImage(image, options); err != nil { - return errors.Wrapf(err, "error pulling image %q", image) - } - return nil -} diff --git a/cmd/kpod/push.go b/cmd/kpod/push.go deleted file mode 100644 index d3d42e0ee..000000000 --- a/cmd/kpod/push.go +++ /dev/null @@ -1,167 +0,0 @@ -package main - -import ( - "fmt" - "io" - "os" - "strings" - - "github.com/containers/image/manifest" - "github.com/containers/image/types" - "github.com/containers/storage/pkg/archive" - imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/projectatomic/libpod/libpod/common" - "github.com/urfave/cli" - "golang.org/x/crypto/ssh/terminal" -) - -var ( - pushFlags = []cli.Flag{ - cli.StringFlag{ - Name: "signature-policy", - Usage: "`pathname` of signature policy file (not usually used)", - Hidden: true, - }, - cli.StringFlag{ - Name: "creds", - Usage: "`credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", - }, - cli.StringFlag{ - Name: "cert-dir", - Usage: "`pathname` of a directory containing TLS certificates and keys", - }, - cli.BoolFlag{ - Name: "compress", - Usage: "compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)", - }, - cli.StringFlag{ - Name: "format, f", - Usage: "manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir:' transport (default is manifest type of source)", - }, - cli.BoolTFlag{ - Name: "tls-verify", - Usage: "require HTTPS and verify certificates when contacting registries (default: true)", - }, - cli.BoolFlag{ - Name: "remove-signatures", - Usage: "discard any pre-existing signatures in the image", - }, - cli.StringFlag{ - Name: "sign-by", - Usage: "add a signature at the destination using the specified key", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "don't output progress information when pushing images", - }, - cli.StringFlag{ - Name: "authfile", - Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", - }, - } - pushDescription = fmt.Sprintf(` - Pushes an image to a specified location. - The Image "DESTINATION" uses a "transport":"details" format. - See kpod-push(1) section "DESTINATION" for the expected format`) - - pushCommand = cli.Command{ - Name: "push", - Usage: "push an image to a specified destination", - Description: pushDescription, - Flags: pushFlags, - Action: pushCmd, - ArgsUsage: "IMAGE DESTINATION", - } -) - -func pushCmd(c *cli.Context) error { - var registryCreds *types.DockerAuthConfig - - args := c.Args() - if len(args) < 2 { - return errors.New("kpod push requires exactly 2 arguments") - } - if err := validateFlags(c, pushFlags); err != nil { - return err - } - srcName := args[0] - destName := args[1] - - // --compress and --format can only be used for the "dir" transport - splitArg := strings.SplitN(destName, ":", 2) - if c.IsSet("compress") || c.IsSet("format") { - if splitArg[0] != libpod.DirTransport { - return errors.Errorf("--compress and --format can be set only when pushing to a directory using the 'dir' transport") - } - } - - registryCredsString := c.String("creds") - certPath := c.String("cert-dir") - skipVerify := !c.BoolT("tls-verify") - removeSignatures := c.Bool("remove-signatures") - signBy := c.String("sign-by") - - if registryCredsString != "" { - creds, err := common.ParseRegistryCreds(registryCredsString) - if err != nil { - if err == common.ErrNoPassword { - fmt.Print("Password: ") - password, err := terminal.ReadPassword(0) - if err != nil { - return errors.Wrapf(err, "could not read password from terminal") - } - creds.Password = string(password) - } else { - return err - } - } - registryCreds = creds - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - defer runtime.Shutdown(false) - - var writer io.Writer - if !c.Bool("quiet") { - writer = os.Stdout - } - - var manifestType string - if c.IsSet("format") { - switch c.String("format") { - case "oci": - manifestType = imgspecv1.MediaTypeImageManifest - case "v2s1": - manifestType = manifest.DockerV2Schema1SignedMediaType - case "v2s2", "docker": - manifestType = manifest.DockerV2Schema2MediaType - default: - return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", c.String("format")) - } - } - - options := libpod.CopyOptions{ - Compression: archive.Uncompressed, - SignaturePolicyPath: c.String("signature-policy"), - DockerRegistryOptions: common.DockerRegistryOptions{ - DockerRegistryCreds: registryCreds, - DockerCertPath: certPath, - DockerInsecureSkipTLSVerify: skipVerify, - }, - SigningOptions: common.SigningOptions{ - RemoveSignatures: removeSignatures, - SignBy: signBy, - }, - AuthFile: c.String("authfile"), - Writer: writer, - ManifestMIMEType: manifestType, - ForceCompress: c.Bool("compress"), - } - - return runtime.PushImage(srcName, destName, options) -} diff --git a/cmd/kpod/rm.go b/cmd/kpod/rm.go deleted file mode 100644 index 2f767457f..000000000 --- a/cmd/kpod/rm.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -var ( - rmFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "force, f", - Usage: "Force removal of a running container. The default is false", - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "Remove all containers", - }, - } - rmDescription = "Remove one or more containers" - rmCommand = cli.Command{ - Name: "rm", - Usage: fmt.Sprintf(`kpod rm will remove one or more containers from the host. The container name or ID can be used. - This does not remove images. Running containers will not be removed without the -f option.`), - Description: rmDescription, - Flags: rmFlags, - Action: rmCmd, - ArgsUsage: "", - UseShortOptionHandling: true, - } -) - -// saveCmd saves the image to either docker-archive or oci -func rmCmd(c *cli.Context) error { - if err := validateFlags(c, rmFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - args := c.Args() - if len(args) == 0 && !c.Bool("all") { - return errors.Errorf("specify one or more containers to remove") - } - - var delContainers []*libpod.Container - var lastError error - if c.Bool("all") { - delContainers, err = runtime.GetContainers() - if err != nil { - return errors.Wrapf(err, "unable to get container list") - } - } else { - for _, i := range args { - container, err := runtime.LookupContainer(i) - if err != nil { - fmt.Fprintln(os.Stderr, err) - lastError = errors.Wrapf(err, "unable to find container %s", i) - continue - } - delContainers = append(delContainers, container) - } - } - for _, container := range delContainers { - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to find container %s", container.ID()) - continue - } - err = runtime.RemoveContainer(container, c.Bool("force")) - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to delete container %v", container.ID()) - } else { - fmt.Println(container.ID()) - } - } - return lastError -} diff --git a/cmd/kpod/rmi.go b/cmd/kpod/rmi.go deleted file mode 100644 index 1b4cb7390..000000000 --- a/cmd/kpod/rmi.go +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "fmt" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -var ( - rmiDescription = "removes one or more locally stored images." - rmiFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "remove all images", - }, - cli.BoolFlag{ - Name: "force, f", - Usage: "force removal of the image", - }, - } - rmiCommand = cli.Command{ - Name: "rmi", - Usage: "removes one or more images from local storage", - Description: rmiDescription, - Action: rmiCmd, - ArgsUsage: "IMAGE-NAME-OR-ID [...]", - Flags: rmiFlags, - UseShortOptionHandling: true, - } -) - -func rmiCmd(c *cli.Context) error { - if err := validateFlags(c, rmiFlags); err != nil { - return err - } - removeAll := c.Bool("all") - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - args := c.Args() - if len(args) == 0 && !removeAll { - return errors.Errorf("image name or ID must be specified") - } - if len(args) > 0 && removeAll { - return errors.Errorf("when using the --all switch, you may not pass any images names or IDs") - } - imagesToDelete := args[:] - if removeAll { - localImages, err := runtime.GetImages(&libpod.ImageFilterParams{}) - if err != nil { - return errors.Wrapf(err, "unable to query local images") - } - for _, image := range localImages { - imagesToDelete = append(imagesToDelete, image.ID) - } - } - - for _, arg := range imagesToDelete { - image, err := runtime.GetImage(arg) - if err != nil { - return errors.Wrapf(err, "could not get image %q", arg) - } - id, err := runtime.RemoveImage(image, c.Bool("force")) - if err != nil { - return errors.Wrapf(err, "error removing image %q", id) - } - fmt.Printf("%s\n", id) - } - return nil -} diff --git a/cmd/kpod/run.go b/cmd/kpod/run.go deleted file mode 100644 index 6142983ad..000000000 --- a/cmd/kpod/run.go +++ /dev/null @@ -1,146 +0,0 @@ -package main - -import ( - "fmt" - "sync" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -var runDescription = "Runs a command in a new container from the given image" - -var runCommand = cli.Command{ - Name: "run", - Usage: "run a command in a new container", - Description: runDescription, - Flags: createFlags, - Action: runCmd, - ArgsUsage: "IMAGE [COMMAND [ARG...]]", - SkipArgReorder: true, - UseShortOptionHandling: true, -} - -func runCmd(c *cli.Context) error { - var imageName string - if err := validateFlags(c, createFlags); err != nil { - return err - } - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.Shutdown(false) - - createConfig, err := parseCreateOpts(c, runtime) - if err != nil { - return err - } - - createImage := runtime.NewImage(createConfig.Image) - createImage.LocalName, _ = createImage.GetLocalImageName() - if createImage.LocalName == "" { - // The image wasnt found by the user input'd name or its fqname - // Pull the image - fmt.Printf("Trying to pull %s...", createImage.PullName) - createImage.Pull() - } - - runtimeSpec, err := createConfigToOCISpec(createConfig) - if err != nil { - return err - } - logrus.Debug("spec is ", runtimeSpec) - - if createImage.LocalName != "" { - nameIsID, err := runtime.IsImageID(createImage.LocalName) - if err != nil { - return err - } - if nameIsID { - // If the input from the user is an ID, then we need to get the image - // name for cstorage - createImage.LocalName, err = createImage.GetNameByID() - if err != nil { - return err - } - } - imageName = createImage.LocalName - } else { - imageName, err = createImage.GetFQName() - } - if err != nil { - return err - } - logrus.Debug("imageName is ", imageName) - - imageID, err := createImage.GetImageID() - if err != nil { - return err - } - logrus.Debug("imageID is ", imageID) - - options, err := createConfig.GetContainerCreateOptions() - if err != nil { - return errors.Wrapf(err, "unable to parse new container options") - } - - // Gather up the options for NewContainer which consist of With... funcs - options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false)) - options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel)) - options = append(options, libpod.WithShmDir(createConfig.ShmDir)) - ctr, err := runtime.NewContainer(runtimeSpec, options...) - if err != nil { - return err - } - - logrus.Debug("new container created ", ctr.ID()) - if err := ctr.Init(); err != nil { - return err - } - logrus.Debugf("container storage created for %q", ctr.ID()) - - if c.String("cidfile") != "" { - libpod.WriteFile(ctr.ID(), c.String("cidfile")) - return nil - } - - // Create a bool channel to track that the console socket attach - // is successful. - attached := make(chan bool) - // Create a waitgroup so we can sync and wait for all goroutines - // to finish before exiting main - var wg sync.WaitGroup - - if !createConfig.Detach { - // We increment the wg counter because we need to do the attach - wg.Add(1) - // Attach to the running container - go func() { - logrus.Debugf("trying to attach to the container %s", ctr.ID()) - defer wg.Done() - if err := ctr.Attach(false, c.String("detach-keys"), attached); err != nil { - logrus.Errorf("unable to attach to container %s: %q", ctr.ID(), err) - } - }() - if !<-attached { - return errors.Errorf("unable to attach to container %s", ctr.ID()) - } - } - // Start the container - if err := ctr.Start(); err != nil { - return errors.Wrapf(err, "unable to start container %q", ctr.ID()) - } - if createConfig.Detach { - fmt.Printf("%s\n", ctr.ID()) - return nil - } - wg.Wait() - - if createConfig.Rm { - return runtime.RemoveContainer(ctr, true) - } - return ctr.CleanupStorage() -} diff --git a/cmd/kpod/save.go b/cmd/kpod/save.go deleted file mode 100644 index 85a8c7930..000000000 --- a/cmd/kpod/save.go +++ /dev/null @@ -1,129 +0,0 @@ -package main - -import ( - "io" - "os" - "strings" - - "github.com/containers/image/manifest" - imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -const ( - ociManifestDir = "oci-dir" - v2s2ManifestDir = "docker-dir" -) - -var ( - saveFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "compress", - Usage: "compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)", - }, - cli.StringFlag{ - Name: "output, o", - Usage: "Write to a file, default is STDOUT", - Value: "/dev/stdout", - }, - cli.BoolFlag{ - Name: "quiet, q", - Usage: "Suppress the output", - }, - cli.StringFlag{ - Name: "format", - Usage: "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-dir (directory with v2s2 manifest type)", - }, - } - saveDescription = ` - Save an image to docker-archive or oci-archive on the local machine. - Default is docker-archive` - - saveCommand = cli.Command{ - Name: "save", - Usage: "Save image to an archive", - Description: saveDescription, - Flags: saveFlags, - Action: saveCmd, - ArgsUsage: "", - } -) - -// saveCmd saves the image to either docker-archive or oci -func saveCmd(c *cli.Context) error { - args := c.Args() - if len(args) == 0 { - return errors.Errorf("need at least 1 argument") - } - if err := validateFlags(c, saveFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - defer runtime.Shutdown(false) - - if c.IsSet("compress") && (c.String("format") != ociManifestDir && c.String("format") != v2s2ManifestDir && c.String("format") == "") { - return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") - } - - var writer io.Writer - if !c.Bool("quiet") { - writer = os.Stdout - } - - output := c.String("output") - if output == "/dev/stdout" { - fi := os.Stdout - if logrus.IsTerminal(fi) { - return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") - } - } - - var dst, manifestType string - switch c.String("format") { - case libpod.OCIArchive: - dst = libpod.OCIArchive + ":" + output - case "oci-dir": - dst = libpod.DirTransport + ":" + output - manifestType = imgspecv1.MediaTypeImageManifest - case "docker-dir": - dst = libpod.DirTransport + ":" + output - manifestType = manifest.DockerV2Schema2MediaType - case libpod.DockerArchive: - fallthrough - case "": - dst = libpod.DockerArchive + ":" + output - default: - return errors.Errorf("unknown format option %q", c.String("format")) - } - - saveOpts := libpod.CopyOptions{ - SignaturePolicyPath: "", - Writer: writer, - ManifestMIMEType: manifestType, - ForceCompress: c.Bool("compress"), - } - - // only one image is supported for now - // future pull requests will fix this - for _, image := range args { - dest := dst - // need dest to be in the format transport:path:reference for the following transports - if strings.Contains(dst, libpod.OCIArchive) || strings.Contains(dst, libpod.DockerArchive) { - dest = dst + ":" + image - } - if err := runtime.PushImage(image, dest, saveOpts); err != nil { - if err2 := os.Remove(output); err2 != nil { - logrus.Errorf("error deleting %q: %v", output, err) - } - return errors.Wrapf(err, "unable to save %q", image) - } - } - return nil -} diff --git a/cmd/kpod/spec.go b/cmd/kpod/spec.go deleted file mode 100644 index adfdf7347..000000000 --- a/cmd/kpod/spec.go +++ /dev/null @@ -1,561 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "strings" - - "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/docker/docker/daemon/caps" - "github.com/docker/docker/pkg/mount" - "github.com/docker/go-units" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" - "github.com/opencontainers/selinux/go-selinux/label" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - ann "github.com/projectatomic/libpod/pkg/annotations" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -func blockAccessToKernelFilesystems(config *createConfig, g *generate.Generator) { - if !config.Privileged { - for _, mp := range []string{ - "/proc/kcore", - "/proc/latency_stats", - "/proc/timer_list", - "/proc/timer_stats", - "/proc/sched_debug", - "/proc/scsi", - "/sys/firmware", - } { - g.AddLinuxMaskedPaths(mp) - } - - for _, rp := range []string{ - "/proc/asound", - "/proc/bus", - "/proc/fs", - "/proc/irq", - "/proc/sys", - "/proc/sysrq-trigger", - } { - g.AddLinuxReadonlyPaths(rp) - } - } -} - -func addPidNS(config *createConfig, g *generate.Generator) error { - pidMode := config.PidMode - if pidMode.IsHost() { - return g.RemoveLinuxNamespace(libpod.PIDNamespace) - } - if pidMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(pidMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", pidMode.Container()) - } - pid, err := ctr.PID() - if err != nil { - return errors.Wrapf(err, "Failed to get pid of container %q", pidMode.Container()) - } - pidNsPath := fmt.Sprintf("/proc/%d/ns/pid", pid) - if err := g.AddOrReplaceLinuxNamespace(libpod.PIDNamespace, pidNsPath); err != nil { - return err - } - } - return nil -} - -func addNetNS(config *createConfig, g *generate.Generator) error { - netMode := config.NetMode - if netMode.IsHost() { - return g.RemoveLinuxNamespace(libpod.NetNamespace) - } - if netMode.IsNone() { - return libpod.ErrNotImplemented - } - if netMode.IsBridge() { - return libpod.ErrNotImplemented - } - if netMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(netMode.ConnectedContainer()) - if err != nil { - return errors.Wrapf(err, "container %q not found", netMode.ConnectedContainer()) - } - pid, err := ctr.PID() - if err != nil { - return errors.Wrapf(err, "Failed to get pid of container %q", netMode.ConnectedContainer()) - } - nsPath := fmt.Sprintf("/proc/%d/ns/net", pid) - if err := g.AddOrReplaceLinuxNamespace(libpod.NetNamespace, nsPath); err != nil { - return err - } - } - return nil -} - -func addUTSNS(config *createConfig, g *generate.Generator) error { - utsMode := config.UtsMode - if utsMode.IsHost() { - return g.RemoveLinuxNamespace(libpod.UTSNamespace) - } - return nil -} - -func addIpcNS(config *createConfig, g *generate.Generator) error { - ipcMode := config.IpcMode - if ipcMode.IsHost() { - return g.RemoveLinuxNamespace(libpod.IPCNamespace) - } - if ipcMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(ipcMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", ipcMode.Container()) - } - pid, err := ctr.PID() - if err != nil { - return errors.Wrapf(err, "Failed to get pid of container %q", ipcMode.Container()) - } - nsPath := fmt.Sprintf("/proc/%d/ns/ipc", pid) - if err := g.AddOrReplaceLinuxNamespace(libpod.IPCNamespace, nsPath); err != nil { - return err - } - } - - return nil -} - -func addRlimits(config *createConfig, g *generate.Generator) error { - var ( - ul *units.Ulimit - err error - ) - - for _, u := range config.Resources.Ulimit { - if ul, err = units.ParseUlimit(u); err != nil { - return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u) - } - - g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Soft), uint64(ul.Hard)) - } - return nil -} - -func setupCapabilities(config *createConfig, configSpec *spec.Spec) error { - var err error - var caplist []string - if config.Privileged { - caplist = caps.GetAllCapabilities() - } else { - caplist, err = caps.TweakCapabilities(configSpec.Process.Capabilities.Bounding, config.CapAdd, config.CapDrop) - if err != nil { - return err - } - } - - configSpec.Process.Capabilities.Bounding = caplist - configSpec.Process.Capabilities.Permitted = caplist - configSpec.Process.Capabilities.Inheritable = caplist - configSpec.Process.Capabilities.Effective = caplist - return nil -} - -// Parses information needed to create a container into an OCI runtime spec -func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { - g := generate.New() - g.AddCgroupsMount("ro") - g.SetProcessCwd(config.WorkDir) - g.SetProcessArgs(config.Command) - g.SetProcessTerminal(config.Tty) - // User and Group must go together - g.SetProcessUID(config.User) - g.SetProcessGID(config.Group) - for _, gid := range config.GroupAdd { - g.AddProcessAdditionalGid(gid) - } - for key, val := range config.GetAnnotations() { - g.AddAnnotation(key, val) - } - g.SetRootReadonly(config.ReadOnlyRootfs) - g.SetHostname(config.Hostname) - if config.Hostname != "" { - g.AddProcessEnv("HOSTNAME", config.Hostname) - } - - for _, sysctl := range config.Sysctl { - s := strings.SplitN(sysctl, "=", 2) - g.AddLinuxSysctl(s[0], s[1]) - } - - // RESOURCES - MEMORY - if config.Resources.Memory != 0 { - g.SetLinuxResourcesMemoryLimit(config.Resources.Memory) - } - if config.Resources.MemoryReservation != 0 { - g.SetLinuxResourcesMemoryReservation(config.Resources.MemoryReservation) - } - if config.Resources.MemorySwap != 0 { - g.SetLinuxResourcesMemorySwap(config.Resources.MemorySwap) - } - if config.Resources.KernelMemory != 0 { - g.SetLinuxResourcesMemoryKernel(config.Resources.KernelMemory) - } - if config.Resources.MemorySwappiness != -1 { - g.SetLinuxResourcesMemorySwappiness(uint64(config.Resources.MemorySwappiness)) - } - g.SetLinuxResourcesMemoryDisableOOMKiller(config.Resources.DisableOomKiller) - g.SetProcessOOMScoreAdj(config.Resources.OomScoreAdj) - - // RESOURCES - CPU - - if config.Resources.CPUShares != 0 { - g.SetLinuxResourcesCPUShares(config.Resources.CPUShares) - } - if config.Resources.CPUQuota != 0 { - g.SetLinuxResourcesCPUQuota(config.Resources.CPUQuota) - } - if config.Resources.CPUPeriod != 0 { - g.SetLinuxResourcesCPUPeriod(config.Resources.CPUPeriod) - } - if config.Resources.CPURtRuntime != 0 { - g.SetLinuxResourcesCPURealtimeRuntime(config.Resources.CPURtRuntime) - } - if config.Resources.CPURtPeriod != 0 { - g.SetLinuxResourcesCPURealtimePeriod(config.Resources.CPURtPeriod) - } - if config.Resources.CPUs != "" { - g.SetLinuxResourcesCPUCpus(config.Resources.CPUs) - } - if config.Resources.CPUsetMems != "" { - g.SetLinuxResourcesCPUMems(config.Resources.CPUsetMems) - } - - // SECURITY OPTS - g.SetProcessNoNewPrivileges(config.NoNewPrivileges) - g.SetProcessApparmorProfile(config.ApparmorProfile) - g.SetProcessSelinuxLabel(config.ProcessLabel) - g.SetLinuxMountLabel(config.MountLabel) - blockAccessToKernelFilesystems(config, &g) - - // RESOURCES - PIDS - if config.Resources.PidsLimit != 0 { - g.SetLinuxResourcesPidsLimit(config.Resources.PidsLimit) - } - - for _, i := range config.Tmpfs { - options := []string{"rw", "noexec", "nosuid", "nodev", "size=65536k"} - spliti := strings.SplitN(i, ":", 2) - if len(spliti) > 1 { - if _, _, err := mount.ParseTmpfsOptions(spliti[1]); err != nil { - return nil, err - } - options = strings.Split(spliti[1], ",") - } - // Default options if nothing passed - g.AddTmpfsMount(spliti[0], options) - } - - for name, val := range config.Env { - g.AddProcessEnv(name, val) - } - - if err := addRlimits(config, &g); err != nil { - return nil, err - } - - if err := addPidNS(config, &g); err != nil { - return nil, err - } - - if err := addNetNS(config, &g); err != nil { - return nil, err - } - - if err := addUTSNS(config, &g); err != nil { - return nil, err - } - - if err := addIpcNS(config, &g); err != nil { - return nil, err - } - configSpec := g.Spec() - - if config.SeccompProfilePath != "" && config.SeccompProfilePath != "unconfined" { - seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath) - if err != nil { - return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath) - } - var seccompConfig spec.LinuxSeccomp - if err := json.Unmarshal(seccompProfile, &seccompConfig); err != nil { - return nil, errors.Wrapf(err, "decoding seccomp profile (%s) failed", config.SeccompProfilePath) - } - configSpec.Linux.Seccomp = &seccompConfig - } - - // BIND MOUNTS - mounts, err := config.GetVolumeMounts() - if err != nil { - return nil, errors.Wrapf(err, "error getting volume mounts") - } - configSpec.Mounts = append(configSpec.Mounts, mounts...) - for _, mount := range configSpec.Mounts { - for _, opt := range mount.Options { - switch opt { - case "private", "rprivate", "slave", "rslave", "shared", "rshared": - if err := g.SetLinuxRootPropagation(opt); err != nil { - return nil, errors.Wrapf(err, "error setting root propagation for %q", mount.Destination) - } - } - } - } - - // HANDLE CAPABILITIES - if err := setupCapabilities(config, configSpec); err != nil { - return nil, err - } - - /* - Hooks: &configSpec.Hooks{}, - //Annotations - Resources: &configSpec.LinuxResources{ - Devices: config.GetDefaultDevices(), - BlockIO: &blkio, - //HugepageLimits: - Network: &configSpec.LinuxNetwork{ - // ClassID *uint32 - // Priorites []LinuxInterfacePriority - }, - }, - //CgroupsPath: - //Namespaces: []LinuxNamespace - //Devices - // DefaultAction: - // Architectures - // Syscalls: - }, - // RootfsPropagation - // MaskedPaths - // ReadonlyPaths: - // IntelRdt - }, - } - */ - return configSpec, nil -} - -func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) { - bio := spec.LinuxBlockIO{} - bio.Weight = &c.Resources.BlkioWeight - if len(c.Resources.BlkioWeightDevice) > 0 { - var lwds []spec.LinuxWeightDevice - for _, i := range c.Resources.BlkioWeightDevice { - wd, err := validateweightDevice(i) - if err != nil { - return bio, errors.Wrapf(err, "invalid values for blkio-weight-device") - } - wdStat := getStatFromPath(wd.path) - lwd := spec.LinuxWeightDevice{ - Weight: &wd.weight, - } - lwd.Major = int64(unix.Major(wdStat.Rdev)) - lwd.Minor = int64(unix.Minor(wdStat.Rdev)) - lwds = append(lwds, lwd) - } - } - if len(c.Resources.DeviceReadBps) > 0 { - readBps, err := makeThrottleArray(c.Resources.DeviceReadBps) - if err != nil { - return bio, err - } - bio.ThrottleReadBpsDevice = readBps - } - if len(c.Resources.DeviceWriteBps) > 0 { - writeBpds, err := makeThrottleArray(c.Resources.DeviceWriteBps) - if err != nil { - return bio, err - } - bio.ThrottleWriteBpsDevice = writeBpds - } - if len(c.Resources.DeviceReadIOps) > 0 { - readIOps, err := makeThrottleArray(c.Resources.DeviceReadIOps) - if err != nil { - return bio, err - } - bio.ThrottleReadIOPSDevice = readIOps - } - if len(c.Resources.DeviceWriteIOps) > 0 { - writeIOps, err := makeThrottleArray(c.Resources.DeviceWriteIOps) - if err != nil { - return bio, err - } - bio.ThrottleWriteIOPSDevice = writeIOps - } - - return bio, nil -} - -// GetAnnotations returns the all the annotations for the container -func (c *createConfig) GetAnnotations() map[string]string { - a := getDefaultAnnotations() - // TODO - Which annotations do we want added by default - // TODO - This should be added to the DB long term - if c.Tty { - a["io.kubernetes.cri-o.TTY"] = "true" - } - return a -} - -func getDefaultAnnotations() map[string]string { - var annotations map[string]string - annotations = make(map[string]string) - annotations[ann.Annotations] = "" - annotations[ann.ContainerID] = "" - annotations[ann.ContainerName] = "" - annotations[ann.ContainerType] = "" - annotations[ann.Created] = "" - annotations[ann.HostName] = "" - annotations[ann.IP] = "" - annotations[ann.Image] = "" - annotations[ann.ImageName] = "" - annotations[ann.ImageRef] = "" - annotations[ann.KubeName] = "" - annotations[ann.Labels] = "" - annotations[ann.LogPath] = "" - annotations[ann.Metadata] = "" - annotations[ann.Name] = "" - annotations[ann.PrivilegedRuntime] = "" - annotations[ann.ResolvPath] = "" - annotations[ann.HostnamePath] = "" - annotations[ann.SandboxID] = "" - annotations[ann.SandboxName] = "" - annotations[ann.ShmPath] = "" - annotations[ann.MountPoint] = "" - annotations[ann.TrustedSandbox] = "" - annotations[ann.TTY] = "false" - annotations[ann.Stdin] = "" - annotations[ann.StdinOnce] = "" - annotations[ann.Volumes] = "" - - return annotations -} - -//GetVolumeMounts takes user provided input for bind mounts and creates Mount structs -func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) { - var m []spec.Mount - var options []string - for _, i := range c.Volumes { - // We need to handle SELinux options better here, specifically :Z - spliti := strings.Split(i, ":") - if len(spliti) > 2 { - options = strings.Split(spliti[2], ",") - } - options = append(options, "rbind") - var foundrw, foundro, foundz, foundZ bool - var rootProp string - for _, opt := range options { - switch opt { - case "rw": - foundrw = true - case "ro": - foundro = true - case "z": - foundz = true - case "Z": - foundZ = true - case "private", "rprivate", "slave", "rslave", "shared", "rshared": - rootProp = opt - } - } - if !foundrw && !foundro { - options = append(options, "rw") - } - if foundz { - if err := label.Relabel(spliti[0], c.MountLabel, true); err != nil { - return nil, errors.Wrapf(err, "relabel failed %q", spliti[0]) - } - } - if foundZ { - if err := label.Relabel(spliti[0], c.MountLabel, false); err != nil { - return nil, errors.Wrapf(err, "relabel failed %q", spliti[0]) - } - } - if rootProp == "" { - options = append(options, "rprivate") - } - - m = append(m, spec.Mount{ - Destination: spliti[1], - Type: string(TypeBind), - Source: spliti[0], - Options: options, - }) - } - return m, nil -} - -//GetTmpfsMounts takes user provided input for Tmpfs mounts and creates Mount structs -func (c *createConfig) GetTmpfsMounts() []spec.Mount { - var m []spec.Mount - for _, i := range c.Tmpfs { - // Default options if nothing passed - options := []string{"rw", "noexec", "nosuid", "nodev", "size=65536k"} - spliti := strings.Split(i, ":") - destPath := spliti[0] - if len(spliti) > 1 { - options = strings.Split(spliti[1], ",") - } - m = append(m, spec.Mount{ - Destination: destPath, - Type: string(TypeTmpfs), - Options: options, - Source: string(TypeTmpfs), - }) - } - return m -} - -func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, error) { - var options []libpod.CtrCreateOption - - // Uncomment after talking to mheon about unimplemented funcs - // options = append(options, libpod.WithLabels(c.labels)) - - if c.Interactive { - options = append(options, libpod.WithStdin()) - } - if c.Name != "" { - logrus.Debugf("appending name %s", c.Name) - options = append(options, libpod.WithName(c.Name)) - } - // TODO parse ports into libpod format and include - // TODO should not happen if --net=host - options = append(options, libpod.WithNetNS([]ocicni.PortMapping{})) - - return options, nil -} - -func getStatFromPath(path string) unix.Stat_t { - s := unix.Stat_t{} - _ = unix.Stat(path, &s) - return s -} - -func makeThrottleArray(throttleInput []string) ([]spec.LinuxThrottleDevice, error) { - var ltds []spec.LinuxThrottleDevice - for _, i := range throttleInput { - t, err := validateBpsDevice(i) - if err != nil { - return []spec.LinuxThrottleDevice{}, err - } - ltd := spec.LinuxThrottleDevice{} - ltd.Rate = t.rate - ltdStat := getStatFromPath(t.path) - ltd.Major = int64(unix.Major(ltdStat.Rdev)) - ltd.Minor = int64(unix.Major(ltdStat.Rdev)) - ltds = append(ltds, ltd) - } - return ltds, nil -} diff --git a/cmd/kpod/spec_test.go b/cmd/kpod/spec_test.go deleted file mode 100644 index 01e1a4ad3..000000000 --- a/cmd/kpod/spec_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "reflect" - "testing" - - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/stretchr/testify/assert" -) - -func TestCreateConfig_GetVolumeMounts(t *testing.T) { - data := spec.Mount{ - Destination: "/foobar", - Type: "bind", - Source: "foobar", - Options: []string{"ro", "rbind", "rprivate"}, - } - config := createConfig{ - Volumes: []string{"foobar:/foobar:ro"}, - } - specMount, err := config.GetVolumeMounts() - assert.NoError(t, err) - assert.True(t, reflect.DeepEqual(data, specMount[0])) -} - -func TestCreateConfig_GetTmpfsMounts(t *testing.T) { - data := spec.Mount{ - Destination: "/homer", - Type: "tmpfs", - Source: "tmpfs", - Options: []string{"rw", "size=787448k", "mode=1777"}, - } - config := createConfig{ - Tmpfs: []string{"/homer:rw,size=787448k,mode=1777"}, - } - tmpfsMount := config.GetTmpfsMounts() - assert.True(t, reflect.DeepEqual(data, tmpfsMount[0])) - -} diff --git a/cmd/kpod/start.go b/cmd/kpod/start.go deleted file mode 100644 index 88dadb1c8..000000000 --- a/cmd/kpod/start.go +++ /dev/null @@ -1,131 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strconv" - "sync" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -var ( - startFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "attach, a", - Usage: "Attach container's STDOUT and STDERR", - }, - cli.StringFlag{ - Name: "detach-keys", - Usage: "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _.", - }, - cli.BoolFlag{ - Name: "interactive, i", - Usage: "Keep STDIN open even if not attached", - }, - } - startDescription = ` - kpod start - - Starts one or more containers. The container name or ID can be used. -` - - startCommand = cli.Command{ - Name: "start", - Usage: "Start one or more containers", - Description: startDescription, - Flags: startFlags, - Action: startCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - UseShortOptionHandling: true, - } -) - -func startCmd(c *cli.Context) error { - args := c.Args() - if len(args) < 1 { - return errors.Errorf("you must provide at least one container name or id") - } - - attach := c.Bool("attach") - - if len(args) > 1 && attach { - return errors.Errorf("you cannot start and attach multiple containers at once") - } - - if err := validateFlags(c, startFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.Shutdown(false) - - var lastError error - for _, container := range args { - // Create a bool channel to track that the console socket attach - // is successful. - attached := make(chan bool) - // Create a waitgroup so we can sync and wait for all goroutines - // to finish before exiting main - var wg sync.WaitGroup - - ctr, err := runtime.LookupContainer(container) - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to find container %s", container) - continue - } - - if err := ctr.Init(); err != nil && errors.Cause(err) != libpod.ErrCtrExists { - return err - } - - // We can only be interactive if both the config and the command-line say so - if c.Bool("interactive") && !ctr.Config().Stdin { - return errors.Errorf("the container was not created with the interactive option") - } - noStdIn := c.Bool("interactive") - tty, err := strconv.ParseBool(ctr.Spec().Annotations["io.kubernetes.cri-o.TTY"]) - if err != nil { - return errors.Wrapf(err, "unable to parse annotations in %s", ctr.ID()) - } - // We only get a terminal session if both a tty was specified in the spec and - // -a on the command-line was given. - if attach && tty { - // We increment the wg counter because we need to do the attach - wg.Add(1) - // Attach to the running container - go func() { - logrus.Debugf("trying to attach to the container %s", ctr.ID()) - defer wg.Done() - if err := ctr.Attach(noStdIn, c.String("detach-keys"), attached); err != nil { - logrus.Errorf("unable to attach to container %s: %q", ctr.ID(), err) - } - }() - if !<-attached { - return errors.Errorf("unable to attach to container %s", ctr.ID()) - } - } - err = ctr.Start() - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to start %s", container) - continue - } - if !attach { - fmt.Println(ctr.ID()) - } - wg.Wait() - } - return lastError -} diff --git a/cmd/kpod/stats.go b/cmd/kpod/stats.go deleted file mode 100644 index d98c2ee27..000000000 --- a/cmd/kpod/stats.go +++ /dev/null @@ -1,226 +0,0 @@ -package main - -import ( - "fmt" - "reflect" - "strings" - "time" - - tm "github.com/buger/goterm" - "github.com/docker/go-units" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -type statsOutputParams struct { - Container string `json:"name"` - ID string `json:"id"` - CPUPerc string `json:"cpu_percent"` - MemUsage string `json:"mem_usage"` - MemPerc string `json:"mem_percent"` - NetIO string `json:"netio"` - BlockIO string `json:"blocki"` - PIDS uint64 `json:"pids"` -} - -var ( - statsFlags = []cli.Flag{ - cli.BoolFlag{ - Name: "all, a", - Usage: "show all containers. Only running containers are shown by default. The default is false", - }, - cli.BoolFlag{ - Name: "no-stream", - Usage: "disable streaming stats and only pull the first result, default setting is false", - }, - cli.StringFlag{ - Name: "format", - Usage: "pretty-print container statistics using a Go template", - }, - cli.BoolFlag{ - Name: "no-reset", - Usage: "disable resetting the screen between intervals", - }, - } - - statsDescription = "display a live stream of one or more containers' resource usage statistics" - statsCommand = cli.Command{ - Name: "stats", - Usage: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers", - Description: statsDescription, - Flags: statsFlags, - Action: statsCmd, - ArgsUsage: "", - } -) - -func statsCmd(c *cli.Context) error { - if err := validateFlags(c, statsFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - times := -1 - if c.Bool("no-stream") { - times = 1 - } - - var format string - var ctrs []*libpod.Container - var containerFunc func() ([]*libpod.Container, error) - all := c.Bool("all") - - if c.IsSet("format") { - format = c.String("format") - } else { - format = genStatsFormat() - } - - if len(c.Args()) > 0 { - containerFunc = func() ([]*libpod.Container, error) { return runtime.GetContainersByList(c.Args()) } - } else if all { - containerFunc = runtime.GetAllContainers - } else { - containerFunc = runtime.GetRunningContainers - } - - ctrs, err = containerFunc() - if err != nil { - return errors.Wrapf(err, "unable to get list of containers") - } - - containerStats := map[string]*libpod.ContainerStats{} - for _, ctr := range ctrs { - initialStats, err := ctr.GetContainerStats(&libpod.ContainerStats{}) - if err != nil { - return err - } - containerStats[ctr.ID()] = initialStats - } - step := 1 - if times == -1 { - times = 1 - step = 0 - } - for i := 0; i < times; i += step { - reportStats := []*libpod.ContainerStats{} - for _, ctr := range ctrs { - id := ctr.ID() - if _, ok := containerStats[ctr.ID()]; !ok { - initialStats, err := ctr.GetContainerStats(&libpod.ContainerStats{}) - if err != nil { - return err - } - containerStats[id] = initialStats - } - stats, err := ctr.GetContainerStats(containerStats[id]) - if err != nil { - 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 - } - if strings.ToLower(format) != formats.JSONString && !c.Bool("no-reset") { - tm.Clear() - tm.MoveCursor(1, 1) - tm.Flush() - } - outputStats(reportStats, format) - time.Sleep(time.Second) - } - return nil -} - -func outputStats(stats []*libpod.ContainerStats, format string) error { - var out formats.Writer - var outputStats []statsOutputParams - for _, s := range stats { - outputStats = append(outputStats, getStatsOutputParams(s)) - } - if len(outputStats) == 0 { - return nil - } - if strings.ToLower(format) == formats.JSONString { - out = formats.JSONStructArray{Output: statsToGeneric(outputStats, []statsOutputParams{})} - } else { - out = formats.StdoutTemplateArray{Output: statsToGeneric(outputStats, []statsOutputParams{}), Template: format, Fields: outputStats[0].headerMap()} - } - return formats.Writer(out).Out() -} - -func genStatsFormat() (format string) { - return "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDS}}" -} - -// imagesToGeneric creates an empty array of interfaces for output -func statsToGeneric(templParams []statsOutputParams, JSONParams []statsOutputParams) (genericParams []interface{}) { - if len(templParams) > 0 { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return - } - for _, v := range JSONParams { - genericParams = append(genericParams, interface{}(v)) - } - return -} - -// generate the header based on the template provided -func (i *statsOutputParams) headerMap() map[string]string { - v := reflect.Indirect(reflect.ValueOf(i)) - values := make(map[string]string) - - for i := 0; i < v.NumField(); i++ { - key := v.Type().Field(i).Name - value := key - switch value { - case "CPUPerc": - value = "CPU%" - case "MemUsage": - value = "MemUsage/Limit" - case "MemPerc": - value = "Mem%" - } - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -func combineHumanValues(a, b uint64) string { - return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b))) -} - -func floatToPercentString(f float64) string { - strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f) - if err != nil { - // If things go bazinga, return a safe value - return "0.00 %" - } - return fmt.Sprintf("%.2f", strippedFloat) + "%" -} - -func getStatsOutputParams(stats *libpod.ContainerStats) statsOutputParams { - return statsOutputParams{ - Container: stats.ContainerID[:12], - ID: stats.ContainerID, - CPUPerc: floatToPercentString(stats.CPU), - MemUsage: combineHumanValues(stats.MemUsage, stats.MemLimit), - MemPerc: floatToPercentString(stats.MemPerc), - NetIO: combineHumanValues(stats.NetInput, stats.NetOutput), - BlockIO: combineHumanValues(stats.BlockInput, stats.BlockOutput), - PIDS: stats.PIDs, - } -} diff --git a/cmd/kpod/stop.go b/cmd/kpod/stop.go deleted file mode 100644 index f18fbc232..000000000 --- a/cmd/kpod/stop.go +++ /dev/null @@ -1,104 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/sirupsen/logrus" - "github.com/urfave/cli" -) - -var ( - defaultTimeout int64 = 10 - stopFlags = []cli.Flag{ - cli.Int64Flag{ - Name: "timeout, t", - Usage: "Seconds to wait for stop before killing the container", - Value: defaultTimeout, - }, - cli.BoolFlag{ - Name: "all, a", - Usage: "stop all running containers", - }, - } - stopDescription = ` - kpod stop - - Stops one or more running containers. The container name or ID can be used. - A timeout to forcibly stop the container can also be set but defaults to 10 - seconds otherwise. -` - - stopCommand = cli.Command{ - Name: "stop", - Usage: "Stop one or more containers", - Description: stopDescription, - Flags: stopFlags, - Action: stopCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - } -) - -func stopCmd(c *cli.Context) error { - args := c.Args() - stopTimeout := c.Int64("timeout") - if c.Bool("all") && len(args) > 0 { - return errors.Errorf("no arguments are needed with -a") - } - if len(args) < 1 && !c.Bool("all") { - return errors.Errorf("you must provide at least one container name or id") - } - if err := validateFlags(c, stopFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - logrus.Debugf("Stopping containers with timeout %d", stopTimeout) - - var filterFuncs []libpod.ContainerFilter - var containers []*libpod.Container - var lastError error - - if c.Bool("all") { - // only get running containers - filterFuncs = append(filterFuncs, func(c *libpod.Container) bool { - state, _ := c.State() - return state == libpod.ContainerStateRunning - }) - containers, err = runtime.GetContainers(filterFuncs...) - if err != nil { - return errors.Wrapf(err, "unable to get running containers") - } - } else { - for _, i := range args { - container, err := runtime.LookupContainer(i) - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to find container %s", i) - continue - } - containers = append(containers, container) - } - } - - for _, ctr := range containers { - if err := ctr.Stop(stopTimeout); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to stop container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) - } - } - return lastError -} diff --git a/cmd/kpod/tag.go b/cmd/kpod/tag.go deleted file mode 100644 index f29c8c182..000000000 --- a/cmd/kpod/tag.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "github.com/containers/image/docker/reference" - "github.com/containers/storage" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -var ( - tagDescription = "Adds one or more additional names to locally-stored image" - tagCommand = cli.Command{ - Name: "tag", - Usage: "Add an additional name to a local image", - Description: tagDescription, - Action: tagCmd, - ArgsUsage: "IMAGE-NAME [IMAGE-NAME ...]", - } -) - -func tagCmd(c *cli.Context) error { - args := c.Args() - if len(args) < 2 { - return errors.Errorf("image name and at least one new name must be specified") - } - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not create runtime") - } - defer runtime.Shutdown(false) - - img, err := runtime.GetImage(args[0]) - if err != nil { - return err - } - if img == nil { - return errors.New("null image") - } - err = addImageNames(runtime, img, args[1:]) - if err != nil { - return errors.Wrapf(err, "error adding names %v to image %q", args[1:], args[0]) - } - return nil -} - -func addImageNames(runtime *libpod.Runtime, image *storage.Image, addNames []string) error { - // Add tags to the names if applicable - names, err := expandedTags(addNames) - if err != nil { - return err - } - for _, name := range names { - if err := runtime.TagImage(image, name); err != nil { - return errors.Wrapf(err, "error adding name (%v) to image %q", name, image.ID) - } - } - return nil -} - -func expandedTags(tags []string) ([]string, error) { - expandedNames := []string{} - for _, tag := range tags { - var labelName string - name, err := reference.Parse(tag) - if err != nil { - return nil, errors.Wrapf(err, "error parsing tag %q", name) - } - if _, ok := name.(reference.NamedTagged); ok { - labelName = name.String() - } else { - labelName = name.String() + ":latest" - } - expandedNames = append(expandedNames, labelName) - } - return expandedNames, nil -} diff --git a/cmd/kpod/top.go b/cmd/kpod/top.go deleted file mode 100644 index 0c1eabbdb..000000000 --- a/cmd/kpod/top.go +++ /dev/null @@ -1,258 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/projectatomic/libpod/libpod" - "github.com/urfave/cli" -) - -var ( - topFlags = []cli.Flag{ - cli.StringFlag{ - Name: "format", - Usage: "Change the output to JSON", - }, - } - topDescription = ` - kpod top - - Display the running processes of the container. -` - - topCommand = cli.Command{ - Name: "top", - Usage: "Display the running processes of a container", - Description: topDescription, - Flags: topFlags, - Action: topCmd, - ArgsUsage: "CONTAINER-NAME", - SkipArgReorder: true, - } -) - -func topCmd(c *cli.Context) error { - doJSON := false - if c.IsSet("format") { - if strings.ToUpper(c.String("format")) == "JSON" { - doJSON = true - } else { - return errors.Errorf("only 'json' is supported for a format option") - } - } - args := c.Args() - var psArgs []string - psOpts := []string{"-o", "uid,pid,ppid,c,stime,tname,time,cmd"} - if len(args) < 1 { - return errors.Errorf("you must provide the name or id of a running container") - } - if err := validateFlags(c, topFlags); err != nil { - return err - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.Shutdown(false) - if len(args) > 1 { - psOpts = args[1:] - } - - container, err := runtime.LookupContainer(args[0]) - if err != nil { - return errors.Wrapf(err, "unable to lookup %s", args[0]) - } - conStat, err := container.State() - if err != nil { - return errors.Wrapf(err, "unable to look up state for %s", args[0]) - } - if conStat != libpod.ContainerStateRunning { - return errors.Errorf("top can only be used on running containers") - } - - psArgs = append(psArgs, psOpts...) - - results, err := container.GetContainerPidInformation(psArgs) - if err != nil { - return err - } - headers := getHeaders(results[0]) - format := genTopFormat(headers) - var out formats.Writer - psParams, err := psDataToPSParams(results[1:], headers) - if err != nil { - return errors.Wrap(err, "unable to convert ps data to proper structure") - } - if doJSON { - out = formats.JSONStructArray{Output: topToGeneric(psParams)} - } else { - out = formats.StdoutTemplateArray{Output: topToGeneric(psParams), Template: format, Fields: createTopHeaderMap(headers)} - } - formats.Writer(out).Out() - return nil -} - -func getHeaders(s string) []string { - var headers []string - tmpHeaders := strings.Fields(s) - for _, header := range tmpHeaders { - headers = append(headers, strings.Replace(header, "%", "", -1)) - } - return headers -} - -func genTopFormat(headers []string) string { - format := "table " - for _, header := range headers { - format = fmt.Sprintf("%s{{.%s}}\t", format, header) - } - return format -} - -// imagesToGeneric creates an empty array of interfaces for output -func topToGeneric(templParams []PSParams) (genericParams []interface{}) { - for _, v := range templParams { - genericParams = append(genericParams, interface{}(v)) - } - return -} - -// generate the header based on the template provided -func createTopHeaderMap(v []string) map[string]string { - values := make(map[string]string) - for _, key := range v { - value := key - if value == "CPU" { - value = "%CPU" - } else if value == "MEM" { - value = "%MEM" - } - values[key] = strings.ToUpper(splitCamelCase(value)) - } - return values -} - -// PSDataToParams converts a string array of data and its headers to an -// arra if PSParams -func psDataToPSParams(data []string, headers []string) ([]PSParams, error) { - var params []PSParams - for _, line := range data { - tmpMap := make(map[string]string) - tmpArray := strings.Fields(line) - if len(tmpArray) == 0 { - continue - } - for index, v := range tmpArray { - header := headers[index] - tmpMap[header] = v - } - jsonData, _ := json.Marshal(tmpMap) - var r PSParams - err := json.Unmarshal(jsonData, &r) - if err != nil { - return []PSParams{}, err - } - params = append(params, r) - } - return params, nil -} - -//PSParams is a list of options that the command line ps recognizes -type PSParams struct { - CPU string - MEM string - COMMAND string - BLOCKED string - START string - TIME string - C string - CAUGHT string - CGROUP string - CLSCLS string - CLS string - CMD string - CP string - DRS string - EGID string - EGROUP string - EIP string - ESP string - ELAPSED string - EUIDE string - USER string - F string - FGID string - FGROUP string - FUID string - FUSER string - GID string - GROUP string - IGNORED string - IPCNS string - LABEL string - STARTED string - SESSION string - LWP string - MACHINE string - MAJFLT string - MINFLT string - MNTNS string - NETNS string - NI string - NLWP string - OWNER string - PENDING string - PGID string - PGRP string - PID string - PIDNS string - POL string - PPID string - PRI string - PSR string - RGID string - RGROUP string - RSS string - RSZ string - RTPRIO string - RUID string - RUSER string - S string - SCH string - SEAT string - SESS string - P string - SGID string - SGROUP string - SID string - SIZE string - SLICE string - SPID string - STACKP string - STIME string - SUID string - SUPGID string - SUPGRP string - SUSER string - SVGID string - SZ string - TGID string - THCNT string - TID string - TTY string - TPGID string - TRS string - TT string - UID string - UNIT string - USERNS string - UTSNS string - UUNIT string - VSZ string - WCHAN string -} diff --git a/cmd/kpod/umount.go b/cmd/kpod/umount.go deleted file mode 100644 index 4b6aba99e..000000000 --- a/cmd/kpod/umount.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "github.com/pkg/errors" - "github.com/urfave/cli" -) - -var ( - umountCommand = cli.Command{ - Name: "umount", - Aliases: []string{"unmount"}, - Usage: "Unmount a working container's root filesystem", - Description: "Unmounts a working container's root filesystem", - Action: umountCmd, - ArgsUsage: "CONTAINER-NAME-OR-ID", - } -) - -func umountCmd(c *cli.Context) error { - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - args := c.Args() - if len(args) == 0 { - return errors.Errorf("container ID must be specified") - } - if len(args) > 1 { - return errors.Errorf("too many arguments specified") - } - - ctr, err := runtime.LookupContainer(args[0]) - if err != nil { - return errors.Wrapf(err, "error looking up container %q", args[0]) - } - - return ctr.Unmount() -} diff --git a/cmd/kpod/unpause.go b/cmd/kpod/unpause.go deleted file mode 100644 index 6deed7e77..000000000 --- a/cmd/kpod/unpause.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/pkg/errors" - "github.com/urfave/cli" -) - -var ( - unpauseDescription = ` - kpod unpause - - Unpauses one or more running containers. The container name or ID can be used. -` - unpauseCommand = cli.Command{ - Name: "unpause", - Usage: "Unpause the processes in one or more containers", - Description: unpauseDescription, - Action: unpauseCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - } -) - -func unpauseCmd(c *cli.Context) error { - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "could not get runtime") - } - defer runtime.Shutdown(false) - - args := c.Args() - if len(args) < 1 { - return errors.Errorf("you must provide at least one container name or id") - } - - var lastError error - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "error looking up container %q", arg) - continue - } - if err = ctr.Unpause(); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to unpause container %v", ctr.ID()) - } else { - fmt.Println(ctr.ID()) - } - } - return lastError -} diff --git a/cmd/kpod/user.go b/cmd/kpod/user.go deleted file mode 100644 index 3e2e308c5..000000000 --- a/cmd/kpod/user.go +++ /dev/null @@ -1,121 +0,0 @@ -package main - -// #include <sys/types.h> -// #include <grp.h> -// #include <pwd.h> -// #include <stdlib.h> -// #include <stdio.h> -// #include <string.h> -// typedef FILE * pFILE; -import "C" - -import ( - "fmt" - "os/user" - "path/filepath" - "sync" - "syscall" - "unsafe" - - "github.com/pkg/errors" -) - -func fopenContainerFile(rootdir, filename string) (C.pFILE, error) { - var st, lst syscall.Stat_t - - ctrfile := filepath.Join(rootdir, filename) - cctrfile := C.CString(ctrfile) - defer C.free(unsafe.Pointer(cctrfile)) - mode := C.CString("r") - defer C.free(unsafe.Pointer(mode)) - f, err := C.fopen(cctrfile, mode) - if f == nil || err != nil { - return nil, errors.Wrapf(err, "error opening %q", ctrfile) - } - if err = syscall.Fstat(int(C.fileno(f)), &st); err != nil { - return nil, errors.Wrapf(err, "fstat(%q)", ctrfile) - } - if err = syscall.Lstat(ctrfile, &lst); err != nil { - return nil, errors.Wrapf(err, "lstat(%q)", ctrfile) - } - if st.Dev != lst.Dev || st.Ino != lst.Ino { - return nil, errors.Errorf("%q is not a regular file", ctrfile) - } - return f, nil -} - -var ( - lookupUser, lookupGroup sync.Mutex -) - -func lookupUserInContainer(rootdir, username string) (uint64, uint64, error) { - name := C.CString(username) - defer C.free(unsafe.Pointer(name)) - - f, err := fopenContainerFile(rootdir, "/etc/passwd") - if err != nil { - return 0, 0, err - } - defer C.fclose(f) - - lookupUser.Lock() - defer lookupUser.Unlock() - - pwd := C.fgetpwent(f) - for pwd != nil { - if C.strcmp(pwd.pw_name, name) != 0 { - pwd = C.fgetpwent(f) - continue - } - return uint64(pwd.pw_uid), uint64(pwd.pw_gid), nil - } - - return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username)) -} - -func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64, error) { - f, err := fopenContainerFile(rootdir, "/etc/passwd") - if err != nil { - return "", 0, err - } - defer C.fclose(f) - - lookupUser.Lock() - defer lookupUser.Unlock() - - pwd := C.fgetpwent(f) - for pwd != nil { - if uint64(pwd.pw_uid) != userid { - pwd = C.fgetpwent(f) - continue - } - return C.GoString(pwd.pw_name), uint64(pwd.pw_gid), nil - } - - return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up user with UID %d", userid)) -} - -func lookupGroupInContainer(rootdir, groupname string) (uint64, error) { - name := C.CString(groupname) - defer C.free(unsafe.Pointer(name)) - - f, err := fopenContainerFile(rootdir, "/etc/group") - if err != nil { - return 0, err - } - defer C.fclose(f) - - lookupGroup.Lock() - defer lookupGroup.Unlock() - - grp := C.fgetgrent(f) - for grp != nil { - if C.strcmp(grp.gr_name, name) != 0 { - grp = C.fgetgrent(f) - continue - } - return uint64(grp.gr_gid), nil - } - - return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname)) -} diff --git a/cmd/kpod/version.go b/cmd/kpod/version.go deleted file mode 100644 index 586c41da6..000000000 --- a/cmd/kpod/version.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "fmt" - "runtime" - "strconv" - "time" - - "github.com/urfave/cli" -) - -// Overwritten at build time -var ( - // gitCommit is the commit that the binary is being built from. - // It will be populated by the Makefile. - gitCommit string - // buildInfo is the time at which the binary was built - // It will be populated by the Makefile. - buildInfo string -) - -// versionCmd gets and prints version info for version command -func versionCmd(c *cli.Context) error { - fmt.Println("Version: ", c.App.Version) - fmt.Println("Go Version: ", runtime.Version()) - if gitCommit != "" { - fmt.Println("Git Commit: ", gitCommit) - } - if buildInfo != "" { - // Converts unix time from string to int64 - buildTime, err := strconv.ParseInt(buildInfo, 10, 64) - if err != nil { - return err - } - // Prints out the build time in readable format - fmt.Println("Built: ", time.Unix(buildTime, 0).Format(time.ANSIC)) - } - fmt.Println("OS/Arch: ", runtime.GOOS+"/"+runtime.GOARCH) - - return nil -} - -// Cli command to print out the full version of kpod -var versionCommand = cli.Command{ - Name: "version", - Usage: "Display the KPOD Version Information", - Action: versionCmd, -} diff --git a/cmd/kpod/wait.go b/cmd/kpod/wait.go deleted file mode 100644 index 6e22f54e5..000000000 --- a/cmd/kpod/wait.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/pkg/errors" - "github.com/urfave/cli" -) - -var ( - waitDescription = ` - kpod wait - - Block until one or more containers stop and then print their exit codes -` - - waitCommand = cli.Command{ - Name: "wait", - Usage: "Block on one or more containers", - Description: waitDescription, - Action: waitCmd, - ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]", - } -) - -func waitCmd(c *cli.Context) error { - args := c.Args() - if len(args) < 1 { - return errors.Errorf("you must provide at least one container name or id") - } - - runtime, err := getRuntime(c) - if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") - } - defer runtime.Shutdown(false) - - if err != nil { - return errors.Wrapf(err, "could not get config") - } - - var lastError error - for _, container := range c.Args() { - ctr, err := runtime.LookupContainer(container) - if err != nil { - return errors.Wrapf(err, "unable to find container %s", container) - } - returnCode, err := ctr.Wait() - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to wait for the container %v", container) - } else { - fmt.Println(returnCode) - } - } - - return lastError -} |