Remove kpod code after repository move

Signed-off-by: Matthew Heon <mheon@redhat.com>
This commit is contained in:
Matthew Heon 2017-11-02 11:53:01 -04:00
parent 19d90e7c23
commit 1bf6d20309
125 changed files with 3 additions and 36015 deletions

View file

@ -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.

View file

@ -1,450 +0,0 @@
package main
import (
"os"
"reflect"
"regexp"
"strings"
is "github.com/containers/image/storage"
"github.com/containers/storage"
"github.com/fatih/camelcase"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/kubernetes-incubator/cri-o/server"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var (
stores = make(map[storage.Store]struct{})
)
func getStore(c *libkpod.Config) (storage.Store, error) {
options := storage.DefaultStoreOptions
options.GraphRoot = c.Root
options.RunRoot = c.RunRoot
options.GraphDriverName = c.Storage
options.GraphDriverOptions = c.StorageOptions
store, err := storage.GetStore(options)
if err != nil {
return nil, err
}
is.Transport.SetStore(store)
stores[store] = struct{}{}
return store, nil
}
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))
}
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(server.CrioConfigPath); err == nil {
configFile = server.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")
}
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)",
},
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: "/",
},
}

View file

@ -1,51 +0,0 @@
package main
import (
"os/exec"
"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")
}
}
func pullTestImage(name string) error {
cmd := exec.Command("crioctl", "image", "pull", name)
err := cmd.Run()
if err != nil {
return err
}
return nil
}

View file

@ -1,343 +0,0 @@
package main
import (
"fmt"
"strconv"
"github.com/docker/go-units"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/pkg/errors"
"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 = []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TERM=xterm"}
)
type createResourceConfig struct {
blkioDevice []string // blkio-weight-device
blkioWeight uint16 // blkio-weight
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
memorySwapiness uint64 // memory-swappiness
oomScoreAdj int //oom-score-adj
pidsLimit int64 // pids-limit
shmSize string
ulimit []string //ulimit
}
type createConfig struct {
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 []string //env
expose []string //expose
groupAdd []uint32 // group-add
hostname string //hostname
image string
interactive bool //interactive
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
network string //network
networkAlias []string //network-alias
nsIPC string // ipc
nsNet string //net
nsPID string //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
securityOpts []string //security-opt
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
volumes []string //volume
volumesFrom []string //volumes-from
workDir string //workdir
}
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...]]",
}
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
if err := validateFlags(c, createFlags); err != nil {
return err
}
runtime, err := getRuntime(c)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
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)
if !createImage.HasImageLocal() {
// 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
}
defer runtime.Shutdown(false)
imageName, err := createImage.GetFQName()
if err != nil {
return err
}
imageID, err := createImage.GetImageID()
if err != nil {
return err
}
options, err := createConfig.GetContainerCreateOptions(c)
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))
ctr, err := runtime.NewContainer(runtimeSpec, options...)
if err != nil {
return err
}
if c.String("cidfile") != "" {
libpod.WriteFile(ctr.ID(), c.String("cidfile"))
} else {
fmt.Printf("%s\n", ctr.ID())
}
return nil
}
// 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
image := c.Args()[0]
if len(c.Args()) < 1 {
return nil, errors.Errorf("image name or ID is required")
}
if len(c.Args()) > 1 {
command = c.Args()[1:]
}
// LABEL VARIABLES
labels, err := getAllLabels(c)
if err != nil {
return &createConfig{}, errors.Wrapf(err, "unable to process labels")
}
// ENVIRONMENT VARIABLES
// TODO where should env variables be verified to be x=y format
env, err := getAllEnvironmentVariables(c)
if 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)
}
config := &createConfig{
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("env"),
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"),
nsIPC: c.String("ipc"),
nsNet: c.String("net"),
nsPID: c.String("pid"),
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,
blkioDevice: 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,
memorySwapiness: c.Uint64("memory-swapiness"),
kernelMemory: memoryKernel,
oomScoreAdj: c.Int("oom-score-adj"),
pidsLimit: c.Int64("pids-limit"),
ulimit: c.StringSlice("ulimit"),
},
rm: c.Bool("rm"),
securityOpts: c.StringSlice("security-opt"),
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: c.Bool("tty"),
user: uid,
group: gid,
volumes: c.StringSlice("volume"),
volumesFrom: c.StringSlice("volumes-from"),
workDir: c.String("workdir"),
}
return config, nil
}

View file

@ -1,52 +0,0 @@
package main
import (
"strings"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
func getAllLabels(cli *cli.Context) (map[string]string, error) {
var labelValues []string
labels := make(map[string]string)
labelValues, labelErr := readKVStrings(cli.StringSlice("label-file"), cli.StringSlice("label"))
if labelErr != nil {
return labels, errors.Wrapf(labelErr, "unable to process labels from --label and label-file")
}
// Process KEY=VALUE stringslice in string map for WithLabels func
if len(labelValues) > 0 {
for _, i := range labelValues {
spliti := strings.Split(i, "=")
if len(spliti) > 1 {
return labels, errors.Errorf("labels must be in KEY=VALUE format: %s is invalid", i)
}
labels[spliti[0]] = spliti[1]
}
}
return labels, nil
}
func getAllEnvironmentVariables(cli *cli.Context) ([]string, error) {
env, err := readKVStrings(cli.StringSlice("env-file"), cli.StringSlice("env"))
if err != nil {
return []string{}, errors.Wrapf(err, "unable to process variables from --env and --env-file")
}
// Add default environment variables if nothing defined
if len(env) == 0 {
env = append(env, defaultEnvVariables...)
}
return env, 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
}

View file

@ -1,128 +0,0 @@
package main
import (
"fmt"
"github.com/containers/storage/pkg/archive"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/pkg/errors"
"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
}

View file

@ -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"`
}

View file

@ -1,106 +0,0 @@
package main
import (
"io"
"os"
"fmt"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
type exportOptions struct {
output string
container string
}
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 {
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.")
}
container := args[0]
if err := validateFlags(c, exportFlags); err != nil {
return err
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
}
store, err := getStore(config)
if err != nil {
return err
}
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")
}
}
opts := exportOptions{
output: output,
container: container,
}
return exportContainer(store, opts)
}
// exportContainer exports the contents of a container and saves it as
// a tarball on disk
func exportContainer(store storage.Store, opts exportOptions) error {
mountPoint, err := store.Mount(opts.container, "")
if err != nil {
return errors.Wrapf(err, "error finding container %q", opts.container)
}
defer func() {
if err := store.Unmount(opts.container); err != nil {
fmt.Printf("error unmounting container %q: %v\n", opts.container, err)
}
}()
input, err := archive.Tar(mountPoint, archive.Uncompressed)
if err != nil {
return errors.Wrapf(err, "error reading container directory %q", opts.container)
}
outFile, err := os.Create(opts.output)
if err != nil {
return errors.Wrapf(err, "error creating file %q", opts.output)
}
defer outFile.Close()
_, err = io.Copy(outFile, input)
return err
}

View file

@ -1,143 +0,0 @@
package formats
import (
"encoding/json"
"fmt"
"os"
"strings"
"text/tabwriter"
"text/template"
"bytes"
"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
}

View file

@ -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]
}

View file

@ -1,243 +0,0 @@
package main
import (
"reflect"
"strconv"
"strings"
"time"
"github.com/containers/image/types"
units "github.com/docker/go-units"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
const (
createdByTruncLength = 45
idTruncLength = 13
)
// 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: "",
}
)
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 config")
}
defer runtime.Shutdown(false)
format := genHistoryFormat(c.Bool("quiet"))
if c.IsSet("format") {
format = c.String("format")
}
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(quiet bool) (format string) {
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()
}

View file

@ -1,330 +0,0 @@
package main
import (
"fmt"
"reflect"
"strings"
"time"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/docker/go-units"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/kubernetes-incubator/cri-o/libpod/common"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
type imagesTemplateParams struct {
ID string
Name string
Digest digest.Digest
CreatedAt string
Size string
}
type imagesJSONParams struct {
ID string `json:"id"`
Name []string `json:"names"`
Digest digest.Digest `json:"digest"`
CreatedAt 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: "",
}
)
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)
var format string
if c.IsSet("format") {
format = c.String("format")
} else {
format = genImagesFormat(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(quiet, noHeading, digests bool) (format string) {
if quiet {
return formats.IDString
}
format = "table {{.ID}}\t{{.Name}}\t"
if noHeading {
format = "{{.ID}}\t{{.Name}}\t"
}
if digests {
format += "{{.Digest}}\t"
}
format += "{{.CreatedAt}}\t{{.Size}}\t"
return
}
// 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 == "Name" {
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 := img.ID
if !opts.noTrunc {
imageID = imageID[:idTruncLength]
}
imageName := "<none>"
if len(img.Names) > 0 {
imageName = img.Names[0]
}
info, imageDigest, size, _ := runtime.InfoAndDigestAndSize(*img)
if info != nil {
createdTime = info.Created
}
params := imagesTemplateParams{
ID: imageID,
Name: imageName,
Digest: imageDigest,
CreatedAt: units.HumanDuration(time.Since((createdTime))) + " ago",
Size: units.HumanSize(float64(size)),
}
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
info, imageDigest, size, _ := runtime.InfoAndDigestAndSize(*img)
if info != nil {
createdTime = info.Created
}
params := imagesJSONParams{
ID: img.ID,
Name: img.Names,
Digest: imageDigest,
CreatedAt: createdTime,
Size: 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 *types.ImageInspectInfo) 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 *types.ImageInspectInfo) bool {
if params == nil || params.BeforeImage.IsZero() {
return true
}
return info.Created.Before(params.BeforeImage)
}
case "since-image":
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
if params == nil || params.SinceImage.IsZero() {
return true
}
return info.Created.After(params.SinceImage)
}
case "dangling":
return func(image *storage.Image, info *types.ImageInspectInfo) 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 *types.ImageInspectInfo) bool {
if params == nil || params.ReferencePattern == "" {
return true
}
return libpod.MatchesReference(params.ImageName, params.ReferencePattern)
}
case "image-input":
return func(image *storage.Image, info *types.ImageInspectInfo) bool {
if params == nil || params.ImageInput == "" {
return true
}
return libpod.MatchesReference(params.ImageName, params.ImageInput)
}
default:
fmt.Println("invalid filter type", filterType)
return nil
}
}

View file

@ -1,200 +0,0 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"runtime"
"github.com/docker/docker/pkg/system"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/pkg/errors"
"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{}{}
infoGivers := []infoGiverFunc{
storeInfo,
hostInfo,
}
if c.Bool("debug") {
infoGivers = append(infoGivers, debugInfo)
}
for _, giver := range infoGivers {
thisName, thisInfo, err := giver(c)
if err != nil {
info[thisName] = infoErr(err)
continue
}
info[thisName] = thisInfo
}
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
}
func infoErr(err error) map[string]interface{} {
return map[string]interface{}{
"error": err.Error(),
}
}
type infoGiverFunc func(c *cli.Context) (name string, info map[string]interface{}, err error)
// top-level "debug" info
func debugInfo(c *cli.Context) (string, map[string]interface{}, error) {
info := map[string]interface{}{}
info["compiler"] = runtime.Compiler
info["go version"] = runtime.Version()
info["kpod version"] = c.App.Version
info["git commit"] = gitCommit
return "debug", info, nil
}
// top-level "host" info
func hostInfo(c *cli.Context) (string, map[string]interface{}, error) {
// lets say OS, arch, number of cpus, amount of memory, maybe os distribution/version, hostname, kernel version, uptime
info := map[string]interface{}{}
info["os"] = runtime.GOOS
info["arch"] = runtime.GOARCH
info["cpus"] = runtime.NumCPU()
mi, err := system.ReadMemInfo()
if err != nil {
info["meminfo"] = infoErr(err)
} else {
// TODO this might be a place for github.com/dustin/go-humanize
info["MemTotal"] = mi.MemTotal
info["MemFree"] = mi.MemFree
info["SwapTotal"] = mi.SwapTotal
info["SwapFree"] = mi.SwapFree
}
if kv, err := readKernelVersion(); err != nil {
info["kernel"] = infoErr(err)
} else {
info["kernel"] = kv
}
if up, err := readUptime(); err != nil {
info["uptime"] = infoErr(err)
} else {
info["uptime"] = up
}
if host, err := os.Hostname(); err != nil {
info["hostname"] = infoErr(err)
} else {
info["hostname"] = host
}
return "host", info, nil
}
// top-level "store" info
func storeInfo(c *cli.Context) (string, map[string]interface{}, error) {
storeStr := "store"
config, err := getConfig(c)
if err != nil {
return storeStr, nil, errors.Wrapf(err, "Could not get config")
}
store, err := getStore(config)
if err != nil {
return storeStr, nil, err
}
// lets say storage driver in use, number of images, number of containers
info := map[string]interface{}{}
info["GraphRoot"] = store.GraphRoot()
info["RunRoot"] = store.RunRoot()
info["GraphDriverName"] = store.GraphDriverName()
info["GraphOptions"] = store.GraphOptions()
statusPairs, err := store.Status()
if err != nil {
return storeStr, nil, err
}
status := map[string]string{}
for _, pair := range statusPairs {
status[pair[0]] = pair[1]
}
info["GraphStatus"] = status
images, err := store.Images()
if err != nil {
info["ImageStore"] = infoErr(err)
} else {
info["ImageStore"] = map[string]interface{}{
"number": len(images),
}
}
containers, err := store.Containers()
if err != nil {
info["ContainerStore"] = infoErr(err)
} else {
info["ContainerStore"] = map[string]interface{}{
"number": len(containers),
}
}
return storeStr, info, nil
}
func readKernelVersion() (string, error) {
buf, err := ioutil.ReadFile("/proc/version")
if err != nil {
return "", err
}
f := bytes.Fields(buf)
if len(f) < 2 {
return string(bytes.TrimSpace(buf)), nil
}
return string(f[2]), nil
}
func readUptime() (string, error) {
buf, err := ioutil.ReadFile("/proc/uptime")
if err != nil {
return "", err
}
f := bytes.Fields(buf)
if len(f) < 1 {
return "", fmt.Errorf("invalid uptime")
}
return string(f[0]), nil
}

View file

@ -1,120 +0,0 @@
package main
import (
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/kubernetes-incubator/cri-o/libpod/images"
"github.com/pkg/errors"
"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
}
itemType := c.String("type")
size := c.Bool("size")
switch itemType {
case inspectTypeContainer:
case inspectTypeImage:
case inspectAll:
default:
return errors.Errorf("the only recognized types are %q, %q, and %q", inspectTypeContainer, inspectTypeImage, inspectAll)
}
name := args[0]
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "Could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not get container server")
}
defer server.Shutdown()
if err = server.Update(); err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
outputFormat := c.String("format")
var data interface{}
switch itemType {
case inspectTypeContainer:
data, err = server.GetContainerData(name, size)
if err != nil {
return errors.Wrapf(err, "error parsing container data")
}
case inspectTypeImage:
data, err = images.GetData(server.Store(), name)
if err != nil {
return errors.Wrapf(err, "error parsing image data")
}
case inspectAll:
ctrData, err := server.GetContainerData(name, size)
if err != nil {
imgData, err := images.GetData(server.Store(), name)
if err != nil {
return errors.Wrapf(err, "error parsing container or image data")
}
data = imgData
} else {
data = ctrData
}
}
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
}

View file

@ -1,74 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/docker/docker/pkg/signal"
"github.com/kubernetes-incubator/cri-o/libkpod"
"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
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not get container server")
}
killSignal := c.String("signal")
// Check if the signalString provided by the user is valid
// Invalid signals will return err
sysSignal, err := signal.ParseSignal(killSignal)
if err != nil {
return err
}
defer server.Shutdown()
err = server.Update()
if err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
var lastError error
for _, container := range c.Args() {
id, err := server.ContainerKill(container, sysSignal)
if err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "unable to kill %v", container)
} else {
fmt.Println(id)
}
}
return lastError
}

View file

@ -1,116 +0,0 @@
package main
import (
"io"
"io/ioutil"
"os"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/pkg/errors"
"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
if err := runtime.PullImage(src, options); err != nil {
src = libpod.OCIArchive + ":" + input
// generate full src name with specified image:tag
if image != "" {
src = src + ":" + image
}
if err := runtime.PullImage(src, options); err != nil {
return errors.Wrapf(err, "error pulling %q", src)
}
}
return nil
}

View file

@ -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/kubernetes-incubator/cri-o/libpod/common"
"github.com/pkg/errors"
"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"))
// 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
}

View file

@ -1,69 +0,0 @@
package main
import (
"fmt"
"github.com/containers/image/pkg/docker/config"
"github.com/kubernetes-incubator/cri-o/libpod/common"
"github.com/pkg/errors"
"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"))
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)
}
}

View file

@ -1,92 +0,0 @@
package main
import (
"fmt"
"time"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
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 {
args := c.Args()
if len(args) != 1 {
return errors.Errorf("'kpod logs' requires exactly one container name/ID")
}
if err := validateFlags(c, logsFlags); err != nil {
return err
}
container := c.Args().First()
var opts libkpod.LogOptions
opts.Details = c.Bool("details")
opts.Follow = c.Bool("follow")
opts.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"))
}
opts.SinceTime = since
}
opts.Tail = c.Uint64("tail")
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not create container server")
}
defer server.Shutdown()
err = server.Update()
if err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
logs := make(chan string)
go func() {
err = server.GetLogs(container, logs, opts)
}()
printLogs(logs)
return err
}
func printLogs(logs chan string) {
for line := range logs {
fmt.Println(line)
}
}

View file

@ -1,135 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/containers/storage/pkg/reexec"
"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
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{
createCommand,
diffCommand,
exportCommand,
historyCommand,
imagesCommand,
infoCommand,
inspectCommand,
killCommand,
loadCommand,
loginCommand,
logoutCommand,
logsCommand,
mountCommand,
pauseCommand,
psCommand,
pullCommand,
pushCommand,
renameCommand,
rmCommand,
rmiCommand,
runCommand,
saveCommand,
statsCommand,
stopCommand,
tagCommand,
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
}
return nil
}
app.After = func(*cli.Context) error {
// called by Run() when the command handler succeeds
shutdownStores()
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: "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: "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)
}
}

View file

@ -1,121 +0,0 @@
package main
import (
js "encoding/json"
"fmt"
of "github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/pkg/errors"
"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,
}
)
// MountOutputParams stores info about each layer
type jsonMountPoint struct {
ID string `json:"id"`
Names []string `json:"names"`
MountPoint string `json:"mountpoint"`
}
func mountCmd(c *cli.Context) error {
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 err := validateFlags(c, mountFlags); err != nil {
return err
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "Could not get config")
}
store, err := getStore(config)
if err != nil {
return errors.Wrapf(err, "error getting store")
}
if len(args) == 1 {
if json {
return errors.Wrapf(err, "json option can not be used with a container id")
}
mountPoint, err := store.Mount(args[0], c.String("label"))
if err != nil {
return errors.Wrapf(err, "error finding container %q", args[0])
}
fmt.Printf("%s\n", mountPoint)
} else {
jsonMountPoints := []jsonMountPoint{}
containers, err2 := store.Containers()
if err2 != nil {
return errors.Wrapf(err2, "error reading list of all containers")
}
for _, container := range containers {
layer, err := store.Layer(container.LayerID)
if err != nil {
return errors.Wrapf(err, "error finding layer %q for container %q", container.LayerID, container.ID)
}
if layer.MountPoint == "" {
continue
}
if json {
jsonMountPoints = append(jsonMountPoints, jsonMountPoint{ID: container.ID, Names: container.Names, MountPoint: layer.MountPoint})
continue
}
if c.Bool("notruncate") {
fmt.Printf("%-64s %s\n", container.ID, layer.MountPoint)
} else {
fmt.Printf("%-12.12s %s\n", container.ID, layer.MountPoint)
}
}
if json {
data, err := js.MarshalIndent(jsonMountPoints, "", " ")
if err != nil {
return err
}
fmt.Printf("%s\n", data)
}
}
return nil
}

View file

@ -1,886 +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(files []string, override []string) ([]string, error) {
envVariables := []string{}
for _, ef := range files {
parsedVars, err := parseEnvFile(ef)
if err != nil {
return nil, err
}
envVariables = append(envVariables, parsedVars...)
}
// parse the '-e' and '--env' after, to allow override
envVariables = append(envVariables, override...)
return envVariables, nil
}
// parseEnvFile reads a file with environment variables enumerated by lines
func parseEnvFile(filename string) ([]string, error) {
fh, err := os.Open(filename)
if err != nil {
return []string{}, err
}
defer fh.Close()
lines := []string{}
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, "#") {
data := strings.SplitN(line, "=", 2)
// trim the front of a variable, but nothing else
variable := strings.TrimLeft(data[0], whiteSpaces)
if strings.ContainsAny(variable, whiteSpaces) {
return []string{}, errors.Errorf("variable %q has white spaces, poorly formatted environment", variable)
}
if len(data) > 1 {
// pass the value through, no trimming
lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
} else {
// if only a pass-through variable is given, clean it up.
lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line)))
}
}
}
return lines, 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
}
// validateLink validates that the specified string has a valid link format (containerName:alias).
func validateLink(val string) (string, error) { //nolint
if _, _, err := parseLink(val); err != nil {
return val, err
}
return val, nil
}
// parseLink parses and validates the specified string as a link format (name:alias)
func parseLink(val string) (string, string, error) {
if val == "" {
return "", "", fmt.Errorf("empty string specified for links")
}
arr := strings.Split(val, ":")
if len(arr) > 2 {
return "", "", fmt.Errorf("bad format for links: %s", val)
}
if len(arr) == 1 {
return val, val, nil
}
// This is kept because we can actually get a HostConfig with links
// from an already created container and the format is not `foo:bar`
// but `/foo:/c1/bar`
if strings.HasPrefix(arr[0], "/") {
_, alias := path.Split(arr[1])
return arr[0][1:], alias, nil
}
return arr[0], arr[1], 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
}

View file

@ -1,58 +0,0 @@
package main
import (
"fmt"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
"os"
)
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 {
args := c.Args()
if len(args) < 1 {
return errors.Errorf("you must provide at least one container name or id")
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not get container server")
}
defer server.Shutdown()
if err := server.Update(); err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
var lastError error
for _, container := range c.Args() {
cid, err := server.ContainerPause(container)
if err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "failed to pause container %v", container)
} else {
fmt.Println(cid)
}
}
return lastError
}

View file

@ -1,665 +0,0 @@
package main
import (
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/docker/go-units"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/fields"
"github.com/kubernetes-incubator/cri-o/cmd/kpod/formats"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/kubernetes-incubator/cri-o/oci"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
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 libkpod.ContainerData,
// 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: "",
}
)
func psCmd(c *cli.Context) error {
if err := validateFlags(c, psFlags); err != nil {
return err
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "error creating server")
}
if err := server.Update(); err != nil {
return errors.Wrapf(err, "error updating list of containers")
}
if len(c.Args()) > 0 {
return errors.Errorf("too many arguments, ps takes no arguments")
}
format := genPsFormat(c.Bool("quiet"), c.Bool("size"), c.Bool("namespace"))
if c.IsSet("format") {
format = c.String("format")
}
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"),
}
// all, latest, and last are mutually exclusive. Only one flag can be used at a time
exclusiveOpts := 0
if opts.last >= 0 {
exclusiveOpts++
}
if opts.latest {
exclusiveOpts++
}
if opts.all {
exclusiveOpts++
}
if exclusiveOpts > 1 {
return errors.Errorf("Last, latest and all are mutually exclusive")
}
containers, err := server.ListContainers()
if err != nil {
return errors.Wrapf(err, "error getting containers from server")
}
var params *FilterParamsPS
if opts.filter != "" {
params, err = parseFilter(opts.filter, containers)
if err != nil {
return errors.Wrapf(err, "error parsing filter")
}
} else {
params = nil
}
containerList := getContainersMatchingFilter(containers, params, server)
return generatePsOutput(containerList, server, opts)
}
// generate the template based on conditions given
func genPsFormat(quiet, size, namespace bool) (format string) {
if quiet {
return formats.IDString
}
if namespace {
format = "table {{.ID}}\t{{.Names}}\t{{.PID}}\t{{.Cgroup}}\t{{.IPC}}\t{{.MNT}}\t{{.NET}}\t{{.PIDNS}}\t{{.User}}\t{{.UTS}}\t"
return
}
format = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.CreatedAt}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}\t"
if size {
format += "{{.Size}}\t"
}
return
}
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
}
// getContainers gets the containers that match the flags given
func getContainers(containers []*libkpod.ContainerData, opts psOptions) []*libkpod.ContainerData {
var containersOutput []*libkpod.ContainerData
if opts.last >= 0 && opts.last < len(containers) {
for i := 0; i < opts.last; i++ {
containersOutput = append(containersOutput, containers[i])
}
return containersOutput
}
if opts.latest {
return []*libkpod.ContainerData{containers[0]}
}
if opts.all || opts.last >= len(containers) {
return containers
}
for _, ctr := range containers {
if ctr.State.Status == oci.ContainerStateRunning {
containersOutput = append(containersOutput, ctr)
}
}
return containersOutput
}
// getTemplateOutput returns the modified container information
func getTemplateOutput(containers []*libkpod.ContainerData, opts psOptions) (psOutput []psTemplateParams) {
var status string
for _, ctr := range containers {
ctrID := ctr.ID
runningFor := units.HumanDuration(time.Since(ctr.State.Created))
createdAt := runningFor + " ago"
command := getStrFromSquareBrackets(ctr.ImageCreatedBy)
imageName := ctr.FromImage
mounts := getMounts(ctr.Mounts, opts.noTrunc)
ports := getPorts(ctr.Config.ExposedPorts)
size := units.HumanSize(float64(ctr.SizeRootFs))
labels := getLabels(ctr.Labels)
ns := getNamespaces(ctr.State.Pid)
switch ctr.State.Status {
case oci.ContainerStateStopped:
status = "Exited (" + strconv.FormatInt(int64(ctr.State.ExitCode), 10) + ") " + runningFor + " ago"
case oci.ContainerStateRunning:
status = "Up " + runningFor + " ago"
case oci.ContainerStatePaused:
status = "Paused"
default:
status = "Created"
}
if !opts.noTrunc {
ctrID = ctr.ID[:idTruncLength]
imageName = getImageName(ctr.FromImage)
}
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: ctr.State.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
}
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 []*libkpod.ContainerData, nSpace bool) (psOutput []psJSONParams) {
var ns *namespace
for _, ctr := range containers {
if nSpace {
ns = getNamespaces(ctr.State.Pid)
}
params := psJSONParams{
ID: ctr.ID,
Image: ctr.FromImage,
ImageID: ctr.FromImageID,
Command: getStrFromSquareBrackets(ctr.ImageCreatedBy),
CreatedAt: ctr.State.Created,
RunningFor: time.Since(ctr.State.Created),
Status: ctr.State.Status,
Ports: ctr.Config.ExposedPorts,
Size: ctr.SizeRootFs,
Names: ctr.Name,
Labels: ctr.Labels,
Mounts: ctr.Mounts,
ContainerRunning: ctr.State.Status == oci.ContainerStateRunning,
Namespaces: ns,
}
psOutput = append(psOutput, params)
}
return
}
func generatePsOutput(containers []*libkpod.ContainerData, server *libkpod.ContainerServer, opts psOptions) error {
containersOutput := getContainers(containers, opts)
// In the case of JSON, we want to continue so we at least pass
// {} --valid JSON-- to the consumer
if len(containersOutput) == 0 && opts.format != formats.JSONString {
return nil
}
var out formats.Writer
switch opts.format {
case formats.JSONString:
psOutput := getJSONOutput(containersOutput, opts.namespace)
out = formats.JSONStructArray{Output: psToGeneric([]psTemplateParams{}, psOutput)}
default:
psOutput := getTemplateOutput(containersOutput, opts)
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, ",")
}
// getImageName shortens the image name
func getImageName(img string) string {
arr := strings.Split(img, "/")
if arr[0] == "docker.io" && arr[1] == "library" {
img = strings.Join(arr[2:], "/")
} else if arr[0] == "docker.io" {
img = strings.Join(arr[1:], "/")
}
return img
}
// getLabels converts the labels to a string of the form "key=value, key2=value2"
func getLabels(labels fields.Set) 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, ",")
}
// FilterParamsPS contains the filter options for ps
type FilterParamsPS struct {
id string
label string
name string
exited int32
status string
ancestor string
before time.Time
since time.Time
volume string
}
// parseFilter takes a filter string and a list of containers and filters it
func parseFilter(filter string, containers []*oci.Container) (*FilterParamsPS, error) {
params := new(FilterParamsPS)
allFilters := strings.Split(filter, ",")
for _, param := range allFilters {
pair := strings.SplitN(param, "=", 2)
switch strings.TrimSpace(pair[0]) {
case "id":
params.id = pair[1]
case "label":
params.label = pair[1]
case "name":
params.name = pair[1]
case "exited":
exitedCode, err := strconv.ParseInt(pair[1], 10, 32)
if err != nil {
return nil, errors.Errorf("exited code out of range %q", pair[1])
}
params.exited = int32(exitedCode)
case "status":
params.status = pair[1]
case "ancestor":
params.ancestor = pair[1]
case "before":
if ctr, err := findContainer(containers, pair[1]); err == nil {
params.before = ctr.CreatedAt()
} else {
return nil, errors.Wrapf(err, "no such container %q", pair[1])
}
case "since":
if ctr, err := findContainer(containers, pair[1]); err == nil {
params.before = ctr.CreatedAt()
} else {
return nil, errors.Wrapf(err, "no such container %q", pair[1])
}
case "volume":
params.volume = pair[1]
default:
return nil, errors.Errorf("invalid filter %q", pair[0])
}
}
return params, nil
}
// findContainer finds a container with a specific name or id from a list of containers
func findContainer(containers []*oci.Container, ref string) (*oci.Container, error) {
for _, ctr := range containers {
if strings.HasPrefix(ctr.ID(), ref) || ctr.Name() == ref {
return ctr, nil
}
}
return nil, errors.Errorf("could not find container")
}
// matchesFilter checks if a container matches all the filter parameters
func matchesFilter(ctrData *libkpod.ContainerData, params *FilterParamsPS) bool {
if params == nil {
return true
}
if params.id != "" && !matchesID(ctrData, params.id) {
return false
}
if params.name != "" && !matchesName(ctrData, params.name) {
return false
}
if !params.before.IsZero() && !matchesBeforeContainer(ctrData, params.before) {
return false
}
if !params.since.IsZero() && !matchesSinceContainer(ctrData, params.since) {
return false
}
if params.exited > 0 && !matchesExited(ctrData, params.exited) {
return false
}
if params.status != "" && !matchesStatus(ctrData, params.status) {
return false
}
if params.ancestor != "" && !matchesAncestor(ctrData, params.ancestor) {
return false
}
if params.label != "" && !matchesLabel(ctrData, params.label) {
return false
}
if params.volume != "" && !matchesVolume(ctrData, params.volume) {
return false
}
return true
}
// GetContainersMatchingFilter returns a slice of all the containers that match the provided filter parameters
func getContainersMatchingFilter(containers []*oci.Container, filter *FilterParamsPS, server *libkpod.ContainerServer) []*libkpod.ContainerData {
var filteredCtrs []*libkpod.ContainerData
for _, ctr := range containers {
ctrData, err := server.GetContainerData(ctr.ID(), true)
if err != nil {
logrus.Warn("unable to get container data for matched container")
}
if filter == nil || matchesFilter(ctrData, filter) {
filteredCtrs = append(filteredCtrs, ctrData)
}
}
return filteredCtrs
}
// matchesID returns true if the id's match
func matchesID(ctrData *libkpod.ContainerData, id string) bool {
return strings.HasPrefix(ctrData.ID, id)
}
// matchesBeforeContainer returns true if the container was created before the filter image
func matchesBeforeContainer(ctrData *libkpod.ContainerData, beforeTime time.Time) bool {
return ctrData.State.Created.Before(beforeTime)
}
// matchesSincecontainer returns true if the container was created since the filter image
func matchesSinceContainer(ctrData *libkpod.ContainerData, sinceTime time.Time) bool {
return ctrData.State.Created.After(sinceTime)
}
// matchesLabel returns true if the container label matches that of the filter label
func matchesLabel(ctrData *libkpod.ContainerData, label string) bool {
pair := strings.SplitN(label, "=", 2)
if val, ok := ctrData.Labels[pair[0]]; ok {
if len(pair) == 2 && val == pair[1] {
return true
}
if len(pair) == 1 {
return true
}
return false
}
return false
}
// matchesName returns true if the names are identical
func matchesName(ctrData *libkpod.ContainerData, name string) bool {
return ctrData.Name == name
}
// matchesExited returns true if the exit codes are identical
func matchesExited(ctrData *libkpod.ContainerData, exited int32) bool {
return ctrData.State.ExitCode == exited
}
// matchesStatus returns true if the container status matches that of filter status
func matchesStatus(ctrData *libkpod.ContainerData, status string) bool {
return ctrData.State.Status == status
}
// matchesAncestor returns true if filter ancestor is in container image name
func matchesAncestor(ctrData *libkpod.ContainerData, ancestor string) bool {
return strings.Contains(ctrData.FromImage, ancestor)
}
// matchesVolue returns true if the volume mounted or path to volue of the container matches that of filter volume
func matchesVolume(ctrData *libkpod.ContainerData, volume string) bool {
for _, vol := range ctrData.Mounts {
if strings.Contains(vol.Source, volume) {
return true
}
}
return false
}

View file

@ -1,118 +0,0 @@
package main
import (
"fmt"
"io"
"os"
"golang.org/x/crypto/ssh/terminal"
"github.com/containers/image/types"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/kubernetes-incubator/cri-o/libpod/common"
"github.com/pkg/errors"
"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,
}
return runtime.PullImage(image, options)
}

View file

@ -1,132 +0,0 @@
package main
import (
"fmt"
"io"
"os"
"github.com/containers/image/types"
"github.com/containers/storage/pkg/archive"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/kubernetes-incubator/cri-o/libpod/common"
"github.com/pkg/errors"
"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.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 := c.Args().Get(0)
destName := c.Args().Get(1)
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
}
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,
}
return runtime.PushImage(srcName, destName, options)
}

View file

@ -1,49 +0,0 @@
package main
import (
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var (
renameDescription = "Rename a container. Container may be created, running, paused, or stopped"
renameFlags = []cli.Flag{}
renameCommand = cli.Command{
Name: "rename",
Usage: "rename a container",
Description: renameDescription,
Action: renameCmd,
ArgsUsage: "CONTAINER NEW-NAME",
Flags: renameFlags,
}
)
func renameCmd(c *cli.Context) error {
if len(c.Args()) != 2 {
return errors.Errorf("Rename requires a src container name/ID and a dest container name")
}
if err := validateFlags(c, renameFlags); err != nil {
return err
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "Could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not get container server")
}
defer server.Shutdown()
err = server.Update()
if err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
err = server.ContainerRename(c.Args().Get(0), c.Args().Get(1))
if err != nil {
return errors.Wrapf(err, "could not rename container")
}
return nil
}

View file

@ -1,69 +0,0 @@
package main
import (
"fmt"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
"golang.org/x/net/context"
)
var (
rmFlags = []cli.Flag{
cli.BoolFlag{
Name: "force, f",
Usage: "Force removal of a running container. The default is false",
},
}
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: "",
}
)
// saveCmd saves the image to either docker-archive or oci
func rmCmd(c *cli.Context) error {
args := c.Args()
if len(args) == 0 {
return errors.Errorf("specify one or more containers to remove")
}
if err := validateFlags(c, rmFlags); err != nil {
return err
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not get container server")
}
defer server.Shutdown()
err = server.Update()
if err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
force := c.Bool("force")
for _, container := range c.Args() {
id, err2 := server.Remove(context.Background(), container, force)
if err2 != nil {
if err == nil {
err = err2
} else {
err = errors.Wrapf(err, "%v. Stop the container before attempting removal or use -f\n", err2)
}
} else {
fmt.Println(id)
}
}
return err
}

View file

@ -1,56 +0,0 @@
package main
import (
"fmt"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var (
rmiDescription = "removes one or more locally stored images."
rmiFlags = []cli.Flag{
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,
}
)
func rmiCmd(c *cli.Context) error {
if err := validateFlags(c, rmiFlags); 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("image name or ID must be specified")
}
for _, arg := range args {
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
}

View file

@ -1,104 +0,0 @@
package main
import (
"fmt"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/pkg/errors"
"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...]]",
}
func runCmd(c *cli.Context) error {
if err := validateFlags(c, createFlags); err != nil {
return err
}
runtime, err := getRuntime(c)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
createConfig, err := parseCreateOpts(c, runtime)
if err != nil {
return err
}
createImage := runtime.NewImage(createConfig.image)
if !createImage.HasImageLocal() {
// 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
}
defer runtime.Shutdown(false)
logrus.Debug("spec is ", runtimeSpec)
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(c)
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))
ctr, err := runtime.NewContainer(runtimeSpec, options...)
if err != nil {
return err
}
logrus.Debug("new container created ", ctr.ID())
if err := ctr.Create(); err != nil {
return err
}
logrus.Debug("container storage created for %q", ctr.ID())
if c.String("cidfile") != "" {
libpod.WriteFile(ctr.ID(), c.String("cidfile"))
return nil
}
// Start the container
if err := ctr.Start(); err != nil {
return errors.Wrapf(err, "unable to start container %q", ctr.ID())
}
logrus.Debug("started container ", ctr.ID())
if createConfig.tty {
// Attach to the running container
logrus.Debug("trying to attach to the container %s", ctr.ID())
if err := ctr.Attach(false, c.String("detach-keys")); err != nil {
return errors.Wrapf(err, "unable to attach to container %s", ctr.ID())
}
} else {
fmt.Printf("%s\n", ctr.ID())
}
return nil
}

View file

@ -1,98 +0,0 @@
package main
import (
"io"
"os"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
var (
saveFlags = []cli.Flag{
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",
},
}
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)
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 string
switch c.String("format") {
case libpod.OCIArchive:
dst = libpod.OCIArchive + ":" + output
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,
}
// only one image is supported for now
// future pull requests will fix this
for _, image := range args {
dest := dst + ":" + image
if err := runtime.PushImage(image, dest, saveOpts); err != nil {
return errors.Wrapf(err, "unable to save %q", image)
}
}
return nil
}

View file

@ -1,490 +0,0 @@
package main
import (
"fmt"
"strings"
"github.com/kubernetes-incubator/cri-o/libpod"
ann "github.com/kubernetes-incubator/cri-o/pkg/annotations"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/sys/unix"
)
// Parses information needed to create a container into an OCI runtime spec
func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
spec := config.GetDefaultLinuxSpec()
spec.Process.Cwd = config.workDir
spec.Process.Args = config.command
spec.Process.Terminal = config.tty
// User and Group must go together
spec.Process.User.UID = config.user
spec.Process.User.GID = config.group
spec.Process.User.AdditionalGids = config.groupAdd
spec.Process.Env = config.env
//TODO
// Need examples of capacity additions so I can load that properly
spec.Root.Readonly = config.readOnlyRootfs
spec.Hostname = config.hostname
// BIND MOUNTS
spec.Mounts = append(spec.Mounts, config.GetVolumeMounts()...)
// TMPFS MOUNTS
spec.Mounts = append(spec.Mounts, config.GetTmpfsMounts()...)
// RESOURCES - MEMORY
spec.Linux.Sysctl = config.sysctl
if config.resources.memory != 0 {
spec.Linux.Resources.Memory.Limit = &config.resources.memory
}
if config.resources.memoryReservation != 0 {
spec.Linux.Resources.Memory.Reservation = &config.resources.memoryReservation
}
if config.resources.memorySwap != 0 {
spec.Linux.Resources.Memory.Swap = &config.resources.memorySwap
}
if config.resources.kernelMemory != 0 {
spec.Linux.Resources.Memory.Kernel = &config.resources.kernelMemory
}
if config.resources.memorySwapiness != 0 {
spec.Linux.Resources.Memory.Swappiness = &config.resources.memorySwapiness
}
if config.resources.disableOomKiller {
spec.Linux.Resources.Memory.DisableOOMKiller = &config.resources.disableOomKiller
}
// RESOURCES - CPU
if config.resources.cpuShares != 0 {
spec.Linux.Resources.CPU.Shares = &config.resources.cpuShares
}
if config.resources.cpuQuota != 0 {
spec.Linux.Resources.CPU.Quota = &config.resources.cpuQuota
}
if config.resources.cpuPeriod != 0 {
spec.Linux.Resources.CPU.Period = &config.resources.cpuPeriod
}
if config.resources.cpuRtRuntime != 0 {
spec.Linux.Resources.CPU.RealtimeRuntime = &config.resources.cpuRtRuntime
}
if config.resources.cpuRtPeriod != 0 {
spec.Linux.Resources.CPU.RealtimePeriod = &config.resources.cpuRtPeriod
}
if config.resources.cpus != "" {
spec.Linux.Resources.CPU.Cpus = config.resources.cpus
}
if config.resources.cpusetMems != "" {
spec.Linux.Resources.CPU.Mems = config.resources.cpusetMems
}
// RESOURCES - PIDS
if config.resources.pidsLimit != 0 {
spec.Linux.Resources.Pids.Limit = config.resources.pidsLimit
}
/*
Capabilities: &spec.LinuxCapabilities{
// Rlimits []PosixRlimit // Where does this come from
// Type string
// Hard uint64
// Limit uint64
// NoNewPrivileges bool // No user input for this
// ApparmorProfile string // No user input for this
OOMScoreAdj: &config.resources.oomScoreAdj,
// Selinuxlabel
},
Hooks: &spec.Hooks{},
//Annotations
Resources: &spec.LinuxResources{
Devices: config.GetDefaultDevices(),
BlockIO: &blkio,
//HugepageLimits:
Network: &spec.LinuxNetwork{
// ClassID *uint32
// Priorites []LinuxInterfacePriority
},
},
//CgroupsPath:
//Namespaces: []LinuxNamespace
//Devices
Seccomp: &spec.LinuxSeccomp{
// DefaultAction:
// Architectures
// Syscalls:
},
// RootfsPropagation
// MaskedPaths
// ReadonlyPaths:
// MountLabel
// IntelRdt
},
}
*/
return &spec, nil
}
func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) {
bio := spec.LinuxBlockIO{}
bio.Weight = &c.resources.blkioWeight
if len(c.resources.blkioDevice) > 0 {
var lwds []spec.LinuxWeightDevice
for _, i := range c.resources.blkioDevice {
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
}
func (c *createConfig) GetDefaultMounts() []spec.Mount {
// Default to 64K default per man page
shmSize := "65536k"
if c.resources.shmSize != "" {
shmSize = c.resources.shmSize
}
return []spec.Mount{
{
Destination: "/proc",
Type: "proc",
Source: "proc",
Options: []string{"nosuid", "noexec", "nodev"},
},
{
Destination: "/dev",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
{
Destination: "/dev/pts",
Type: "devpts",
Source: "devpts",
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
},
{
Destination: "/sys",
Type: "sysfs",
Source: "sysfs",
Options: []string{"nosuid", "noexec", "nodev", "ro"},
},
{
Destination: "/sys/fs/cgroup",
Type: "cgroup",
Source: "cgroup",
Options: []string{"ro", "nosuid", "noexec", "nodev"},
},
{
Destination: "/dev/mqueue",
Type: "mqueue",
Source: "mqueue",
Options: []string{"nosuid", "noexec", "nodev"},
},
{
Destination: "/dev/shm",
Type: "tmpfs",
Source: "shm",
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", fmt.Sprintf("size=%s", shmSize)},
},
}
}
func iPtr(i int64) *int64 { return &i }
func (c *createConfig) GetDefaultDevices() []spec.LinuxDeviceCgroup {
return []spec.LinuxDeviceCgroup{
{
Allow: false,
Access: "rwm",
},
{
Allow: true,
Type: "c",
Major: iPtr(1),
Minor: iPtr(5),
Access: "rwm",
},
{
Allow: true,
Type: "c",
Major: iPtr(1),
Minor: iPtr(3),
Access: "rwm",
},
{
Allow: true,
Type: "c",
Major: iPtr(1),
Minor: iPtr(9),
Access: "rwm",
},
{
Allow: true,
Type: "c",
Major: iPtr(1),
Minor: iPtr(8),
Access: "rwm",
},
{
Allow: true,
Type: "c",
Major: iPtr(5),
Minor: iPtr(0),
Access: "rwm",
},
{
Allow: true,
Type: "c",
Major: iPtr(5),
Minor: iPtr(1),
Access: "rwm",
},
{
Allow: false,
Type: "c",
Major: iPtr(10),
Minor: iPtr(229),
Access: "rwm",
},
}
}
func defaultCapabilities() []string {
return []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
}
}
func (c *createConfig) GetDefaultLinuxSpec() spec.Spec {
s := spec.Spec{
Version: spec.Version,
Root: &spec.Root{},
}
s.Annotations = c.GetAnnotations()
s.Mounts = c.GetDefaultMounts()
s.Process = &spec.Process{
Capabilities: &spec.LinuxCapabilities{
Bounding: defaultCapabilities(),
Permitted: defaultCapabilities(),
Inheritable: defaultCapabilities(),
Effective: defaultCapabilities(),
},
}
s.Linux = &spec.Linux{
MaskedPaths: []string{
"/proc/kcore",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
},
ReadonlyPaths: []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
},
Namespaces: []spec.LinuxNamespace{
{Type: "mount"},
{Type: "network"},
{Type: "uts"},
{Type: "pid"},
{Type: "ipc"},
},
Devices: []spec.LinuxDevice{},
Resources: &spec.LinuxResources{
Devices: c.GetDefaultDevices(),
},
}
return s
}
// 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
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 {
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], ",")
}
// always add rbind bc mount ignores the bind filesystem when mounting
options = append(options, "rbind")
m = append(m, spec.Mount{
Destination: spliti[1],
Type: string(TypeBind),
Source: spliti[0],
Options: options,
})
}
return m
}
//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(cli *cli.Context) ([]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.Debug("appending name %s", c.name)
options = append(options, libpod.WithName(c.name))
}
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
}

View file

@ -1,245 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"text/template"
"time"
"github.com/docker/go-units"
tm "github.com/buger/goterm"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/kubernetes-incubator/cri-o/oci"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var printf func(format string, a ...interface{}) (n int, err error)
var println func(a ...interface{}) (n int, err error)
type statsOutputParams struct {
Container string
ID string
CPUPerc string
MemUsage string
MemPerc string
NetIO string
BlockIO string
PIDs uint64
}
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: "json",
Usage: "output container statistics in json format",
},
}
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
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not read config")
}
containerServer, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not create container server")
}
defer containerServer.Shutdown()
err = containerServer.Update()
if err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
times := -1
if c.Bool("no-stream") {
times = 1
}
statsChan := make(chan []*libkpod.ContainerStats)
// iterate over the channel until it is closed
go func() {
// print using goterm
printf = tm.Printf
println = tm.Println
for stats := range statsChan {
// Continually refresh statistics
tm.Clear()
tm.MoveCursor(1, 1)
outputStats(stats, c.String("format"), c.Bool("json"))
tm.Flush()
time.Sleep(time.Second)
}
}()
return getStats(containerServer, c.Args(), c.Bool("all"), statsChan, times)
}
func getStats(server *libkpod.ContainerServer, args []string, all bool, statsChan chan []*libkpod.ContainerStats, times int) error {
ctrs, err := server.ListContainers(isRunning, ctrInList(args))
if err != nil {
return err
}
containerStats := map[string]*libkpod.ContainerStats{}
for _, ctr := range ctrs {
initialStats, err := server.GetContainerStats(ctr, &libkpod.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 := []*libkpod.ContainerStats{}
for _, ctr := range ctrs {
id := ctr.ID()
if _, ok := containerStats[ctr.ID()]; !ok {
initialStats, err := server.GetContainerStats(ctr, &libkpod.ContainerStats{})
if err != nil {
return err
}
containerStats[id] = initialStats
}
stats, err := server.GetContainerStats(ctr, containerStats[id])
if err != nil {
return err
}
// replace the previous measurement with the current one
containerStats[id] = stats
reportStats = append(reportStats, stats)
}
statsChan <- reportStats
err := server.Update()
if err != nil {
return err
}
ctrs, err = server.ListContainers(isRunning, ctrInList(args))
if err != nil {
return err
}
}
return nil
}
func outputStats(stats []*libkpod.ContainerStats, format string, json bool) error {
if format == "" {
outputStatsHeader()
}
if json {
return outputStatsAsJSON(stats)
}
var err error
for _, s := range stats {
if format == "" {
outputStatsUsingFormatString(s)
} else {
params := getStatsOutputParams(s)
err2 := outputStatsUsingTemplate(format, params)
if err2 != nil {
err = errors.Wrapf(err, err2.Error())
}
}
}
return err
}
func outputStatsHeader() {
printf("%-64s %-16s %-32s %-16s %-24s %-24s %s\n", "CONTAINER", "CPU %", "MEM USAGE / MEM LIMIT", "MEM %", "NET I/O", "BLOCK I/O", "PIDS")
}
func outputStatsUsingFormatString(stats *libkpod.ContainerStats) {
printf("%-64s %-16s %-32s %-16s %-24s %-24s %d\n", stats.Container, floatToPercentString(stats.CPU), combineHumanValues(stats.MemUsage, stats.MemLimit), floatToPercentString(stats.MemPerc), combineHumanValues(stats.NetInput, stats.NetOutput), combineHumanValues(stats.BlockInput, stats.BlockOutput), stats.PIDs)
}
func combineHumanValues(a, b uint64) string {
return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
}
func floatToPercentString(f float64) string {
return fmt.Sprintf("%.2f %s", f, "%")
}
func getStatsOutputParams(stats *libkpod.ContainerStats) statsOutputParams {
return statsOutputParams{
Container: stats.Container,
ID: stats.Container,
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,
}
}
func outputStatsUsingTemplate(format string, params statsOutputParams) error {
tmpl, err := template.New("stats").Parse(format)
if err != nil {
return errors.Wrapf(err, "template parsing error")
}
err = tmpl.Execute(os.Stdout, params)
if err != nil {
return err
}
println()
return nil
}
func outputStatsAsJSON(stats []*libkpod.ContainerStats) error {
s, err := json.Marshal(stats)
if err != nil {
return err
}
println(s)
return nil
}
func isRunning(ctr *oci.Container) bool {
return ctr.State().Status == "running"
}
func ctrInList(idsOrNames []string) func(ctr *oci.Container) bool {
if len(idsOrNames) == 0 {
return func(*oci.Container) bool { return true }
}
return func(ctr *oci.Container) bool {
for _, idOrName := range idsOrNames {
if strings.HasPrefix(ctr.ID(), idOrName) || strings.HasSuffix(ctr.Name(), idOrName) {
return true
}
}
return false
}
}

View file

@ -1,77 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
"golang.org/x/net/context"
)
var (
defaultTimeout int64 = 10
stopFlags = []cli.Flag{
cli.Int64Flag{
Name: "timeout, t",
Usage: "Seconds to wait for stop before killing the container",
Value: defaultTimeout,
},
}
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 len(args) < 1 {
return errors.Errorf("you must provide at least one container name or id")
}
if err := validateFlags(c, stopFlags); err != nil {
return err
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not get container server")
}
defer server.Shutdown()
err = server.Update()
if err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
var lastError error
for _, container := range c.Args() {
cid, err := server.ContainerStop(context.Background(), container, stopTimeout)
if err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "failed to stop container %v", container)
} else {
fmt.Println(cid)
}
}
return lastError
}

View file

@ -1,77 +0,0 @@
package main
import (
"github.com/containers/image/docker/reference"
"github.com/containers/storage"
"github.com/kubernetes-incubator/cri-o/libpod"
"github.com/pkg/errors"
"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
}

View file

@ -1,41 +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 {
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")
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "Could not get config")
}
store, err := getStore(config)
if err != nil {
return err
}
err = store.Unmount(args[0])
if err != nil {
return errors.Wrapf(err, "error unmounting container %q", args[0])
}
return nil
}

View file

@ -1,58 +0,0 @@
package main
import (
"fmt"
"github.com/kubernetes-incubator/cri-o/libkpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
"os"
)
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 {
args := c.Args()
if len(args) < 1 {
return errors.Errorf("you must provide at least one container name or id")
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not get container server")
}
defer server.Shutdown()
if err := server.Update(); err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
var lastError error
for _, container := range c.Args() {
cid, err := server.ContainerUnpause(container)
if err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "failed to unpause container %v", container)
} else {
fmt.Println(cid)
}
}
return lastError
}

View file

@ -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))
}

View file

@ -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,
}

View file

@ -1,62 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/kubernetes-incubator/cri-o/libkpod"
"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")
}
config, err := getConfig(c)
if err != nil {
return errors.Wrapf(err, "could not get config")
}
server, err := libkpod.New(config)
if err != nil {
return errors.Wrapf(err, "could not get container server")
}
defer server.Shutdown()
err = server.Update()
if err != nil {
return errors.Wrapf(err, "could not update list of containers")
}
var lastError error
for _, container := range c.Args() {
returnCode, err := server.ContainerWait(container)
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
}