Merge pull request #1111 from mheon/remove_kpod
Remove kpod code after repository move
This commit is contained in:
commit
c9b3d3df28
125 changed files with 5 additions and 36015 deletions
|
@ -1 +0,0 @@
|
|||
0.1
|
15
Makefile
15
Makefile
|
@ -23,8 +23,6 @@ COMMIT_NO := $(shell git rev-parse HEAD 2> /dev/null || true)
|
|||
GIT_COMMIT := $(if $(shell git status --porcelain --untracked-files=no),"${COMMIT_NO}-dirty","${COMMIT_NO}")
|
||||
BUILD_INFO := $(shell date +%s)
|
||||
|
||||
KPOD_VERSION := ${shell cat ./KPOD_VERSION}
|
||||
|
||||
# If GOPATH not specified, use one in the local directory
|
||||
ifeq ($(GOPATH),)
|
||||
export GOPATH := $(CURDIR)/_output
|
||||
|
@ -37,9 +35,7 @@ GOPKGBASEDIR := $(shell dirname "$(GOPKGDIR)")
|
|||
VPATH := $(VPATH):$(GOPATH)
|
||||
SHRINKFLAGS := -s -w
|
||||
BASE_LDFLAGS := ${SHRINKFLAGS} -X main.gitCommit=${GIT_COMMIT} -X main.buildInfo=${BUILD_INFO}
|
||||
KPOD_LDFLAGS := -X main.kpodVersion=${KPOD_VERSION}
|
||||
LDFLAGS := -ldflags '${BASE_LDFLAGS}'
|
||||
LDFLAGS_KPOD := -ldflags '${BASE_LDFLAGS} ${KPOD_LDFLAGS}'
|
||||
|
||||
all: binaries crio.conf docs
|
||||
|
||||
|
@ -49,7 +45,7 @@ help:
|
|||
@echo "Usage: make <target>"
|
||||
@echo
|
||||
@echo " * 'install' - Install binaries to system locations"
|
||||
@echo " * 'binaries' - Build crio, conmon, pause, crioctl and kpod"
|
||||
@echo " * 'binaries' - Build crio, conmon, pause, and crioctl"
|
||||
@echo " * 'integration' - Execute integration tests"
|
||||
@echo " * 'clean' - Clean artifacts"
|
||||
@echo " * 'lint' - Execute the source code linter"
|
||||
|
@ -90,9 +86,6 @@ crio: .gopathok $(shell hack/find-godeps.sh $(GOPKGDIR) cmd/crio $(PROJECT))
|
|||
crioctl: .gopathok $(shell hack/find-godeps.sh $(GOPKGDIR) cmd/crioctl $(PROJECT))
|
||||
$(GO) build $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o bin/$@ $(PROJECT)/cmd/crioctl
|
||||
|
||||
kpod: .gopathok $(shell hack/find-godeps.sh $(GOPKGDIR) cmd/kpod $(PROJECT))
|
||||
$(GO) build $(LDFLAGS_KPOD) -tags "$(BUILDTAGS)" -o bin/$@ $(PROJECT)/cmd/kpod
|
||||
|
||||
crio.conf: crio
|
||||
./bin/crio --config="" config --default > crio.conf
|
||||
|
||||
|
@ -105,7 +98,7 @@ endif
|
|||
rm -fr test/testdata/redis-image
|
||||
find . -name \*~ -delete
|
||||
find . -name \#\* -delete
|
||||
rm -f bin/crioctl bin/crio bin/kpod
|
||||
rm -f bin/crioctl bin/crio
|
||||
make -C conmon clean
|
||||
make -C pause clean
|
||||
rm -f test/bin2img/bin2img
|
||||
|
@ -127,7 +120,7 @@ testunit:
|
|||
localintegration: clean binaries test-binaries
|
||||
./test/test_runner.sh ${TESTFLAGS}
|
||||
|
||||
binaries: crio conmon pause kpod crioctl
|
||||
binaries: crio conmon pause crioctl
|
||||
test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp
|
||||
|
||||
MANPAGES_MD := $(wildcard docs/*.md)
|
||||
|
@ -149,7 +142,6 @@ install: .gopathok install.bin install.man
|
|||
install.bin:
|
||||
install ${SELINUXOPT} -D -m 755 bin/crio $(BINDIR)/crio
|
||||
install ${SELINUXOPT} -D -m 755 bin/crioctl $(BINDIR)/crioctl
|
||||
install ${SELINUXOPT} -D -m 755 bin/kpod $(BINDIR)/kpod
|
||||
install ${SELINUXOPT} -D -m 755 bin/conmon $(LIBEXECDIR)/crio/conmon
|
||||
install ${SELINUXOPT} -D -m 755 bin/pause $(LIBEXECDIR)/crio/pause
|
||||
|
||||
|
@ -168,7 +160,6 @@ install.config:
|
|||
|
||||
install.completions:
|
||||
install ${SELINUXOPT} -d -m 755 ${BASHINSTALLDIR}
|
||||
install ${SELINUXOPT} -m 644 -D completions/bash/kpod ${BASHINSTALLDIR}
|
||||
|
||||
install.systemd:
|
||||
install ${SELINUXOPT} -D -m 644 contrib/systemd/crio.service $(PREFIX)/lib/systemd/system/crio.service
|
||||
|
|
34
README.md
34
README.md
|
@ -40,38 +40,8 @@ It is currently in active development in the Kubernetes community through the [d
|
|||
| Command | Description | Demo|
|
||||
| ---------------------------------------------------- | --------------------------------------------------------------------------|-----|
|
||||
| [crio(8)](/docs/crio.8.md) | OCI Kubernetes Container Runtime daemon ||
|
||||
| [kpod(1)](/docs/kpod.1.md) | Simple management tool for pods and images ||
|
||||
| [kpod-attach(1)](/docs/kpod-attach.1.md) | Instead of providing a `kpod attach` command, the man page `kpod-attach` describes how to use the `kpod logs` and `kpod exec` commands to achieve the same goals as `kpod attach`.||
|
||||
| [kpod-cp(1)](/docs/kpod-cp.1.md) | Instead of providing a `kpod cp` command, the man page `kpod-cp` describes how to use the `kpod mount` command to have even more flexibility and functionality.||
|
||||
| [kpod-create(1)](/docs/kpod-create.1.md) | Create a new container ||
|
||||
| [kpod-diff(1)](/docs/kpod-diff.1.md) | Inspect changes on a container or image's filesystem ||
|
||||
| [kpod-export(1)](/docs/kpod-export.1.md) | Export container's filesystem contents as a tar archive |[![...](/docs/play.png)](https://asciinema.org/a/913lBIRAg5hK8asyIhhkQVLtV)|
|
||||
| [kpod-history(1)](/docs/kpod-history.1.md) | Shows the history of an image |[![...](/docs/play.png)](https://asciinema.org/a/bCvUQJ6DkxInMELZdc5DinNSx)|
|
||||
| [kpod-images(1)](/docs/kpod-images.1.md) | List images in local storage |[![...](/docs/play.png)](https://asciinema.org/a/133649)|
|
||||
| [kpod-info(1)](/docs/kpod-info.1.md) | Display system information ||
|
||||
| [kpod-inspect(1)](/docs/kpod-inspect.1.md) | Display the configuration of a container or image |[![...](/docs/play.png)](https://asciinema.org/a/133418)|
|
||||
| [kpod-kill(1)](/docs/kpod-kill.1.md) | Kill the main process in one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/3jNos0A5yzO4hChu7ddKkUPw7)|
|
||||
| [kpod-load(1)](/docs/kpod-load.1.md) | Load an image from docker archive or oci |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)|
|
||||
| [kpod-login(1)](/docs/kpod-login.1.md) | Login to a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)|
|
||||
| [kpod-logout(1)](/docs/kpod-logout.1.md) | Logout of a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)|
|
||||
| [kpod-logs(1)](/docs/kpod-logs.1.md) | Display the logs of a container ||
|
||||
| [kpod-mount(1)](/docs/kpod-mount.1.md) | Mount a working container's root filesystem ||
|
||||
| [kpod-pause(1)](/docs/kpod-pause.1.md) | Pause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)|
|
||||
| [kpod-ps(1)](/docs/kpod-ps.1.md) | Prints out information about containers |[![...](/docs/play.png)](https://asciinema.org/a/bbT41kac6CwZ5giESmZLIaTLR)|
|
||||
| [kpod-pull(1)](/docs/kpod-pull.1.md) | Pull an image from a registry |[![...](/docs/play.png)](https://asciinema.org/a/lr4zfoynHJOUNu1KaXa1dwG2X)|
|
||||
| [kpod-push(1)](/docs/kpod-push.1.md) | Push an image to a specified destination |[![...](/docs/play.png)](https://asciinema.org/a/133276)|
|
||||
| [kpod-rename(1)](/docs/kpod-rename.1.md) | Rename a container ||
|
||||
| [kpod-rm(1)](/docs/kpod-rm.1.md) | Removes one or more containers |[![...](/docs/play.png)](https://asciinema.org/a/7EMk22WrfGtKWmgHJX9Nze1Qp)|
|
||||
| [kpod-rmi(1)](/docs/kpod-rmi.1.md) | Removes one or more images |[![...](/docs/play.png)](https://asciinema.org/a/133799)|
|
||||
| [kpod-run(1)](/docs/kpod-run.1.md) | Run a command in a new container ||
|
||||
| [kpod-save(1)](/docs/kpod-save.1.md) | Saves an image to an archive |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)|
|
||||
| [kpod-stats(1)](/docs/kpod-stats.1.md) | Display a live stream of one or more containers' resource usage statistics||
|
||||
| [kpod-stop(1)](/docs/kpod-stop.1.md) | Stops one or more running containers ||
|
||||
| [kpod-tag(1)](/docs/kpod-tag.1.md) | Add an additional name to a local image |[![...](/docs/play.png)](https://asciinema.org/a/133803)|
|
||||
| [kpod-umount(1)](/docs/kpod-umount.1.md) | Unmount a working container's root filesystem ||
|
||||
| [kpod-unpause(1)](/docs/kpod-unpause.1.md) | Unpause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)|
|
||||
| [kpod-version(1)](/docs/kpod-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)|
|
||||
| [kpod-wait(1)](/docs/kpod-wait.1.md) | Wait on one or more containers to stop and print their exit codes||
|
||||
|
||||
Note that kpod and its container management and debugging commands have moved to a separate repository, located [here](https://github.com/projectatomic/libpod).
|
||||
|
||||
## Configuration
|
||||
| File | Description |
|
||||
|
|
|
@ -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.
|
|
@ -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: "/",
|
||||
},
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
128
cmd/kpod/diff.go
128
cmd/kpod/diff.go
|
@ -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
|
||||
}
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
200
cmd/kpod/info.go
200
cmd/kpod/info.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
116
cmd/kpod/load.go
116
cmd/kpod/load.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
135
cmd/kpod/main.go
135
cmd/kpod/main.go
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
665
cmd/kpod/ps.go
665
cmd/kpod/ps.go
|
@ -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
|
||||
}
|
118
cmd/kpod/pull.go
118
cmd/kpod/pull.go
|
@ -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)
|
||||
|
||||
}
|
132
cmd/kpod/push.go
132
cmd/kpod/push.go
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
104
cmd/kpod/run.go
104
cmd/kpod/run.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
490
cmd/kpod/spec.go
490
cmd/kpod/spec.go
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
121
cmd/kpod/user.go
121
cmd/kpod/user.go
|
@ -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))
|
||||
}
|
|
@ -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,
|
||||
}
|
|
@ -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
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,31 +0,0 @@
|
|||
% kpod(1) kpod-attach - See the output of pid 1 of a container or enter the container
|
||||
% Dan Walsh
|
||||
# kpod-attach "1" "September 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-attach - Attach to a running container
|
||||
|
||||
## Description
|
||||
|
||||
We chose not to implement the `attach` feature in `kpod` even though the upstream Docker
|
||||
project has it. The upstream project has had lots of issues with attaching to running
|
||||
processes that we did not want to replicate. The `kpod exec` and `kpod log` commands
|
||||
offer you the same functionality far more dependably.
|
||||
|
||||
**Reasons to attach to the primary PID of a container:**
|
||||
|
||||
|
||||
1) Executing commands inside of the container
|
||||
|
||||
We recommend that you use `kpod exec` to execute a command within a container
|
||||
|
||||
`kpod exec CONTAINERID /bin/sh`
|
||||
|
||||
2) Viewing the output stream of the primary process in the container
|
||||
|
||||
We recommend that you use `kpod logs` to see the output from the container
|
||||
|
||||
`kpod logs CONTAINERID`
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-exec(1), kpod-logs(1)
|
|
@ -1,46 +0,0 @@
|
|||
% kpod(1) kpod-cp - Copy content between container's file system and the host
|
||||
% Dan Walsh
|
||||
# kpod-cp "1" "August 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-cp - Copy files/folders between a container and the local filesystem.
|
||||
|
||||
## Description
|
||||
We chose not to implement the `cp` feature in `kpod` even though the upstream Docker
|
||||
project has it. We have a much stronger capability. Using standard kpod-mount
|
||||
and kpod-umount, we can take advantage of the entire linux tool chain, rather
|
||||
then just cp.
|
||||
|
||||
If a user wants to copy contents out of a container or into a container, they
|
||||
can execute a few simple commands.
|
||||
|
||||
You can copy from the container's file system to the local machine or the
|
||||
reverse, from the local filesystem to the container.
|
||||
|
||||
If you want to copy the /etc/foobar directory out of a container and onto /tmp
|
||||
on the host, you could execute the following commands:
|
||||
|
||||
mnt=$(kpod mount CONTAINERID)
|
||||
cp -R ${mnt}/etc/foobar /tmp
|
||||
kpod umount CONTAINERID
|
||||
|
||||
If you want to untar a tar ball into a container, you can execute these commands:
|
||||
|
||||
mnt=$(kpod mount CONTAINERID)
|
||||
tar xf content.tgz -C ${mnt}
|
||||
kpod umount CONTAINERID
|
||||
|
||||
One last example, if you want to install a package into a container that
|
||||
does not have dnf installed, you could execute something like:
|
||||
|
||||
mnt=$(kpod mount CONTAINERID)
|
||||
dnf install --installroot=${mnt} httpd
|
||||
chroot ${mnt} rm -rf /var/log/dnf /var/cache/dnf
|
||||
kpod umount CONTAINERID
|
||||
|
||||
This shows that using `kpod mount` and `kpod umount` you can use all of the
|
||||
standard linux tools for moving files into and out of containers, not just
|
||||
the cp command.
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-mount(1), kpod-umount(1)
|
|
@ -1,594 +0,0 @@
|
|||
% kpod(1) kpod-create - Create a new container
|
||||
% Dan Walsh
|
||||
kpod-create - Create a new container
|
||||
|
||||
# SYNOPSIS
|
||||
**kpod create** [*options* [...]] IMAGE [COMMAND] [ARG...]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Creates a writeable container layer over the specified image and prepares it for
|
||||
running the specified command. The container ID is then printed to STDOUT. This
|
||||
is similar to **kpod run -d** except the container is never started. You can
|
||||
then use the **kpod start <container_id>** command to start the container at
|
||||
any point.
|
||||
|
||||
The initial status of the container created with **kpod create** is 'created'.
|
||||
|
||||
# OPTIONS
|
||||
**--add-host**=[]
|
||||
Add a custom host-to-IP mapping (host:ip)
|
||||
|
||||
Add a line to /etc/hosts. The format is hostname:ip. The **--add-host**
|
||||
option can be set multiple times.
|
||||
|
||||
**-a**, **--attach**=[]
|
||||
Attach to STDIN, STDOUT or STDERR.
|
||||
|
||||
In foreground mode (the default when **-d**
|
||||
is not specified), **kpod run** can start the process in the container
|
||||
and attach the console to the process's standard input, output, and standard
|
||||
error. It can even pretend to be a TTY (this is what most commandline
|
||||
executables expect) and pass along signals. The **-a** option can be set for
|
||||
each of stdin, stdout, and stderr.
|
||||
|
||||
**--blkio-weight**=*0*
|
||||
Block IO weight (relative weight) accepts a weight value between 10 and 1000.
|
||||
|
||||
**--blkio-weight-device**=[]
|
||||
Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`).
|
||||
|
||||
**--cap-add**=[]
|
||||
Add Linux capabilities
|
||||
|
||||
**--cap-drop**=[]
|
||||
Drop Linux capabilities
|
||||
|
||||
**--cgroup-parent**=""
|
||||
Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
|
||||
|
||||
**--cidfile**=""
|
||||
Write the container ID to the file
|
||||
|
||||
**--cpu-count**=*0*
|
||||
Limit the number of CPUs available for execution by the container.
|
||||
|
||||
On Windows Server containers, this is approximated as a percentage of total CPU usage.
|
||||
|
||||
On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
|
||||
|
||||
**--cpu-period**=*0*
|
||||
Limit the CPU CFS (Completely Fair Scheduler) period
|
||||
|
||||
Limit the container's CPU usage. This flag tell the kernel to restrict the container's CPU usage to the period you specify.
|
||||
|
||||
**--cpu-quota**=*0*
|
||||
Limit the CPU CFS (Completely Fair Scheduler) quota
|
||||
|
||||
Limit the container's CPU usage. By default, containers run with the full
|
||||
CPU resource. This flag tell the kernel to restrict the container's CPU usage
|
||||
to the quota you specify.
|
||||
|
||||
**--cpu-rt-period**=0
|
||||
Limit the CPU real-time period in microseconds
|
||||
|
||||
Limit the container's Real Time CPU usage. This flag tell the kernel to restrict the container's Real Time CPU usage to the period you specify.
|
||||
|
||||
**--cpu-rt-runtime**=0
|
||||
Limit the CPU real-time runtime in microseconds
|
||||
|
||||
Limit the containers Real Time CPU usage. This flag tells the kernel to limit the amount of time in a given CPU period Real Time tasks may consume. Ex:
|
||||
Period of 1,000,000us and Runtime of 950,000us means that this container could consume 95% of available CPU and leave the remaining 5% to normal priority tasks.
|
||||
|
||||
The sum of all runtimes across containers cannot exceed the amount allotted to the parent cgroup.
|
||||
|
||||
**--cpu-shares**=*0*
|
||||
CPU shares (relative weight)
|
||||
|
||||
By default, all containers get the same proportion of CPU cycles. This proportion
|
||||
can be modified by changing the container's CPU share weighting relative
|
||||
to the weighting of all other running containers.
|
||||
|
||||
To modify the proportion from the default of 1024, use the **--cpu-shares**
|
||||
flag to set the weighting to 2 or higher.
|
||||
|
||||
The proportion will only apply when CPU-intensive processes are running.
|
||||
When tasks in one container are idle, other containers can use the
|
||||
left-over CPU time. The actual amount of CPU time will vary depending on
|
||||
the number of containers running on the system.
|
||||
|
||||
For example, consider three containers, one has a cpu-share of 1024 and
|
||||
two others have a cpu-share setting of 512. When processes in all three
|
||||
containers attempt to use 100% of CPU, the first container would receive
|
||||
50% of the total CPU time. If you add a fourth container with a cpu-share
|
||||
of 1024, the first container only gets 33% of the CPU. The remaining containers
|
||||
receive 16.5%, 16.5% and 33% of the CPU.
|
||||
|
||||
On a multi-core system, the shares of CPU time are distributed over all CPU
|
||||
cores. Even if a container is limited to less than 100% of CPU time, it can
|
||||
use 100% of each individual CPU core.
|
||||
|
||||
For example, consider a system with more than three cores. If you start one
|
||||
container **{C0}** with **-c=512** running one process, and another container
|
||||
**{C1}** with **-c=1024** running two processes, this can result in the following
|
||||
division of CPU shares:
|
||||
|
||||
PID container CPU CPU share
|
||||
100 {C0} 0 100% of CPU0
|
||||
101 {C1} 1 100% of CPU1
|
||||
102 {C1} 2 100% of CPU2
|
||||
|
||||
**--cpus**=0.0
|
||||
Number of CPUs. The default is *0.0* which means no limit.
|
||||
|
||||
**--cpuset-cpus**=""
|
||||
CPUs in which to allow execution (0-3, 0,1)
|
||||
|
||||
**--cpuset-mems**=""
|
||||
Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
|
||||
|
||||
If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1`
|
||||
then processes in your container will only use memory from the first
|
||||
two memory nodes.
|
||||
|
||||
**-d**, **--detach**=*true*|*false*
|
||||
Detached mode: run the container in the background and print the new container ID. The default is *false*.
|
||||
|
||||
At any time you can run **kpod ps** in
|
||||
the other shell to view a list of the running containers. You can reattach to a
|
||||
detached container with **kpod attach**. If you choose to run a container in
|
||||
the detached mode, then you cannot use the **-rm** option.
|
||||
|
||||
When attached in the tty mode, you can detach from the container (and leave it
|
||||
running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`.
|
||||
You configure the key sequence using the **--detach-keys** option or a configuration file.
|
||||
See **config-json(5)** for documentation on using a configuration file.
|
||||
|
||||
**--detach-keys**=""
|
||||
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 `_`.
|
||||
|
||||
**--device**=[]
|
||||
Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
|
||||
|
||||
**--device-read-bps**=[]
|
||||
Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)
|
||||
|
||||
**--device-read-iops**=[]
|
||||
Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)
|
||||
|
||||
**--device-write-bps**=[]
|
||||
Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)
|
||||
|
||||
**--device-write-iops**=[]
|
||||
Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)
|
||||
|
||||
**--dns**=[]
|
||||
Set custom DNS servers
|
||||
|
||||
This option can be used to override the DNS
|
||||
configuration passed to the container. Typically this is necessary when the
|
||||
host DNS configuration is invalid for the container (e.g., 127.0.0.1). When this
|
||||
is the case the **--dns** flags is necessary for every run.
|
||||
|
||||
**--dns-option**=[]
|
||||
Set custom DNS options
|
||||
|
||||
**--dns-search**=[]
|
||||
Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)
|
||||
|
||||
**--entrypoint**=""
|
||||
Overwrite the default ENTRYPOINT of the image
|
||||
|
||||
This option allows you to overwrite the default entrypoint of the image.
|
||||
The ENTRYPOINT of an image is similar to a COMMAND
|
||||
because it specifies what executable to run when the container starts, but it is
|
||||
(purposely) more difficult to override. The ENTRYPOINT gives a container its
|
||||
default nature or behavior, so that when you set an ENTRYPOINT you can run the
|
||||
container as if it were that binary, complete with default options, and you can
|
||||
pass in more options via the COMMAND. But, sometimes an operator may want to run
|
||||
something else inside the container, so you can override the default ENTRYPOINT
|
||||
at runtime by using a **--entrypoint** and a string to specify the new
|
||||
ENTRYPOINT.
|
||||
|
||||
**-e**, **--env**=[]
|
||||
Set environment variables
|
||||
|
||||
This option allows you to specify arbitrary
|
||||
environment variables that are available for the process that will be launched
|
||||
inside of the container.
|
||||
|
||||
**--env-file**=[]
|
||||
Read in a line delimited file of environment variables
|
||||
|
||||
**--expose**=[]
|
||||
Expose a port, or a range of ports (e.g. --expose=3300-3310) to set up port redirection
|
||||
on the host system.
|
||||
|
||||
**--group-add**=[]
|
||||
Add additional groups to run as
|
||||
|
||||
**--hostname**=""
|
||||
Container host name
|
||||
|
||||
Sets the container host name that is available inside the container.
|
||||
|
||||
**--help**
|
||||
Print usage statement
|
||||
|
||||
**-i**, **--interactive**=*true*|*false*
|
||||
Keep STDIN open even if not attached. The default is *false*.
|
||||
|
||||
**--ip**=""
|
||||
Sets the container's interface IPv4 address (e.g. 172.23.0.9)
|
||||
|
||||
It can only be used in conjunction with **--network** for user-defined networks
|
||||
|
||||
**--ip6**=""
|
||||
Sets the container's interface IPv6 address (e.g. 2001:db8::1b99)
|
||||
|
||||
It can only be used in conjunction with **--network** for user-defined networks
|
||||
|
||||
**--ipc**=""
|
||||
Default is to create a private IPC namespace (POSIX SysV IPC) for the container
|
||||
'container:<name|id>': reuses another container shared memory, semaphores and message queues
|
||||
'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure.
|
||||
|
||||
**--kernel-memory**=""
|
||||
Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)
|
||||
|
||||
Constrains the kernel memory available to a container. If a limit of 0
|
||||
is specified (not using `--kernel-memory`), the container's kernel memory
|
||||
is not limited. If you specify a limit, it may be rounded up to a multiple
|
||||
of the operating system's page size and the value can be very large,
|
||||
millions of trillions.
|
||||
|
||||
**-l**, **--label**=[]
|
||||
Add metadata to a container (e.g., --label com.example.key=value)
|
||||
|
||||
**--label-file**=[]
|
||||
Read in a line delimited file of labels
|
||||
|
||||
**--link-local-ip**=[]
|
||||
Add one or more link-local IPv4/IPv6 addresses to the container's interface
|
||||
|
||||
**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*etwlogs*|*gcplogs*|*none*"
|
||||
Logging driver for the container. Default is defined by daemon `--log-driver` flag.
|
||||
**Warning**: the `kpod logs` command works only for the `json-file` and
|
||||
`journald` logging drivers.
|
||||
|
||||
**--log-opt**=[]
|
||||
Logging driver specific options.
|
||||
|
||||
**--mac-address**=""
|
||||
Container MAC address (e.g. 92:d0:c6:0a:29:33)
|
||||
|
||||
Remember that the MAC address in an Ethernet network must be unique.
|
||||
The IPv6 link-local address will be based on the device's MAC address
|
||||
according to RFC4862.
|
||||
|
||||
**-m**, **--memory**=""
|
||||
Memory limit (format: <number>[<unit>], where unit = b, k, m or g)
|
||||
|
||||
Allows you to constrain the memory available to a container. If the host
|
||||
supports swap memory, then the **-m** memory setting can be larger than physical
|
||||
RAM. If a limit of 0 is specified (not using **-m**), the container's memory is
|
||||
not limited. The actual limit may be rounded up to a multiple of the operating
|
||||
system's page size (the value would be very large, that's millions of trillions).
|
||||
|
||||
**--memory-reservation**=""
|
||||
Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)
|
||||
|
||||
After setting memory reservation, when the system detects memory contention
|
||||
or low memory, containers are forced to restrict their consumption to their
|
||||
reservation. So you should always set the value below **--memory**, otherwise the
|
||||
hard limit will take precedence. By default, memory reservation will be the same
|
||||
as memory limit.
|
||||
|
||||
**--memory-swap**="LIMIT"
|
||||
A limit value equal to memory plus swap. Must be used with the **-m**
|
||||
(**--memory**) flag. The swap `LIMIT` should always be larger than **-m**
|
||||
(**--memory**) value. By default, the swap `LIMIT` will be set to double
|
||||
the value of --memory.
|
||||
|
||||
The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes),
|
||||
`k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you don't specify a
|
||||
unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
|
||||
|
||||
**--memory-swappiness**=""
|
||||
Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
|
||||
|
||||
**--name**=""
|
||||
Assign a name to the container
|
||||
|
||||
The operator can identify a container in three ways:
|
||||
UUID long identifier (“f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778”)
|
||||
UUID short identifier (“f78375b1c487”)
|
||||
Name (“jonah”)
|
||||
|
||||
kpod generates a UUID for each container, and if a name is not assigned
|
||||
to the container with **--name** then the daemon will also generate a random
|
||||
string name. The name is useful when defining links (see **--link**) (or any
|
||||
other place you need to identify a container). This works for both background
|
||||
and foreground containers.
|
||||
|
||||
**--network**="*bridge*"
|
||||
Set the Network mode for the container
|
||||
'bridge': create a network stack on the default bridge
|
||||
'none': no networking
|
||||
'container:<name|id>': reuse another container's network stack
|
||||
'host': use the kpod host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
|
||||
'<network-name>|<network-id>': connect to a user-defined network
|
||||
|
||||
**--network-alias**=[]
|
||||
Add network-scoped alias for the container
|
||||
|
||||
**--oom-kill-disable**=*true*|*false*
|
||||
Whether to disable OOM Killer for the container or not.
|
||||
|
||||
**--oom-score-adj**=""
|
||||
Tune the host's OOM preferences for containers (accepts -1000 to 1000)
|
||||
|
||||
**--pid**=""
|
||||
Set the PID mode for the container
|
||||
Default is to create a private PID namespace for the container
|
||||
'container:<name|id>': join another container's PID namespace
|
||||
'host': use the host's PID namespace for the container. Note: the host mode gives the container full access to local PID and is therefore considered insecure.
|
||||
|
||||
**--pids-limit**=""
|
||||
Tune the container's pids limit. Set `-1` to have unlimited pids for the container.
|
||||
|
||||
**--pod**=""
|
||||
Run container in an existing pod
|
||||
|
||||
**--privileged**=*true*|*false*
|
||||
Give extended privileges to this container. The default is *false*.
|
||||
|
||||
By default, kpod containers are
|
||||
“unprivileged” (=false) and cannot, for example, modify parts of the kernel.
|
||||
This is because by default a container is not allowed to access any devices.
|
||||
A “privileged” container is given access to all devices.
|
||||
|
||||
When the operator executes **kpod run --privileged**, kpod enables access
|
||||
to all devices on the host as well as set turn off most of the security messurs
|
||||
protecting the host from the container.
|
||||
|
||||
**-p**, **--publish**=[]
|
||||
Publish a container's port, or range of ports, to the host
|
||||
|
||||
Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort`
|
||||
Both hostPort and containerPort can be specified as a range of ports.
|
||||
When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range.
|
||||
(e.g., `kpod run -p 1234-1236:1222-1224 --name thisWorks -t busybox`
|
||||
but not `kpod run -p 1230-1236:1230-1240 --name RangeContainerPortsBiggerThanRangeHostPorts -t busybox`)
|
||||
With ip: `kpod run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage`
|
||||
Use `kpod port` to see the actual mapping: `kpod port CONTAINER $CONTAINERPORT`
|
||||
|
||||
**-P**, **--publish-all**=*true*|*false*
|
||||
Publish all exposed ports to random ports on the host interfaces. The default is *false*.
|
||||
|
||||
When set to true publish all exposed ports to the host interfaces. The
|
||||
default is false. If the operator uses -P (or -p) then kpod will make the
|
||||
exposed port accessible on the host and the ports will be available to any
|
||||
client that can reach the host. When using -P, kpod will bind any exposed
|
||||
port to a random port on the host within an *ephemeral port range* defined by
|
||||
`/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host
|
||||
ports and the exposed ports, use `kpod port`.
|
||||
|
||||
**--read-only**=*true*|*false*
|
||||
Mount the container's root filesystem as read only.
|
||||
|
||||
By default a container will have its root filesystem writable allowing processes
|
||||
to write files anywhere. By specifying the `--read-only` flag the container will have
|
||||
its root filesystem mounted as read only prohibiting any writes.
|
||||
|
||||
**--rm**=*true*|*false*
|
||||
Automatically remove the container when it exits. The default is *false*.
|
||||
`--rm` flag can work together with `-d`, and auto-removal will be done on daemon side. Note that it's
|
||||
incompatible with any restart policy other than `none`.
|
||||
|
||||
**--security-opt**=[]
|
||||
Security Options
|
||||
|
||||
|
||||
"label=user:USER" : Set the label user for the container
|
||||
"label=role:ROLE" : Set the label role for the container
|
||||
"label=type:TYPE" : Set the label type for the container
|
||||
"label=level:LEVEL" : Set the label level for the container
|
||||
"label=disable" : Turn off label confinement for the container
|
||||
"no-new-privileges" : Disable container processes from gaining additional privileges
|
||||
|
||||
"seccomp=unconfined" : Turn off seccomp confinement for the container
|
||||
"seccomp=profile.json : White listed syscalls seccomp Json file to be used as a seccomp filter
|
||||
|
||||
"apparmor=unconfined" : Turn off apparmor confinement for the container
|
||||
"apparmor=your-profile" : Set the apparmor confinement profile for the container
|
||||
|
||||
**--shm-size**=""
|
||||
Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`.
|
||||
Unit is optional and can be `b` (bytes), `k` (kilobytes), `m`(megabytes), or `g` (gigabytes).
|
||||
If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`.
|
||||
|
||||
**--sig-proxy**=*true*|*false*
|
||||
Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is *true*.
|
||||
|
||||
**--stop-signal**=*SIGTERM*
|
||||
Signal to stop a container. Default is SIGTERM.
|
||||
|
||||
**--stop-timeout**=*10*
|
||||
Timeout (in seconds) to stop a container. Default is 10.
|
||||
|
||||
**--storage-opt**=[]
|
||||
Storage driver options per container
|
||||
|
||||
$ kpod create -it --storage-opt size=120G fedora /bin/bash
|
||||
|
||||
This (size) will allow to set the container rootfs size to 120G at creation time.
|
||||
This option is only available for the `devicemapper`, `btrfs`, `overlay2` and `zfs` graph drivers.
|
||||
For the `devicemapper`, `btrfs` and `zfs` storage drivers, user cannot pass a size less than the Default BaseFS Size.
|
||||
For the `overlay2` storage driver, the size option is only available if the backing fs is `xfs` and mounted with the `pquota` mount option.
|
||||
Under these conditions, user can pass any size less then the backing fs size.
|
||||
|
||||
**--sysctl**=SYSCTL
|
||||
Configure namespaced kernel parameters at runtime
|
||||
|
||||
IPC Namespace - current sysctls allowed:
|
||||
|
||||
kernel.msgmax, kernel.msgmnb, kernel.msgmni, kernel.sem, kernel.shmall, kernel.shmmax, kernel.shmmni, kernel.shm_rmid_forced
|
||||
Sysctls beginning with fs.mqueue.*
|
||||
|
||||
Note: if you use the --ipc=host option these sysctls will not be allowed.
|
||||
|
||||
Network Namespace - current sysctls allowed:
|
||||
Sysctls beginning with net.*
|
||||
|
||||
Note: if you use the --network=host option these sysctls will not be allowed.
|
||||
|
||||
**--tmpfs**=[] Create a tmpfs mount
|
||||
|
||||
Mount a temporary filesystem (`tmpfs`) mount into a container, for example:
|
||||
|
||||
$ kpod run -d --tmpfs /tmp:rw,size=787448k,mode=1777 my_image
|
||||
|
||||
This command mounts a `tmpfs` at `/tmp` within the container. The supported mount
|
||||
options are the same as the Linux default `mount` flags. If you do not specify
|
||||
any options, the systems uses the following options:
|
||||
`rw,noexec,nosuid,nodev,size=65536k`.
|
||||
|
||||
**-t**, **--tty**=*true*|*false*
|
||||
Allocate a pseudo-TTY. The default is *false*.
|
||||
|
||||
When set to true kpod will allocate a pseudo-tty and attach to the standard
|
||||
input of the container. This can be used, for example, to run a throwaway
|
||||
interactive shell. The default is false.
|
||||
|
||||
Note: The **-t** option is incompatible with a redirection of the kpod client
|
||||
standard input.
|
||||
|
||||
**--ulimit**=[]
|
||||
Ulimit options
|
||||
|
||||
**-u**, **--user**=""
|
||||
Sets the username or UID used and optionally the groupname or GID for the specified command.
|
||||
|
||||
The followings examples are all valid:
|
||||
--user [user | user:group | uid | uid:gid | user:gid | uid:group ]
|
||||
|
||||
Without this argument the command will be run as root in the container.
|
||||
|
||||
**--userns**=""
|
||||
Set the usernamespace mode for the container when `userns-remap` option is enabled.
|
||||
**host**: use the host usernamespace and enable all privileged options (e.g., `pid=host` or `--privileged`).
|
||||
|
||||
**--uts**=*host*
|
||||
Set the UTS mode for the container
|
||||
**host**: use the host's UTS namespace inside the container.
|
||||
Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure.
|
||||
|
||||
**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*]
|
||||
Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, kpod
|
||||
bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the kpod
|
||||
container. If 'HOST-DIR' is omitted, kpod automatically creates the new
|
||||
volume on the host. The `OPTIONS` are a comma delimited list and can be:
|
||||
|
||||
* [rw|ro]
|
||||
* [z|Z]
|
||||
* [`[r]shared`|`[r]slave`|`[r]private`]
|
||||
|
||||
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR`
|
||||
can be an absolute path or a `name` value. A `name` value must start with an
|
||||
alphanumeric character, followed by `a-z0-9`, `_` (underscore), `.` (period) or
|
||||
`-` (hyphen). An absolute path starts with a `/` (forward slash).
|
||||
|
||||
If you supply a `HOST-DIR` that is an absolute path, kpod bind-mounts to the
|
||||
path you specify. If you supply a `name`, kpod creates a named volume by that
|
||||
`name`. For example, you can specify either `/foo` or `foo` for a `HOST-DIR`
|
||||
value. If you supply the `/foo` value, kpod creates a bind-mount. If you
|
||||
supply the `foo` specification, kpod creates a named volume.
|
||||
|
||||
You can specify multiple **-v** options to mount one or more mounts to a
|
||||
container. To use these same mounts in other containers, specify the
|
||||
**--volumes-from** option also.
|
||||
|
||||
You can add `:ro` or `:rw` suffix to a volume to mount it read-only or
|
||||
read-write mode, respectively. By default, the volumes are mounted read-write.
|
||||
See examples.
|
||||
|
||||
Labeling systems like SELinux require that proper labels are placed on volume
|
||||
content mounted into a container. Without a label, the security system might
|
||||
prevent the processes running inside the container from using the content. By
|
||||
default, kpod does not change the labels set by the OS.
|
||||
|
||||
To change a label in the container context, you can add either of two suffixes
|
||||
`:z` or `:Z` to the volume mount. These suffixes tell kpod to relabel file
|
||||
objects on the shared volumes. The `z` option tells kpod that two containers
|
||||
share the volume content. As a result, kpod labels the content with a shared
|
||||
content label. Shared volume labels allow all containers to read/write content.
|
||||
The `Z` option tells kpod to label the content with a private unshared label.
|
||||
Only the current container can use a private volume.
|
||||
|
||||
By default bind mounted volumes are `private`. That means any mounts done
|
||||
inside container will not be visible on host and vice-a-versa. One can change
|
||||
this behavior by specifying a volume mount propagation property. Making a
|
||||
volume `shared` mounts done under that volume inside container will be
|
||||
visible on host and vice-a-versa. Making a volume `slave` enables only one
|
||||
way mount propagation and that is mounts done on host under that volume
|
||||
will be visible inside container but not the other way around.
|
||||
|
||||
To control mount propagation property of volume one can use `:[r]shared`,
|
||||
`:[r]slave` or `:[r]private` propagation flag. Propagation property can
|
||||
be specified only for bind mounted volumes and not for internal volumes or
|
||||
named volumes. For mount propagation to work source mount point (mount point
|
||||
where source dir is mounted on) has to have right propagation properties. For
|
||||
shared volumes, source mount point has to be shared. And for slave volumes,
|
||||
source mount has to be either shared or slave.
|
||||
|
||||
Use `df <source-dir>` to figure out the source mount and then use
|
||||
`findmnt -o TARGET,PROPAGATION <source-mount-dir>` to figure out propagation
|
||||
properties of source mount. If `findmnt` utility is not available, then one
|
||||
can look at mount entry for source mount point in `/proc/self/mountinfo`. Look
|
||||
at `optional fields` and see if any propagaion properties are specified.
|
||||
`shared:X` means mount is `shared`, `master:X` means mount is `slave` and if
|
||||
nothing is there that means mount is `private`.
|
||||
|
||||
To change propagation properties of a mount point use `mount` command. For
|
||||
example, if one wants to bind mount source directory `/foo` one can do
|
||||
`mount --bind /foo /foo` and `mount --make-private --make-shared /foo`. This
|
||||
will convert /foo into a `shared` mount point. Alternatively one can directly
|
||||
change propagation properties of source mount. Say `/` is source mount for
|
||||
`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount.
|
||||
|
||||
To disable automatic copying of data from the container path to the volume, use
|
||||
the `nocopy` flag. The `nocopy` flag can be set on bind mounts and named volumes.
|
||||
|
||||
**--volumes-from**=[]
|
||||
Mount volumes from the specified container(s)
|
||||
|
||||
Mounts already mounted volumes from a source container onto another
|
||||
container. You must supply the source's container-id. To share
|
||||
a volume, use the **--volumes-from** option when running
|
||||
the target container. You can share volumes even if the source container
|
||||
is not running.
|
||||
|
||||
By default, kpod mounts the volumes in the same mode (read-write or
|
||||
read-only) as it is mounted in the source container. Optionally, you
|
||||
can change this by suffixing the container-id with either the `:ro` or
|
||||
`:rw ` keyword.
|
||||
|
||||
If the location of the volume from the source container overlaps with
|
||||
data residing on a target container, then the volume hides
|
||||
that data on the target.
|
||||
|
||||
**-w**, **--workdir**=""
|
||||
Working directory inside the container
|
||||
|
||||
The default working directory for running binaries within a container is the root directory (/).
|
||||
The image developer can set a different default with the WORKDIR instruction. The operator
|
||||
can override the working directory by using the **-w** option.
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
# HISTORY
|
||||
August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
September 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
November 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
October 2017, converted from Docker documentation to kpod by Dan Walsh for kpod <dwalsh@redhat.com>
|
|
@ -1,45 +0,0 @@
|
|||
% kpod(1) kpod-diff - Inspect changes on a container or image's filesystem
|
||||
% Dan Walsh
|
||||
# kpod-diff "1" "August 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod diff - Inspect changes on a container or image's filesystem
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **diff** [*options* [...]] NAME
|
||||
|
||||
## DESCRIPTION
|
||||
Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--format**
|
||||
|
||||
Alter the output into a different format. The only valid format for diff is `json`.
|
||||
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod diff redis:alpine
|
||||
C /usr
|
||||
C /usr/local
|
||||
C /usr/local/bin
|
||||
A /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
kpod diff --format json redis:alpine
|
||||
{
|
||||
"changed": [
|
||||
"/usr",
|
||||
"/usr/local",
|
||||
"/usr/local/bin"
|
||||
],
|
||||
"added": [
|
||||
"/usr/local/bin/docker-entrypoint.sh"
|
||||
]
|
||||
}
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1)
|
||||
|
||||
## HISTORY
|
||||
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
|
|
@ -1,44 +0,0 @@
|
|||
% kpod(1) kpod-export - Simple tool to export a container's filesystem as a tarball
|
||||
% Urvashi Mohnani
|
||||
# kpod-export "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-export - Export container's filesystem contents as a tar archive
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod export**
|
||||
**CONTAINER**
|
||||
[**--output**|**-o**]
|
||||
[**--help**|**-h**]
|
||||
|
||||
## DESCRIPTION
|
||||
**kpod export** exports the filesystem of a container and saves it as a tarball
|
||||
on the local machine. **kpod export** writes to STDOUT by default and can be
|
||||
redirected to a file using the **output flag**.
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod export [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod export [OPTIONS] CONTAINER**
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--output, -o**
|
||||
Write to a file, default is STDOUT
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
# kpod export -o redis-container.tar 883504668ec465463bc0fe7e63d53154ac3b696ea8d7b233748918664ea90e57
|
||||
```
|
||||
|
||||
```
|
||||
# kpod export > redis-container.tar 883504668ec465463bc0fe7e63d53154ac3b696ea8d7b233748918664ea90e57
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-import(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
August 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -1,106 +0,0 @@
|
|||
% kpod(1) kpod-history - Simple tool to view the history of an image
|
||||
% Urvashi Mohnani
|
||||
% kpod-history "1" "JULY 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-history - Shows the history of an image
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod history**
|
||||
**IMAGE[:TAG|DIGEST]**
|
||||
[**--human**|**-H**]
|
||||
[**--no-trunc**]
|
||||
[**--quiet**|**-q**]
|
||||
[**--format**]
|
||||
[**--help**|**-h**]
|
||||
|
||||
## DESCRIPTION
|
||||
**kpod history** displays the history of an image by printing out information
|
||||
about each layer used in the image. The information printed out for each layer
|
||||
include Created (time and date), Created By, Size, and Comment. The output can
|
||||
be truncated or not using the **--no-trunc** flag. If the **--human** flag is
|
||||
set, the time of creation and size are printed out in a human readable format.
|
||||
The **--quiet** flag displays the ID of the image only when set and the **--format**
|
||||
flag is used to print the information using the Go template provided by the user.
|
||||
|
||||
Valid placeholders for the Go template are listed below:
|
||||
|
||||
| **Placeholder** | **Description** |
|
||||
| --------------- | ----------------------------------------------------------------------------- |
|
||||
| .ID | Image ID |
|
||||
| .Created | if **--human**, time elapsed since creation, otherwise time stamp of creation |
|
||||
| .CreatedBy | Command used to create the layer |
|
||||
| .Size | Size of layer on disk |
|
||||
| .Comment | Comment for the layer |
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod history [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod history [OPTIONS] IMAGE[:TAG|DIGEST]**
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--human, -H**
|
||||
Display sizes and dates in human readable format
|
||||
|
||||
**--no-trunc**
|
||||
Do not truncate the output
|
||||
|
||||
**--quiet, -q**
|
||||
Print the numeric IDs only
|
||||
|
||||
**--format**
|
||||
Alter the output for a format like 'json' or a Go template.
|
||||
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
# kpod history debian
|
||||
ID CREATED CREATED BY SIZE COMMENT
|
||||
b676ca55e4f2c 9 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0 B
|
||||
<missing> 9 weeks ago /bin/sh -c #(nop) ADD file:ebba725fb97cea4... 45.14 MB
|
||||
```
|
||||
|
||||
```
|
||||
# kpod history --no-trunc=true --human=false debian
|
||||
ID CREATED CREATED BY SIZE COMMENT
|
||||
b676ca55e4f2c 2017-07-24T16:52:55Z /bin/sh -c #(nop) CMD ["bash"] 0
|
||||
<missing> 2017-07-24T16:52:54Z /bin/sh -c #(nop) ADD file:ebba725fb97cea4... 45142935
|
||||
```
|
||||
|
||||
```
|
||||
# kpod history --format "{{.ID}} {{.Created}}" debian
|
||||
b676ca55e4f2c 9 weeks ago
|
||||
<missing> 9 weeks ago
|
||||
```
|
||||
|
||||
```
|
||||
# kpod history --format json debian
|
||||
[
|
||||
{
|
||||
"id": "b676ca55e4f2c0ce53d0636438c5372d3efeb5ae99b676fa5a5d1581bad46060",
|
||||
"created": "2017-07-24T16:52:55.195062314Z",
|
||||
"createdBy": "/bin/sh -c #(nop) CMD [\"bash\"]",
|
||||
"size": 0,
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"id": "b676ca55e4f2c0ce53d0636438c5372d3efeb5ae99b676fa5a5d1581bad46060",
|
||||
"created": "2017-07-24T16:52:54.898893387Z",
|
||||
"createdBy": "/bin/sh -c #(nop) ADD file:ebba725fb97cea45d0b1b35ccc8144e766fcfc9a78530465c23b0c4674b14042 in / ",
|
||||
"size": 45142935,
|
||||
"comment": ""
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## history
|
||||
Show the history of an image
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -1,60 +0,0 @@
|
|||
% kpod(1) kpod-images - List images in local storage
|
||||
% Dan Walsh
|
||||
# kpod-images "1" "March 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod images - List images in local storage
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **images** [*options* [...]]
|
||||
|
||||
## DESCRIPTION
|
||||
Displays locally stored images, their names, and their IDs.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--digests**
|
||||
|
||||
Show image digests
|
||||
|
||||
**--filter, -f=[]**
|
||||
|
||||
Filter output based on conditions provided (default [])
|
||||
|
||||
**--format**
|
||||
|
||||
Change the default output format. This can be of a supported type like 'json'
|
||||
or a Go template.
|
||||
|
||||
**--noheading, -n**
|
||||
|
||||
Omit the table headings from the listing of images.
|
||||
|
||||
**--no-trunc, --notruncate**
|
||||
|
||||
Do not truncate output.
|
||||
|
||||
**--quiet, -q**
|
||||
|
||||
Lists only the image IDs.
|
||||
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod images
|
||||
|
||||
kpod images --quiet
|
||||
|
||||
kpod images -q --noheading --notruncate
|
||||
|
||||
kpod images --format json
|
||||
|
||||
kpod images --format "{{.ID}}"
|
||||
|
||||
kpod images --filter dangling=true
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1)
|
||||
|
||||
## HISTORY
|
||||
March 2017, Originally compiled by Dan Walsh <dwalsh@redhat.com>
|
|
@ -1,36 +0,0 @@
|
|||
% kpod(1) kpod-version - Simple tool to view version information
|
||||
% Vincent Batts
|
||||
% kpod-version "1" "JULY 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-info - Display system information
|
||||
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **info** [*options* [...]]
|
||||
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Information display here pertain to the host, current storage stats, and build of kpod. Useful for the user and when reporting issues.
|
||||
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--debug, -D**
|
||||
|
||||
Show additional information
|
||||
|
||||
**--format**
|
||||
|
||||
Change output format to "json" or a Go template.
|
||||
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
`kpod info`
|
||||
|
||||
`kpod info --debug --format json| jq .host.kernel`
|
||||
|
||||
## SEE ALSO
|
||||
crio(8), crio.conf(5)
|
|
@ -1,171 +0,0 @@
|
|||
% kpod(1) kpod-inspect - Display a container or image's configuration
|
||||
% Dan Walsh
|
||||
# kpod-inspect "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod inspect - Display a container or image's configuration
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **inspect** [*options* [...]] name
|
||||
|
||||
## DESCRIPTION
|
||||
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. If a format is specified, the given template will be executed for each result.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--type, t="TYPE"**
|
||||
|
||||
Return data on items of the specified type. Type can be 'container', 'image' or 'all' (default: all)
|
||||
|
||||
**--format, -f="FORMAT"**
|
||||
|
||||
Format the output using the given Go template
|
||||
|
||||
**--size**
|
||||
|
||||
Display the total file size if the type is a container
|
||||
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod inspect redis:alpine
|
||||
|
||||
{
|
||||
"ArgsEscaped": true,
|
||||
"AttachStderr": false,
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"Cmd": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"#(nop) ",
|
||||
"CMD [\"redis-server\"]"
|
||||
],
|
||||
"Domainname": "",
|
||||
"Entrypoint": [
|
||||
"entrypoint.sh"
|
||||
],
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"REDIS_VERSION=3.2.9",
|
||||
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.9.tar.gz",
|
||||
"REDIS_DOWNLOAD_SHA=6eaacfa983b287e440d0839ead20c2231749d5d6b78bbe0e0ffa3a890c59ff26"
|
||||
],
|
||||
"ExposedPorts": {
|
||||
"6379/tcp": {}
|
||||
},
|
||||
"Hostname": "e1ede117fb1e",
|
||||
"Image": "sha256:75e877aa15b534396de82d385386cc4dda7819d5cbb018b9f97b77aeb8f4b55a",
|
||||
"Labels": {},
|
||||
"OnBuild": [],
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Tty": false,
|
||||
"User": "",
|
||||
"Volumes": {
|
||||
"/data": {}
|
||||
},
|
||||
"WorkingDir": "/data"
|
||||
}
|
||||
{
|
||||
"ID": "b3f2436bdb978c1d33b1387afb5d7ba7e3243ed2ce908db431ac0069da86cb45",
|
||||
"Names": [
|
||||
"docker.io/library/redis:alpine"
|
||||
],
|
||||
"Digests": [
|
||||
"sha256:88286f41530e93dffd4b964e1db22ce4939fffa4a4c665dab8591fbab03d4926",
|
||||
"sha256:07b1ac6c7a5068201d8b63a09bb15358ec1616b813ef3942eb8cc12ae191227f",
|
||||
"sha256:91e2e140ea27b3e89f359cd9fab4ec45647dda2a8e5fb0c78633217d9dca87b5",
|
||||
"sha256:08957ceaa2b3be874cde8d7fa15c274300f47185acd62bca812a2ffb6228482d",
|
||||
"sha256:acd3d12a6a79f772961a771f678c1a39e1f370e7baeb9e606ad8f1b92572f4ab",
|
||||
"sha256:4ad88df090801e8faa8cf0be1f403b77613d13e11dad73f561461d482f79256c",
|
||||
"sha256:159ac12c79e1a8d85dfe61afff8c64b96881719139730012a9697f432d6b739a"
|
||||
],
|
||||
"Parent": "",
|
||||
"Comment": "",
|
||||
"Created": "2017-06-28T22:14:36.35280993Z",
|
||||
"Container": "ba8d6c6b0d7fdd201fce404236136b44f3bfdda883466531a3d1a1f87906770b",
|
||||
"ContainerConfig": {
|
||||
"Hostname": "e1ede117fb1e",
|
||||
"Domainname": "",
|
||||
"User": "",
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"AttachStderr": false,
|
||||
"Tty": false,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"REDIS_VERSION=3.2.9",
|
||||
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.9.tar.gz",
|
||||
"REDIS_DOWNLOAD_SHA=6eaacfa983b287e440d0839ead20c2231749d5d6b78bbe0e0ffa3a890c59ff26"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"#(nop) ",
|
||||
"CMD [\"redis-server\"]"
|
||||
],
|
||||
"ArgsEscaped": true,
|
||||
"Image": "sha256:75e877aa15b534396de82d385386cc4dda7819d5cbb018b9f97b77aeb8f4b55a",
|
||||
"Volumes": {
|
||||
"/data": {}
|
||||
},
|
||||
"WorkingDir": "/data",
|
||||
"Entrypoint": [
|
||||
"entrypoint.sh"
|
||||
],
|
||||
"Labels": {},
|
||||
"OnBuild": []
|
||||
},
|
||||
"Author": "",
|
||||
"Config": {
|
||||
"ExposedPorts": {
|
||||
"6379/tcp": {}
|
||||
},
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
"REDIS_VERSION=3.2.9",
|
||||
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.9.tar.gz",
|
||||
"REDIS_DOWNLOAD_SHA=6eaacfa983b287e440d0839ead20c2231749d5d6b78bbe0e0ffa3a890c59ff26"
|
||||
],
|
||||
"Entrypoint": [
|
||||
"entrypoint.sh"
|
||||
],
|
||||
"Cmd": [
|
||||
"redis-server"
|
||||
],
|
||||
"Volumes": {
|
||||
"/data": {}
|
||||
},
|
||||
"WorkingDir": "/data"
|
||||
},
|
||||
"Architecture": "amd64",
|
||||
"OS": "linux",
|
||||
"Size": 3965955,
|
||||
"VirtualSize": 19808086,
|
||||
"GraphDriver": {
|
||||
"Name": "overlay",
|
||||
"Data": {
|
||||
"MergedDir": "/var/lib/containers/storage/overlay/2059d805c90e034cb773d9722232ef018a72143dd31113b470fb876baeccd700/merged",
|
||||
"UpperDir": "/var/lib/containers/storage/overlay/2059d805c90e034cb773d9722232ef018a72143dd31113b470fb876baeccd700/diff",
|
||||
"WorkDir": "/var/lib/containers/storage/overlay/2059d805c90e034cb773d9722232ef018a72143dd31113b470fb876baeccd700/work"
|
||||
}
|
||||
},
|
||||
"RootFS": {
|
||||
"type": "layers",
|
||||
"diff_ids": [
|
||||
"sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0",
|
||||
"sha256:c92a8fc997217611d0bfc9ff14d7ec00350ca564aef0ecbf726624561d7872d7",
|
||||
"sha256:d4c406dea37a107b0cccb845611266a146725598be3e82ba31c55c08d1583b5a",
|
||||
"sha256:8b4fa064e2b6c03a6c37089b0203f167375a8b49259c0ad7cb47c8c1e58b3fa0",
|
||||
"sha256:c393e3d0b00ddf6b4166f1e2ad68245e08e9e3be0a0567a36d0a43854f03bfd6",
|
||||
"sha256:38047b4117cb8bb3bba82991daf9a4e14ba01f9f66c1434d4895a7e96f67d8ba"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1)
|
|
@ -1,33 +0,0 @@
|
|||
% kpod(1) kpod-kill- Kill one or more containers with a signal
|
||||
% Brent Baude
|
||||
# kpod-kill"1" "September 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod kill - Kills one or more containers with a signal
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod kill [OPTIONS] CONTAINER [...]**
|
||||
|
||||
## DESCRIPTION
|
||||
The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--signal, s**
|
||||
|
||||
Signal to send to the container. For more information on Linux signals, refer to *man signal(7)*.
|
||||
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod kill mywebserver
|
||||
|
||||
kpod kill 860a4b23
|
||||
|
||||
kpod kill --signal TERM 860a4b23
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-stop(1)
|
||||
|
||||
## HISTORY
|
||||
September 2017, Originally compiled by Brent Baude <bbaude@redhat.com>
|
|
@ -1,76 +0,0 @@
|
|||
% kpod(1) kpod-load - Simple tool to load an image from an archive to containers-storage
|
||||
% Urvashi Mohnani
|
||||
# kpod-load "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-load - Load an image from docker archive
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod load**
|
||||
**NAME[:TAG|@DIGEST]**
|
||||
[**--input**|**-i**]
|
||||
[**--quiet**|**-q**]
|
||||
[**--help**|**-h**]
|
||||
|
||||
## DESCRIPTION
|
||||
**kpod load** copies an image from either **docker-archive** or **oci-archive** stored
|
||||
on the local machine. **kpod load** reads from stdin by default or a file if the **input** flag is set.
|
||||
The **quiet** flag suppresses the output when set.
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod load [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod load [OPTIONS] NAME[:TAG|@DIGEST]**
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--input, -i**
|
||||
Read from archive file, default is STDIN
|
||||
|
||||
**--quiet, -q**
|
||||
Suppress the output
|
||||
|
||||
**--signature-policy="PATHNAME"**
|
||||
|
||||
Pathname of a signature policy file to use. It is not recommended that this
|
||||
option be used, as the default behavior of using the system-wide default policy
|
||||
(frequently */etc/containers/policy.json*) is most often preferred
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
# kpod load --quiet -i fedora.tar
|
||||
```
|
||||
|
||||
```
|
||||
# kpod load -q --signature-policy /etc/containers/policy.json -i fedora.tar
|
||||
```
|
||||
|
||||
```
|
||||
# kpod load < fedora.tar
|
||||
Getting image source signatures
|
||||
Copying blob sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0
|
||||
0 B / 4.03 MB [---------------------------------------------------------------]
|
||||
Copying config sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d624560
|
||||
0 B / 1.48 KB [---------------------------------------------------------------]
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
```
|
||||
# cat fedora.tar | kpod load
|
||||
Getting image source signatures
|
||||
Copying blob sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0
|
||||
0 B / 4.03 MB [---------------------------------------------------------------]
|
||||
Copying config sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d624560
|
||||
0 B / 1.48 KB [---------------------------------------------------------------]
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-save(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -1,65 +0,0 @@
|
|||
% kpod(1) kpod-login - Simple tool to login to a registry server
|
||||
% Urvashi Mohnani
|
||||
# kpod-login "1" "August 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-login - Login to a container registry
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod login**
|
||||
[**--help**|**-h**]
|
||||
[**--authfile**]
|
||||
[**--user**|**-u**]
|
||||
[**--password**|**-p**]
|
||||
**REGISTRY**
|
||||
|
||||
## DESCRIPTION
|
||||
**kpod login** logs into a specified registry server with the correct username
|
||||
and password. **kpod login** reads in the username and password from STDIN.
|
||||
The username and password can also be set using the **username** and **password** flags.
|
||||
The path of the authentication file can be specified by the user by setting the **authfile**
|
||||
flag. The default path used is **${XDG\_RUNTIME_DIR}/containers/auth.json**.
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod login [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod login [OPTIONS] REGISTRY [GLOBAL OPTIONS]**
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--password, -p**
|
||||
Password for registry
|
||||
|
||||
**--username, -u**
|
||||
Username for registry
|
||||
|
||||
**--authfile**
|
||||
Path of the authentication file. Default is ${XDG_\RUNTIME\_DIR}/containers/auth.json
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
# kpod login docker.io
|
||||
Username: umohnani
|
||||
Password:
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
```
|
||||
# kpod login -u testuser -p testpassword localhost:5000
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
```
|
||||
# kpod login --authfile authdir/myauths.json docker.io
|
||||
Username: umohnani
|
||||
Password:
|
||||
Login Succeeded!
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-logout(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
August 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -1,56 +0,0 @@
|
|||
% kpod(1) kpod-logout - Simple tool to logout of a registry server
|
||||
% Urvashi Mohnani
|
||||
# kpod-logout "1" "August 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-logout - Logout of a container registry
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod logout**
|
||||
[**--help**|**-h**]
|
||||
[**--authfile**]
|
||||
[**--all**|**-a**]
|
||||
**REGISTRY**
|
||||
|
||||
## DESCRIPTION
|
||||
**kpod logout** logs out of a specified registry server by deleting the cached credentials
|
||||
stored in the **auth.json** file. The path of the authentication file can be overrriden by the user by setting the **authfile** flag.
|
||||
The default path used is **${XDG\_RUNTIME_DIR}/containers/auth.json**.
|
||||
All the cached credentials can be removed by setting the **all** flag.
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod logout [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod logout [OPTIONS] REGISTRY [GLOBAL OPTIONS]**
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--authfile**
|
||||
Path of the authentication file. Default is ${XDG_\RUNTIME\_DIR}/containers/auth.json
|
||||
|
||||
**--all, -a**
|
||||
Remove the cached credentials for all registries in the auth file
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
# kpod logout docker.io
|
||||
Remove login credentials for https://registry-1.docker.io/v2/
|
||||
```
|
||||
|
||||
```
|
||||
# kpod logout --authfile authdir/myauths.json docker.io
|
||||
Remove login credentials for https://registry-1.docker.io/v2/
|
||||
```
|
||||
|
||||
```
|
||||
# kpod logout --all
|
||||
Remove login credentials for all registries
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-login(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
August 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -1,61 +0,0 @@
|
|||
% kpod(1) kpod-logs - Fetch the logs of a container
|
||||
% Ryan Cole
|
||||
# kpod-logs "1" "March 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod logs - Fetch the logs of a container
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **logs** [*options* [...]] container
|
||||
|
||||
## DESCRIPTION
|
||||
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
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--follow, -f**
|
||||
|
||||
Follow log output. Default is false
|
||||
|
||||
**--since=TIMESTAMP**
|
||||
|
||||
Show logs since TIMESTAMP
|
||||
|
||||
**--tail=LINES**
|
||||
|
||||
Ouput the specified number of LINES at the end of the logs. LINES must be a positive integer. Defaults to 0, which prints all lines
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod logs b3f2436bdb978c1d33b1387afb5d7ba7e3243ed2ce908db431ac0069da86cb45
|
||||
|
||||
2017/08/07 10:16:21 Seeked /var/log/crio/pods/eb296bd56fab164d4d3cc46e5776b54414af3bf543d138746b25832c816b933b/c49f49788da14f776b7aa93fb97a2a71f9912f4e5a3e30397fca7dfe0ee0367b.log - &{Offset:0 Whence:0}
|
||||
1:C 07 Aug 14:10:09.055 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
|
||||
1:C 07 Aug 14:10:09.055 # Redis version=4.0.1, bits=64, commit=00000000, modified=0, pid=1, just started
|
||||
1:C 07 Aug 14:10:09.055 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
|
||||
1:M 07 Aug 14:10:09.055 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
|
||||
1:M 07 Aug 14:10:09.055 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
|
||||
1:M 07 Aug 14:10:09.055 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
|
||||
1:M 07 Aug 14:10:09.056 * Running mode=standalone, port=6379.
|
||||
1:M 07 Aug 14:10:09.056 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
|
||||
1:M 07 Aug 14:10:09.056 # Server initialized
|
||||
|
||||
|
||||
kpod logs --tail 2 b3f2436bdb97
|
||||
|
||||
1:M 07 Aug 14:10:09.056 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
|
||||
1:M 07 Aug 14:10:09.056 # Server initialized
|
||||
|
||||
kpod logs 224c375f27cd --since 2017-08-07T10:10:09.055837383-04:00 myserver
|
||||
|
||||
1:M 07 Aug 14:10:09.055 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
|
||||
1:M 07 Aug 14:10:09.055 # Current maximum open files is 4096. maxclients has been reduced to 4064 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
|
||||
1:M 07 Aug 14:10:09.056 * Running mode=standalone, port=6379.
|
||||
1:M 07 Aug 14:10:09.056 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
|
||||
1:M 07 Aug 14:10:09.056 # Server initialized
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1)
|
||||
|
||||
## HISTORY
|
||||
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
|
|
@ -1,50 +0,0 @@
|
|||
% kpod(1) kpod-mount - Mount a working container's root filesystem.
|
||||
% Dan Walsh
|
||||
# kpod-mount "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod mount - Mount a working container's root filesystem
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **mount**
|
||||
|
||||
**kpod** **mount** **containerID**
|
||||
|
||||
## DESCRIPTION
|
||||
Mounts the specified container's root file system in a location which can be
|
||||
accessed from the host, and returns its location.
|
||||
|
||||
If you execute the command without any arguments, the tool will list all of the
|
||||
currently mounted containers.
|
||||
|
||||
## RETURN VALUE
|
||||
The location of the mounted file system. On error an empty string and errno is
|
||||
returned.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--format**
|
||||
Print the mounted containers in specified format (json)
|
||||
|
||||
**--notruncate**
|
||||
|
||||
Do not truncate IDs in output.
|
||||
|
||||
**--label**
|
||||
|
||||
SELinux label for the mount point
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod mount c831414b10a3
|
||||
|
||||
/var/lib/containers/storage/overlay/f3ac502d97b5681989dff84dfedc8354239bcecbdc2692f9a639f4e080a02364/merged
|
||||
|
||||
kpod mount
|
||||
|
||||
c831414b10a3 /var/lib/containers/storage/overlay/f3ac502d97b5681989dff84dfedc8354239bcecbdc2692f9a639f4e080a02364/merged
|
||||
|
||||
a7060253093b /var/lib/containers/storage/overlay/0ff7d7ca68bed1ace424f9df154d2dd7b5a125c19d887f17653cbcd5b6e30ba1/merged
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-umount(1), mount(8)
|
|
@ -1,24 +0,0 @@
|
|||
% kpod(1) kpod-pause - Pause one or more containers
|
||||
% Dan Walsh
|
||||
# kpod-pause "1" "September 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod pause - Pause one or more containers
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod pause [OPTIONS] CONTAINER [...]**
|
||||
|
||||
## DESCRIPTION
|
||||
Pauses all the processes in one or more containers. You may use container IDs or names as input.
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod pause mywebserver
|
||||
|
||||
kpod pause 860a4b23
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-unpause(1)
|
||||
|
||||
## HISTORY
|
||||
September 2017, Originally compiled by Dan Walsh <dwalsh@redhat.com>
|
|
@ -1,131 +0,0 @@
|
|||
% kpod(1) kpod-ps - Simple tool to list containers
|
||||
% Urvashi Mohnani
|
||||
% kpod-ps "1" "AUGUST 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-ps - Prints out information about containers
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod ps**
|
||||
[**--all**|**-a**]
|
||||
[**--no-trunc**]
|
||||
[**--quiet**|**-q**]
|
||||
[**--fromat**]
|
||||
[**--help**|**-h**]
|
||||
|
||||
## DESCRIPTION
|
||||
**kpod ps** lists the running containers on the system. Use the **--all** flag to view
|
||||
all the containers information. By default it lists:
|
||||
|
||||
* container id
|
||||
* the name of the image the container is using
|
||||
* the COMMAND the container is executing
|
||||
* the time the container was created
|
||||
* the status of the container
|
||||
* port mappings the container is using
|
||||
* alternative names for the container
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod ps [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod ps [OPTIONS]**
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--all, -a**
|
||||
Show all the containers, default is only running containers
|
||||
|
||||
**--no-trunc**
|
||||
Display the extended information
|
||||
|
||||
**--quiet, -q**
|
||||
Print the numeric IDs of the containers only
|
||||
|
||||
**--format**
|
||||
Pretty-print containers to JSON or using a Go template
|
||||
|
||||
Valid placeholders for the Go template are listed below:
|
||||
|
||||
| **Placeholder** | **Description** |
|
||||
| --------------- | ------------------------------------------------ |
|
||||
| .ID | Container ID |
|
||||
| .Image | Image ID/Name |
|
||||
| .Command | Quoted command used |
|
||||
| .CreatedAt | Creation time for container |
|
||||
| .RunningFor | Time elapsed since container was started |
|
||||
| .Status | Status of container |
|
||||
| .Ports | Exposed ports |
|
||||
| .Size | Size of container |
|
||||
| .Names | Name of container |
|
||||
| .Labels | All the labels assigned to the container |
|
||||
| .Mounts | Volumes mounted in the container |
|
||||
|
||||
|
||||
**--size, -s**
|
||||
Display the total file size
|
||||
|
||||
**--last, -n**
|
||||
Print the n last created containers (all states)
|
||||
|
||||
**--latest, -l**
|
||||
show the latest container created (all states)
|
||||
|
||||
**--namespace, --ns**
|
||||
Display namespace information
|
||||
|
||||
**--filter, -f**
|
||||
Filter output based on conditions given
|
||||
|
||||
Valid filters are listed below:
|
||||
|
||||
| **Filter** | **Description** |
|
||||
| --------------- | ------------------------------------------------------------------- |
|
||||
| id | [ID] Container's ID |
|
||||
| name | [Name] Container's name |
|
||||
| label | [Key] or [Key=Value] Label assigned to a container |
|
||||
| exited | [Int] Container's exit code |
|
||||
| status | [Status] Container's status, e.g *running*, *stopped* |
|
||||
| ancestor | [ImageName] Image or descendant used to create container |
|
||||
| before | [ID] or [Name] Containers created before this container |
|
||||
| since | [ID] or [Name] Containers created since this container |
|
||||
| volume | [VolumeName] or [MountpointDestination] Volume mounted in container |
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
sudo kpod ps -a
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
02f65160e14ca redis:alpine "redis-server" 19 hours ago Exited (-1) 19 hours ago 6379/tcp k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0
|
||||
69ed779d8ef9f redis:alpine "redis-server" 25 hours ago Created 6379/tcp k8s_container1_podsandbox1_redhat.test.crio_redhat-test-crio_1
|
||||
```
|
||||
|
||||
```
|
||||
sudo kpod ps -a -s
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES SIZE
|
||||
02f65160e14ca redis:alpine "redis-server" 20 hours ago Exited (-1) 20 hours ago 6379/tcp k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0 27.49 MB
|
||||
69ed779d8ef9f redis:alpine "redis-server" 25 hours ago Created 6379/tcp k8s_container1_podsandbox1_redhat.test.crio_redhat-test-crio_1 27.49 MB
|
||||
```
|
||||
|
||||
```
|
||||
sudo kpod ps -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}"
|
||||
02f65160e14ca redis:alpine tier=backend proc,tmpfs,devpts,shm,mqueue,sysfs,cgroup,/var/run/,/var/run/
|
||||
69ed779d8ef9f redis:alpine batch=no,type=small proc,tmpfs,devpts,shm,mqueue,sysfs,cgroup,/var/run/,/var/run/
|
||||
```
|
||||
|
||||
```
|
||||
sudo kpod ps --ns -a
|
||||
CONTAINER ID NAMES PID CGROUP IPC MNT NET PIDNS USER UTS
|
||||
3557d882a82e3 k8s_container2_podsandbox1_redhat.test.crio_redhat-test-crio_1 29910 4026531835 4026532585 4026532593 4026532508 4026532595 4026531837 4026532594
|
||||
09564cdae0bec k8s_container1_podsandbox1_redhat.test.crio_redhat-test-crio_1 29851 4026531835 4026532585 4026532590 4026532508 4026532592 4026531837 4026532591
|
||||
a31ebbee9cee7 k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0 29717 4026531835 4026532585 4026532587 4026532508 4026532589 4026531837 4026532588
|
||||
```
|
||||
|
||||
## ps
|
||||
Print a list of containers
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
August 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -1,135 +0,0 @@
|
|||
% kpod(1) kpod-pull - Simple tool to pull an image from a registry
|
||||
% Urvashi Mohnani
|
||||
# kpod-pull "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-pull - Pull an image from a registry
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod pull**
|
||||
**NAME[:TAG|@DIGEST]**
|
||||
[**--help**|**-h**]
|
||||
|
||||
## DESCRIPTION
|
||||
Copies an image from a registry onto the local machine. **kpod pull** pulls an
|
||||
image from Docker Hub if a registry is not specified in the command line argument.
|
||||
If an image tag is not specified, **kpod pull** defaults to the image with the
|
||||
**latest** tag (if it exists) and pulls it. **kpod pull** can also pull an image
|
||||
using its digest **kpod pull [image]@[digest]**. **kpod pull** can be used to pull
|
||||
images from archives and local storage using different transports.
|
||||
|
||||
## imageID
|
||||
Image stored in local container/storage
|
||||
|
||||
## SOURCE
|
||||
|
||||
The SOURCE is a location to get container images
|
||||
The Image "SOURCE" uses a "transport":"details" format.
|
||||
|
||||
Multiple transports are supported:
|
||||
|
||||
**dir:**_path_
|
||||
An existing local directory _path_ storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection.
|
||||
|
||||
**docker://**_docker-reference_
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$XDG_RUNTIME_DIR/containers/auth.json`, which is set e.g. using `(kpod login)`.
|
||||
|
||||
**docker-archive:**_path_[**:**_docker-reference_]
|
||||
An image is stored in the `docker save` formatted file. _docker-reference_ is only used when creating such a file, and it must not contain a digest.
|
||||
|
||||
**docker-daemon:**_docker-reference_
|
||||
An image _docker-reference_ stored in the docker daemon internal storage. _docker-reference_ must contain either a tag or a digest. Alternatively, when reading images, the format can also be docker-daemon:algo:digest (an image ID).
|
||||
|
||||
**oci-archive:**_path_**:**_tag_
|
||||
An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_.
|
||||
|
||||
**ostree:**_image_[**@**_/absolute/repo/path_]
|
||||
An image in local OSTree repository. _/absolute/repo/path_ defaults to _/ostree/repo_.
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod pull [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod pull NAME[:TAG|@DIGEST]**
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--authfile**
|
||||
|
||||
Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json
|
||||
|
||||
**--cert-dir**
|
||||
|
||||
Pathname of a directory containing TLS certificates and keys
|
||||
|
||||
**--creds**
|
||||
|
||||
Credentials (USERNAME:PASSWORD) to use for authenticating to a registry
|
||||
|
||||
**--quiet, -q**
|
||||
|
||||
Suppress output information when pulling images
|
||||
|
||||
**--signature-policy="PATHNAME"**
|
||||
|
||||
Pathname of a signature policy file to use. It is not recommended that this
|
||||
option be used, as the default behavior of using the system-wide default policy
|
||||
(frequently */etc/containers/policy.json*) is most often preferred
|
||||
|
||||
**--tls-verify**
|
||||
|
||||
Require HTTPS and verify certificates when contacting registries (default: true)
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
# kpod pull --signature-policy /etc/containers/policy.json alpine:latest
|
||||
Trying to pull registry.access.redhat.com/alpine:latest... Failed
|
||||
Trying to pull registry.fedoraproject.org/alpine:latest... Failed
|
||||
Trying to pull docker.io/library/alpine:latest...Getting image source signatures
|
||||
Copying blob sha256:88286f41530e93dffd4b964e1db22ce4939fffa4a4c665dab8591fbab03d4926
|
||||
1.90 MB / 1.90 MB [========================================================] 0s
|
||||
Copying config sha256:76da55c8019d7a47c347c0dceb7a6591144d232a7dd616242a367b8bed18ecbc
|
||||
1.48 KB / 1.48 KB [========================================================] 0s
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
```
|
||||
# kpod pull --authfile temp-auths/myauths.json docker://docker.io/umohnani/finaltest
|
||||
Trying to pull docker.io/umohnani/finaltest:latest...Getting image source signatures
|
||||
Copying blob sha256:6d987f6f42797d81a318c40d442369ba3dc124883a0964d40b0c8f4f7561d913
|
||||
1.90 MB / 1.90 MB [========================================================] 0s
|
||||
Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156
|
||||
1.41 KB / 1.41 KB [========================================================] 0s
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
```
|
||||
# kpod pull --creds testuser:testpassword docker.io/umohnani/finaltest
|
||||
Trying to pull docker.io/umohnani/finaltest:latest...Getting image source signatures
|
||||
Copying blob sha256:6d987f6f42797d81a318c40d442369ba3dc124883a0964d40b0c8f4f7561d913
|
||||
1.90 MB / 1.90 MB [========================================================] 0s
|
||||
Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156
|
||||
1.41 KB / 1.41 KB [========================================================] 0s
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
```
|
||||
# kpod pull --tls-verify=false --cert-dir image/certs docker.io/umohnani/finaltest
|
||||
Trying to pull docker.io/umohnani/finaltest:latest...Getting image source signatures
|
||||
Copying blob sha256:6d987f6f42797d81a318c40d442369ba3dc124883a0964d40b0c8f4f7561d913
|
||||
1.90 MB / 1.90 MB [========================================================] 0s
|
||||
Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156
|
||||
1.41 KB / 1.41 KB [========================================================] 0s
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-push(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -1,116 +0,0 @@
|
|||
% kpod(1) kpod-push - Push an image from local storage to elsewhere
|
||||
% Dan Walsh
|
||||
# kpod-push "1" "June 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod push - Push an image from local storage to elsewhere
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **push** [*options* [...]] **imageID** [**destination**]
|
||||
|
||||
## DESCRIPTION
|
||||
Pushes an image from local storage to a specified destination, decompressing
|
||||
and recompressing layers as needed.
|
||||
|
||||
## imageID
|
||||
Image stored in local container/storage
|
||||
|
||||
## DESTINATION
|
||||
|
||||
The DESTINATION is a location to store container images
|
||||
The Image "DESTINATION" uses a "transport":"details" format.
|
||||
|
||||
Multiple transports are supported:
|
||||
|
||||
**dir:**_path_
|
||||
An existing local directory _path_ storing the manifest, layer tarballs and signatures as individual files. This is a non-standardized format, primarily useful for debugging or noninvasive container inspection.
|
||||
|
||||
**docker://**_docker-reference_
|
||||
An image in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in `$XDG_RUNTIME_DIR/containers/auth.json`, which is set e.g. using `(kpod login)`.
|
||||
|
||||
**docker-archive:**_path_[**:**_docker-reference_]
|
||||
An image is stored in the `docker save` formatted file. _docker-reference_ is only used when creating such a file, and it must not contain a digest.
|
||||
|
||||
**docker-daemon:**_docker-reference_
|
||||
An image _docker-reference_ stored in the docker daemon internal storage. _docker-reference_ must contain either a tag or a digest. Alternatively, when reading images, the format can also be docker-daemon:algo:digest (an image ID).
|
||||
|
||||
**oci-archive:**_path_**:**_tag_
|
||||
An image _tag_ in a directory compliant with "Open Container Image Layout Specification" at _path_.
|
||||
|
||||
**ostree:**_image_[**@**_/absolute/repo/path_]
|
||||
An image in local OSTree repository. _/absolute/repo/path_ defaults to _/ostree/repo_.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--authfile**
|
||||
|
||||
Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json
|
||||
|
||||
**--creds="CREDENTIALS"**
|
||||
|
||||
Credentials (USERNAME:PASSWORD) to use for authenticating to a registry
|
||||
|
||||
**cert-dir="PATHNAME"**
|
||||
|
||||
Pathname of a directory containing TLS certificates and keys
|
||||
|
||||
**--disable-compression, -D**
|
||||
|
||||
Don't compress copies of filesystem layers which will be pushed
|
||||
|
||||
**--quiet, -q**
|
||||
|
||||
When writing the output image, suppress progress output
|
||||
|
||||
**--remove-signatures**
|
||||
|
||||
Discard any pre-existing signatures in the image
|
||||
|
||||
**--signature-policy="PATHNAME"**
|
||||
|
||||
Pathname of a signature policy file to use. It is not recommended that this
|
||||
option be used, as the default behavior of using the system-wide default policy
|
||||
(frequently */etc/containers/policy.json*) is most often preferred
|
||||
|
||||
**--sign-by="KEY"**
|
||||
|
||||
Add a signature at the destination using the specified key
|
||||
|
||||
**--tls-verify**
|
||||
|
||||
Require HTTPS and verify certificates when contacting registries (default: true)
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
This example extracts the imageID image to a local directory in docker format.
|
||||
|
||||
`# kpod push imageID dir:/path/to/image`
|
||||
|
||||
This example extracts the imageID image to a local directory in oci format.
|
||||
|
||||
`# kpod push imageID oci-archive:/path/to/layout:image:tag`
|
||||
|
||||
This example extracts the imageID image to a container registry named registry.example.com
|
||||
|
||||
`# kpod push imageID docker://registry.example.com/repository:tag`
|
||||
|
||||
This example extracts the imageID image and puts into the local docker container store
|
||||
|
||||
`# kpod push imageID docker-daemon:image:tag`
|
||||
|
||||
This example pushes the alpine image to umohnani/alpine on dockerhub and reads the creds from
|
||||
the path given to --authfile
|
||||
|
||||
```
|
||||
# kpod push --authfile temp-auths/myauths.json alpine docker://docker.io/umohnani/alpine
|
||||
Getting image source signatures
|
||||
Copying blob sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0
|
||||
4.03 MB / 4.03 MB [========================================================] 1s
|
||||
Copying config sha256:ad4686094d8f0186ec8249fc4917b71faa2c1030d7b5a025c29f26e19d95c156
|
||||
1.41 KB / 1.41 KB [========================================================] 1s
|
||||
Writing manifest to image destination
|
||||
Storing signatures
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-pull(1), crio(8), crio.conf(5)
|
|
@ -1,24 +0,0 @@
|
|||
% kpod(1) kpod-rename - Rename a container
|
||||
% Ryan Cole
|
||||
# kpod-images "1" "March 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod rename - Rename a container
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **rename** CONTAINER NEW-NAME
|
||||
|
||||
## DESCRIPTION
|
||||
Rename a container. Container may be created, running, paused, or stopped
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod rename redis-container webserver
|
||||
|
||||
kpod rename a236b9a4 mycontainer
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1)
|
||||
|
||||
## HISTORY
|
||||
March 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
|
|
@ -1,31 +0,0 @@
|
|||
% kpod(1) kpod-rm - Remove one or more containers
|
||||
% Ryan Cole
|
||||
# kpod-rm "1" "August 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod rm - Remove one or more containers
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **rm** [*options* [...]] container
|
||||
|
||||
## DESCRIPTION
|
||||
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
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--force, f**
|
||||
|
||||
Force the removal of a running container
|
||||
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod rm mywebserver
|
||||
|
||||
kpod rm -f 860a4b23
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-rmi(1)
|
||||
|
||||
## HISTORY
|
||||
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
|
|
@ -1,32 +0,0 @@
|
|||
% kpod(1) kpod-rmi - Removes one or more images
|
||||
% Dan Walsh
|
||||
# kpod-rmi "1" "March 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod rmi - Removes one or more images
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **rmi** **imageID [...]**
|
||||
|
||||
## DESCRIPTION
|
||||
Removes one or more locally stored images.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--force, -f**
|
||||
|
||||
Executing this command will stop all containers that are using the image and remove them from the system
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod rmi imageID
|
||||
|
||||
kpod rmi --force imageID
|
||||
|
||||
kpod rmi imageID1 imageID2 imageID3
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1)
|
||||
|
||||
## HISTORY
|
||||
March 2017, Originally compiled by Dan Walsh <dwalsh@redhat.com>
|
|
@ -1,847 +0,0 @@
|
|||
% kpod(1) kpod-run - Run a command in a container
|
||||
% Dan Walsh
|
||||
kpod-run - Run a command in a new container
|
||||
|
||||
# SYNOPSIS
|
||||
**kpod run** [*options* [...]] IMAGE [COMMAND] [ARG...]
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Run a process in a new container. **kpod run** starts a process with its own
|
||||
file system, its own networking, and its own isolated process tree. The IMAGE
|
||||
which starts the process may define defaults related to the process that will be
|
||||
run in the container, the networking to expose, and more, but **kpod run**
|
||||
gives final control to the operator or administrator who starts the container
|
||||
from the image. For that reason **kpod run** has more options than any other
|
||||
kpod command.
|
||||
|
||||
If the IMAGE is not already loaded then **kpod run** will pull the IMAGE, and
|
||||
all image dependencies, from the repository in the same way running **kpod
|
||||
pull** IMAGE, before it starts the container from that image.
|
||||
|
||||
# OPTIONS
|
||||
**--add-host**=[]
|
||||
Add a custom host-to-IP mapping (host:ip)
|
||||
|
||||
Add a line to /etc/hosts. The format is hostname:ip. The **--add-host**
|
||||
option can be set multiple times.
|
||||
|
||||
**-a**, **--attach**=[]
|
||||
Attach to STDIN, STDOUT or STDERR.
|
||||
|
||||
In foreground mode (the default when **-d**
|
||||
is not specified), **kpod run** can start the process in the container
|
||||
and attach the console to the process's standard input, output, and standard
|
||||
error. It can even pretend to be a TTY (this is what most commandline
|
||||
executables expect) and pass along signals. The **-a** option can be set for
|
||||
each of stdin, stdout, and stderr.
|
||||
|
||||
**--blkio-weight**=*0*
|
||||
Block IO weight (relative weight) accepts a weight value between 10 and 1000.
|
||||
|
||||
**--blkio-weight-device**=[]
|
||||
Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`).
|
||||
|
||||
**--cap-add**=[]
|
||||
Add Linux capabilities
|
||||
|
||||
**--cap-drop**=[]
|
||||
Drop Linux capabilities
|
||||
|
||||
**--cgroup-parent**=""
|
||||
Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
|
||||
|
||||
**--cidfile**=""
|
||||
Write the container ID to the file
|
||||
|
||||
**--cpu-count**=*0*
|
||||
Limit the number of CPUs available for execution by the container.
|
||||
|
||||
On Windows Server containers, this is approximated as a percentage of total CPU usage.
|
||||
|
||||
On Windows Server containers, the processor resource controls are mutually exclusive, the order of precedence is CPUCount first, then CPUShares, and CPUPercent last.
|
||||
|
||||
**--cpu-period**=*0*
|
||||
Limit the CPU CFS (Completely Fair Scheduler) period
|
||||
|
||||
Limit the container's CPU usage. This flag tell the kernel to restrict the container's CPU usage to the period you specify.
|
||||
|
||||
**--cpu-quota**=*0*
|
||||
Limit the CPU CFS (Completely Fair Scheduler) quota
|
||||
|
||||
Limit the container's CPU usage. By default, containers run with the full
|
||||
CPU resource. This flag tell the kernel to restrict the container's CPU usage
|
||||
to the quota you specify.
|
||||
|
||||
**--cpu-rt-period**=0
|
||||
Limit the CPU real-time period in microseconds
|
||||
|
||||
Limit the container's Real Time CPU usage. This flag tell the kernel to restrict the container's Real Time CPU usage to the period you specify.
|
||||
|
||||
**--cpu-rt-runtime**=0
|
||||
Limit the CPU real-time runtime in microseconds
|
||||
|
||||
Limit the containers Real Time CPU usage. This flag tells the kernel to limit the amount of time in a given CPU period Real Time tasks may consume. Ex:
|
||||
Period of 1,000,000us and Runtime of 950,000us means that this container could consume 95% of available CPU and leave the remaining 5% to normal priority tasks.
|
||||
|
||||
The sum of all runtimes across containers cannot exceed the amount allotted to the parent cgroup.
|
||||
|
||||
**--cpu-shares**=*0*
|
||||
CPU shares (relative weight)
|
||||
|
||||
By default, all containers get the same proportion of CPU cycles. This proportion
|
||||
can be modified by changing the container's CPU share weighting relative
|
||||
to the weighting of all other running containers.
|
||||
|
||||
To modify the proportion from the default of 1024, use the **--cpu-shares**
|
||||
flag to set the weighting to 2 or higher.
|
||||
|
||||
The proportion will only apply when CPU-intensive processes are running.
|
||||
When tasks in one container are idle, other containers can use the
|
||||
left-over CPU time. The actual amount of CPU time will vary depending on
|
||||
the number of containers running on the system.
|
||||
|
||||
For example, consider three containers, one has a cpu-share of 1024 and
|
||||
two others have a cpu-share setting of 512. When processes in all three
|
||||
containers attempt to use 100% of CPU, the first container would receive
|
||||
50% of the total CPU time. If you add a fourth container with a cpu-share
|
||||
of 1024, the first container only gets 33% of the CPU. The remaining containers
|
||||
receive 16.5%, 16.5% and 33% of the CPU.
|
||||
|
||||
On a multi-core system, the shares of CPU time are distributed over all CPU
|
||||
cores. Even if a container is limited to less than 100% of CPU time, it can
|
||||
use 100% of each individual CPU core.
|
||||
|
||||
For example, consider a system with more than three cores. If you start one
|
||||
container **{C0}** with **-c=512** running one process, and another container
|
||||
**{C1}** with **-c=1024** running two processes, this can result in the following
|
||||
division of CPU shares:
|
||||
|
||||
PID container CPU CPU share
|
||||
100 {C0} 0 100% of CPU0
|
||||
101 {C1} 1 100% of CPU1
|
||||
102 {C1} 2 100% of CPU2
|
||||
|
||||
**--cpus**=0.0
|
||||
Number of CPUs. The default is *0.0* which means no limit.
|
||||
|
||||
**--cpuset-cpus**=""
|
||||
CPUs in which to allow execution (0-3, 0,1)
|
||||
|
||||
**--cpuset-mems**=""
|
||||
Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
|
||||
|
||||
If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1`
|
||||
then processes in your container will only use memory from the first
|
||||
two memory nodes.
|
||||
|
||||
**-d**, **--detach**=*true*|*false*
|
||||
Detached mode: run the container in the background and print the new container ID. The default is *false*.
|
||||
|
||||
At any time you can run **kpod ps** in
|
||||
the other shell to view a list of the running containers. You can reattach to a
|
||||
detached container with **kpod attach**. If you choose to run a container in
|
||||
the detached mode, then you cannot use the **-rm** option.
|
||||
|
||||
When attached in the tty mode, you can detach from the container (and leave it
|
||||
running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`.
|
||||
You configure the key sequence using the **--detach-keys** option or a configuration file.
|
||||
See **config-json(5)** for documentation on using a configuration file.
|
||||
|
||||
**--detach-keys**=""
|
||||
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 `_`.
|
||||
|
||||
**--device**=[]
|
||||
Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
|
||||
|
||||
**--device-read-bps**=[]
|
||||
Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)
|
||||
|
||||
**--device-read-iops**=[]
|
||||
Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)
|
||||
|
||||
**--device-write-bps**=[]
|
||||
Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)
|
||||
|
||||
**--device-write-iops**=[]
|
||||
Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)
|
||||
|
||||
**--dns**=[]
|
||||
Set custom DNS servers
|
||||
|
||||
This option can be used to override the DNS
|
||||
configuration passed to the container. Typically this is necessary when the
|
||||
host DNS configuration is invalid for the container (e.g., 127.0.0.1). When this
|
||||
is the case the **--dns** flags is necessary for every run.
|
||||
|
||||
**--dns-option**=[]
|
||||
Set custom DNS options
|
||||
|
||||
**--dns-search**=[]
|
||||
Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)
|
||||
|
||||
**--entrypoint**=""
|
||||
Overwrite the default ENTRYPOINT of the image
|
||||
|
||||
This option allows you to overwrite the default entrypoint of the image.
|
||||
The ENTRYPOINT of an image is similar to a COMMAND
|
||||
because it specifies what executable to run when the container starts, but it is
|
||||
(purposely) more difficult to override. The ENTRYPOINT gives a container its
|
||||
default nature or behavior, so that when you set an ENTRYPOINT you can run the
|
||||
container as if it were that binary, complete with default options, and you can
|
||||
pass in more options via the COMMAND. But, sometimes an operator may want to run
|
||||
something else inside the container, so you can override the default ENTRYPOINT
|
||||
at runtime by using a **--entrypoint** and a string to specify the new
|
||||
ENTRYPOINT.
|
||||
|
||||
**-e**, **--env**=[]
|
||||
Set environment variables
|
||||
|
||||
This option allows you to specify arbitrary
|
||||
environment variables that are available for the process that will be launched
|
||||
inside of the container.
|
||||
|
||||
**--env-file**=[]
|
||||
Read in a line delimited file of environment variables
|
||||
|
||||
**--expose**=[]
|
||||
Expose a port, or a range of ports (e.g. --expose=3300-3310) to set up port redirection
|
||||
on the host system.
|
||||
|
||||
**--group-add**=[]
|
||||
Add additional groups to run as
|
||||
|
||||
**--hostname**=""
|
||||
Container host name
|
||||
|
||||
Sets the container host name that is available inside the container.
|
||||
|
||||
**--help**
|
||||
Print usage statement
|
||||
|
||||
**-i**, **--interactive**=*true*|*false*
|
||||
Keep STDIN open even if not attached. The default is *false*.
|
||||
|
||||
When set to true, keep stdin open even if not attached. The default is false.
|
||||
|
||||
**--ip**=""
|
||||
Sets the container's interface IPv4 address (e.g. 172.23.0.9)
|
||||
|
||||
It can only be used in conjunction with **--network** for user-defined networks
|
||||
|
||||
**--ip6**=""
|
||||
Sets the container's interface IPv6 address (e.g. 2001:db8::1b99)
|
||||
|
||||
It can only be used in conjunction with **--network** for user-defined networks
|
||||
|
||||
**--ipc**=""
|
||||
Default is to create a private IPC namespace (POSIX SysV IPC) for the container
|
||||
'container:<name|id>': reuses another container shared memory, semaphores and message queues
|
||||
'host': use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure.
|
||||
|
||||
**--kernel-memory**=""
|
||||
Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)
|
||||
|
||||
Constrains the kernel memory available to a container. If a limit of 0
|
||||
is specified (not using `--kernel-memory`), the container's kernel memory
|
||||
is not limited. If you specify a limit, it may be rounded up to a multiple
|
||||
of the operating system's page size and the value can be very large,
|
||||
millions of trillions.
|
||||
|
||||
**-l**, **--label**=[]
|
||||
Add metadata to a container (e.g., --label com.example.key=value)
|
||||
|
||||
**--label-file**=[]
|
||||
Read in a line delimited file of labels
|
||||
|
||||
**--link-local-ip**=[]
|
||||
Add one or more link-local IPv4/IPv6 addresses to the container's interface
|
||||
|
||||
**--log-driver**="*json-file*|*syslog*|*journald*|*gelf*|*fluentd*|*awslogs*|*splunk*|*etwlogs*|*gcplogs*|*none*"
|
||||
Logging driver for the container. Default is defined by daemon `--log-driver` flag.
|
||||
**Warning**: the `kpod logs` command works only for the `json-file` and
|
||||
`journald` logging drivers.
|
||||
|
||||
**--log-opt**=[]
|
||||
Logging driver specific options.
|
||||
|
||||
**--mac-address**=""
|
||||
Container MAC address (e.g. 92:d0:c6:0a:29:33)
|
||||
|
||||
Remember that the MAC address in an Ethernet network must be unique.
|
||||
The IPv6 link-local address will be based on the device's MAC address
|
||||
according to RFC4862.
|
||||
|
||||
**-m**, **--memory**=""
|
||||
Memory limit (format: <number>[<unit>], where unit = b, k, m or g)
|
||||
|
||||
Allows you to constrain the memory available to a container. If the host
|
||||
supports swap memory, then the **-m** memory setting can be larger than physical
|
||||
RAM. If a limit of 0 is specified (not using **-m**), the container's memory is
|
||||
not limited. The actual limit may be rounded up to a multiple of the operating
|
||||
system's page size (the value would be very large, that's millions of trillions).
|
||||
|
||||
**--memory-reservation**=""
|
||||
Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)
|
||||
|
||||
After setting memory reservation, when the system detects memory contention
|
||||
or low memory, containers are forced to restrict their consumption to their
|
||||
reservation. So you should always set the value below **--memory**, otherwise the
|
||||
hard limit will take precedence. By default, memory reservation will be the same
|
||||
as memory limit.
|
||||
|
||||
**--memory-swap**="LIMIT"
|
||||
A limit value equal to memory plus swap. Must be used with the **-m**
|
||||
(**--memory**) flag. The swap `LIMIT` should always be larger than **-m**
|
||||
(**--memory**) value. By default, the swap `LIMIT` will be set to double
|
||||
the value of --memory.
|
||||
|
||||
The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes),
|
||||
`k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you don't specify a
|
||||
unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
|
||||
|
||||
**--memory-swappiness**=""
|
||||
Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
|
||||
|
||||
**--name**=""
|
||||
Assign a name to the container
|
||||
|
||||
The operator can identify a container in three ways:
|
||||
UUID long identifier (“f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778”)
|
||||
UUID short identifier (“f78375b1c487”)
|
||||
Name (“jonah”)
|
||||
|
||||
kpod generates a UUID for each container, and if a name is not assigned
|
||||
to the container with **--name** then the daemon will also generate a random
|
||||
string name. The name is useful when defining links (see **--link**) (or any
|
||||
other place you need to identify a container). This works for both background
|
||||
and foreground containers.
|
||||
|
||||
**--network**="*bridge*"
|
||||
Set the Network mode for the container
|
||||
'bridge': create a network stack on the default bridge
|
||||
'none': no networking
|
||||
'container:<name|id>': reuse another container's network stack
|
||||
'host': use the kpod host network stack. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
|
||||
'<network-name>|<network-id>': connect to a user-defined network
|
||||
|
||||
**--network-alias**=[]
|
||||
Add network-scoped alias for the container
|
||||
|
||||
**--oom-kill-disable**=*true*|*false*
|
||||
Whether to disable OOM Killer for the container or not.
|
||||
|
||||
**--oom-score-adj**=""
|
||||
Tune the host's OOM preferences for containers (accepts -1000 to 1000)
|
||||
|
||||
**--pid**=""
|
||||
Set the PID mode for the container
|
||||
Default is to create a private PID namespace for the container
|
||||
'container:<name|id>': join another container's PID namespace
|
||||
'host': use the host's PID namespace for the container. Note: the host mode gives the container full access to local PID and is therefore considered insecure.
|
||||
|
||||
**--pids-limit**=""
|
||||
Tune the container's pids limit. Set `-1` to have unlimited pids for the container.
|
||||
|
||||
**--pod**=""
|
||||
Run container in an existing pod
|
||||
|
||||
**--privileged**=*true*|*false*
|
||||
Give extended privileges to this container. The default is *false*.
|
||||
|
||||
By default, kpod containers are
|
||||
“unprivileged” (=false) and cannot, for example, modify parts of the kernel.
|
||||
This is because by default a container is not allowed to access any devices.
|
||||
A “privileged” container is given access to all devices.
|
||||
|
||||
When the operator executes **kpod run --privileged**, kpod enables access
|
||||
to all devices on the host as well as set turn off most of the security messurs
|
||||
protecting the host from the container.
|
||||
|
||||
**-p**, **--publish**=[]
|
||||
Publish a container's port, or range of ports, to the host
|
||||
|
||||
Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort`
|
||||
Both hostPort and containerPort can be specified as a range of ports.
|
||||
When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range.
|
||||
(e.g., `kpod run -p 1234-1236:1222-1224 --name thisWorks -t busybox`
|
||||
but not `kpod run -p 1230-1236:1230-1240 --name RangeContainerPortsBiggerThanRangeHostPorts -t busybox`)
|
||||
With ip: `kpod run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage`
|
||||
Use `kpod port` to see the actual mapping: `kpod port CONTAINER $CONTAINERPORT`
|
||||
|
||||
**-P**, **--publish-all**=*true*|*false*
|
||||
Publish all exposed ports to random ports on the host interfaces. The default is *false*.
|
||||
|
||||
When set to true publish all exposed ports to the host interfaces. The
|
||||
default is false. If the operator uses -P (or -p) then kpod will make the
|
||||
exposed port accessible on the host and the ports will be available to any
|
||||
client that can reach the host. When using -P, kpod will bind any exposed
|
||||
port to a random port on the host within an *ephemeral port range* defined by
|
||||
`/proc/sys/net/ipv4/ip_local_port_range`. To find the mapping between the host
|
||||
ports and the exposed ports, use `kpod port`.
|
||||
|
||||
**--read-only**=*true*|*false*
|
||||
Mount the container's root filesystem as read only.
|
||||
|
||||
By default a container will have its root filesystem writable allowing processes
|
||||
to write files anywhere. By specifying the `--read-only` flag the container will have
|
||||
its root filesystem mounted as read only prohibiting any writes.
|
||||
|
||||
**--rm**=*true*|*false*
|
||||
Automatically remove the container when it exits. The default is *false*.
|
||||
`--rm` flag can work together with `-d`, and auto-removal will be done on daemon side. Note that it's
|
||||
incompatible with any restart policy other than `none`.
|
||||
|
||||
**--security-opt**=[]
|
||||
Security Options
|
||||
|
||||
"label=user:USER" : Set the label user for the container
|
||||
"label=role:ROLE" : Set the label role for the container
|
||||
"label=type:TYPE" : Set the label type for the container
|
||||
"label=level:LEVEL" : Set the label level for the container
|
||||
"label=disable" : Turn off label confinement for the container
|
||||
"no-new-privileges" : Disable container processes from gaining additional privileges
|
||||
|
||||
"seccomp=unconfined" : Turn off seccomp confinement for the container
|
||||
"seccomp=profile.json : White listed syscalls seccomp Json file to be used as a seccomp filter
|
||||
|
||||
"apparmor=unconfined" : Turn off apparmor confinement for the container
|
||||
"apparmor=your-profile" : Set the apparmor confinement profile for the container
|
||||
|
||||
**--shm-size**=""
|
||||
Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`.
|
||||
Unit is optional and can be `b` (bytes), `k` (kilobytes), `m`(megabytes), or `g` (gigabytes).
|
||||
If you omit the unit, the system uses bytes. If you omit the size entirely, the system uses `64m`.
|
||||
|
||||
**--sig-proxy**=*true*|*false*
|
||||
Proxy received signals to the process (non-TTY mode only). SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is *true*.
|
||||
|
||||
**--stop-signal**=*SIGTERM*
|
||||
Signal to stop a container. Default is SIGTERM.
|
||||
|
||||
**--stop-timeout**=*10*
|
||||
Timeout (in seconds) to stop a container. Default is 10.
|
||||
|
||||
**--storage-opt**=[]
|
||||
Storage driver options per container
|
||||
|
||||
$ kpod run -it --storage-opt size=120G fedora /bin/bash
|
||||
|
||||
This (size) will allow to set the container rootfs size to 120G at creation time.
|
||||
This option is only available for the `devicemapper`, `btrfs`, `overlay2` and `zfs` graph drivers.
|
||||
For the `devicemapper`, `btrfs` and `zfs` storage drivers, user cannot pass a size less than the Default BaseFS Size.
|
||||
For the `overlay2` storage driver, the size option is only available if the backing fs is `xfs` and mounted with the `pquota` mount option.
|
||||
Under these conditions, user can pass any size less then the backing fs size.
|
||||
|
||||
**--sysctl**=SYSCTL
|
||||
Configure namespaced kernel parameters at runtime
|
||||
|
||||
IPC Namespace - current sysctls allowed:
|
||||
|
||||
kernel.msgmax, kernel.msgmnb, kernel.msgmni, kernel.sem, kernel.shmall, kernel.shmmax, kernel.shmmni, kernel.shm_rmid_forced
|
||||
Sysctls beginning with fs.mqueue.*
|
||||
|
||||
Note: if you use the `--ipc=host` option these sysctls will not be allowed.
|
||||
|
||||
Network Namespace - current sysctls allowed:
|
||||
Sysctls beginning with net.*
|
||||
|
||||
Note: if you use the `--network=host` option these sysctls will not be allowed.
|
||||
|
||||
**--tmpfs**=[] Create a tmpfs mount
|
||||
|
||||
Mount a temporary filesystem (`tmpfs`) mount into a container, for example:
|
||||
|
||||
$ kpod run -d --tmpfs /tmp:rw,size=787448k,mode=1777 my_image
|
||||
|
||||
This command mounts a `tmpfs` at `/tmp` within the container. The supported mount
|
||||
options are the same as the Linux default `mount` flags. If you do not specify
|
||||
any options, the systems uses the following options:
|
||||
`rw,noexec,nosuid,nodev,size=65536k`.
|
||||
|
||||
**-t**, **--tty**=*true*|*false*
|
||||
Allocate a pseudo-TTY. The default is *false*.
|
||||
|
||||
When set to true kpod will allocate a pseudo-tty and attach to the standard
|
||||
input of the container. This can be used, for example, to run a throwaway
|
||||
interactive shell. The default is false.
|
||||
|
||||
Note: The **-t** option is incompatible with a redirection of the kpod client
|
||||
standard input.
|
||||
|
||||
**--ulimit**=[]
|
||||
Ulimit options
|
||||
|
||||
**-u**, **--user**=""
|
||||
Sets the username or UID used and optionally the groupname or GID for the specified command.
|
||||
|
||||
The followings examples are all valid:
|
||||
--user [user | user:group | uid | uid:gid | user:gid | uid:group ]
|
||||
|
||||
Without this argument the command will be run as root in the container.
|
||||
|
||||
**--userns**=""
|
||||
Set the usernamespace mode for the container when `userns-remap` option is enabled.
|
||||
**host**: use the host usernamespace and enable all privileged options (e.g., `pid=host` or `--privileged`).
|
||||
|
||||
**--uts**=*host*
|
||||
Set the UTS mode for the container
|
||||
**host**: use the host's UTS namespace inside the container.
|
||||
Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure.
|
||||
|
||||
**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*]
|
||||
Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, kpod
|
||||
bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the kpod
|
||||
container. If 'HOST-DIR' is omitted, kpod automatically creates the new
|
||||
volume on the host. The `OPTIONS` are a comma delimited list and can be:
|
||||
|
||||
* [rw|ro]
|
||||
* [z|Z]
|
||||
* [`[r]shared`|`[r]slave`|`[r]private`]
|
||||
* [nocopy]
|
||||
|
||||
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR`
|
||||
can be an absolute path or a `name` value. A `name` value must start with an
|
||||
alphanumeric character, followed by `a-z0-9`, `_` (underscore), `.` (period) or
|
||||
`-` (hyphen). An absolute path starts with a `/` (forward slash).
|
||||
|
||||
If you supply a `HOST-DIR` that is an absolute path, kpod bind-mounts to the
|
||||
path you specify. If you supply a `name`, kpod creates a named volume by that
|
||||
`name`. For example, you can specify either `/foo` or `foo` for a `HOST-DIR`
|
||||
value. If you supply the `/foo` value, kpod creates a bind-mount. If you
|
||||
supply the `foo` specification, kpod creates a named volume.
|
||||
|
||||
You can specify multiple **-v** options to mount one or more mounts to a
|
||||
container. To use these same mounts in other containers, specify the
|
||||
**--volumes-from** option also.
|
||||
|
||||
You can add `:ro` or `:rw` suffix to a volume to mount it read-only or
|
||||
read-write mode, respectively. By default, the volumes are mounted read-write.
|
||||
See examples.
|
||||
|
||||
Labeling systems like SELinux require that proper labels are placed on volume
|
||||
content mounted into a container. Without a label, the security system might
|
||||
prevent the processes running inside the container from using the content. By
|
||||
default, kpod does not change the labels set by the OS.
|
||||
|
||||
To change a label in the container context, you can add either of two suffixes
|
||||
`:z` or `:Z` to the volume mount. These suffixes tell kpod to relabel file
|
||||
objects on the shared volumes. The `z` option tells kpod that two containers
|
||||
share the volume content. As a result, kpod labels the content with a shared
|
||||
content label. Shared volume labels allow all containers to read/write content.
|
||||
The `Z` option tells kpod to label the content with a private unshared label.
|
||||
Only the current container can use a private volume.
|
||||
|
||||
By default bind mounted volumes are `private`. That means any mounts done
|
||||
inside container will not be visible on host and vice-a-versa. One can change
|
||||
this behavior by specifying a volume mount propagation property. Making a
|
||||
volume `shared` mounts done under that volume inside container will be
|
||||
visible on host and vice-a-versa. Making a volume `slave` enables only one
|
||||
way mount propagation and that is mounts done on host under that volume
|
||||
will be visible inside container but not the other way around.
|
||||
|
||||
To control mount propagation property of volume one can use `:[r]shared`,
|
||||
`:[r]slave` or `:[r]private` propagation flag. Propagation property can
|
||||
be specified only for bind mounted volumes and not for internal volumes or
|
||||
named volumes. For mount propagation to work source mount point (mount point
|
||||
where source dir is mounted on) has to have right propagation properties. For
|
||||
shared volumes, source mount point has to be shared. And for slave volumes,
|
||||
source mount has to be either shared or slave.
|
||||
|
||||
Use `df <source-dir>` to figure out the source mount and then use
|
||||
`findmnt -o TARGET,PROPAGATION <source-mount-dir>` to figure out propagation
|
||||
properties of source mount. If `findmnt` utility is not available, then one
|
||||
can look at mount entry for source mount point in `/proc/self/mountinfo`. Look
|
||||
at `optional fields` and see if any propagaion properties are specified.
|
||||
`shared:X` means mount is `shared`, `master:X` means mount is `slave` and if
|
||||
nothing is there that means mount is `private`.
|
||||
|
||||
To change propagation properties of a mount point use `mount` command. For
|
||||
example, if one wants to bind mount source directory `/foo` one can do
|
||||
`mount --bind /foo /foo` and `mount --make-private --make-shared /foo`. This
|
||||
will convert /foo into a `shared` mount point. Alternatively one can directly
|
||||
change propagation properties of source mount. Say `/` is source mount for
|
||||
`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount.
|
||||
|
||||
To disable automatic copying of data from the container path to the volume, use
|
||||
the `nocopy` flag. The `nocopy` flag can be set on bind mounts and named volumes.
|
||||
|
||||
**--volumes-from**=[]
|
||||
Mount volumes from the specified container(s)
|
||||
|
||||
Mounts already mounted volumes from a source container onto another
|
||||
container. You must supply the source's container-id. To share
|
||||
a volume, use the **--volumes-from** option when running
|
||||
the target container. You can share volumes even if the source container
|
||||
is not running.
|
||||
|
||||
By default, kpod mounts the volumes in the same mode (read-write or
|
||||
read-only) as it is mounted in the source container. Optionally, you
|
||||
can change this by suffixing the container-id with either the `:ro` or
|
||||
`:rw ` keyword.
|
||||
|
||||
If the location of the volume from the source container overlaps with
|
||||
data residing on a target container, then the volume hides
|
||||
that data on the target.
|
||||
|
||||
**-w**, **--workdir**=""
|
||||
Working directory inside the container
|
||||
|
||||
The default working directory for running binaries within a container is the root directory (/).
|
||||
The image developer can set a different default with the WORKDIR instruction. The operator
|
||||
can override the working directory by using the **-w** option.
|
||||
|
||||
# Exit Status
|
||||
|
||||
The exit code from `kpod run` gives information about why the container
|
||||
failed to run or why it exited. When `kpod run` exits with a non-zero code,
|
||||
the exit codes follow the `chroot` standard, see below:
|
||||
|
||||
**_125_** if the error is with kpod **_itself_**
|
||||
|
||||
$ kpod run --foo busybox; echo $?
|
||||
# flag provided but not defined: --foo
|
||||
See 'kpod run --help'.
|
||||
125
|
||||
|
||||
**_126_** if the **_contained command_** cannot be invoked
|
||||
|
||||
$ kpod run busybox /etc; echo $?
|
||||
# exec: "/etc": permission denied
|
||||
kpod: Error response from daemon: Contained command could not be invoked
|
||||
126
|
||||
|
||||
**_127_** if the **_contained command_** cannot be found
|
||||
|
||||
$ kpod run busybox foo; echo $?
|
||||
# exec: "foo": executable file not found in $PATH
|
||||
kpod: Error response from daemon: Contained command not found or does not exist
|
||||
127
|
||||
|
||||
**_Exit code_** of **_contained command_** otherwise
|
||||
|
||||
$ kpod run busybox /bin/sh -c 'exit 3'
|
||||
# 3
|
||||
|
||||
# EXAMPLES
|
||||
|
||||
## Running container in read-only mode
|
||||
|
||||
During container image development, containers often need to write to the image
|
||||
content. Installing packages into /usr, for example. In production,
|
||||
applications seldom need to write to the image. Container applications write
|
||||
to volumes if they need to write to file systems at all. Applications can be
|
||||
made more secure by running them in read-only mode using the - -read-only switch.
|
||||
This protects the containers image from modification. Read only containers may
|
||||
still need to write temporary data. The best way to handle this is to mount
|
||||
tmpfs directories on /run and /tmp.
|
||||
|
||||
# kpod run --read-only --tmpfs /run --tmpfs /tmp -i -t fedora /bin/bash
|
||||
|
||||
## Exposing log messages from the container to the host's log
|
||||
|
||||
If you want messages that are logged in your container to show up in the host's
|
||||
syslog/journal then you should bind mount the /dev/log directory as follows.
|
||||
|
||||
# kpod run -v /dev/log:/dev/log -i -t fedora /bin/bash
|
||||
|
||||
From inside the container you can test this by sending a message to the log.
|
||||
|
||||
(bash)# logger "Hello from my container"
|
||||
|
||||
Then exit and check the journal.
|
||||
|
||||
# exit
|
||||
|
||||
# journalctl -b | grep Hello
|
||||
|
||||
This should list the message sent to logger.
|
||||
|
||||
## Attaching to one or more from STDIN, STDOUT, STDERR
|
||||
|
||||
If you do not specify -a then kpod will attach everything (stdin,stdout,stderr)
|
||||
. You can specify to which of the three standard streams (stdin, stdout, stderr)
|
||||
you'd like to connect instead, as in:
|
||||
|
||||
# kpod run -a stdin -a stdout -i -t fedora /bin/bash
|
||||
|
||||
## Sharing IPC between containers
|
||||
|
||||
Using shm_server.c available here: https://www.cs.cf.ac.uk/Dave/C/node27.html
|
||||
|
||||
Testing `--ipc=host` mode:
|
||||
|
||||
Host shows a shared memory segment with 7 pids attached, happens to be from httpd:
|
||||
|
||||
```
|
||||
$ sudo ipcs -m
|
||||
|
||||
------ Shared Memory Segments --------
|
||||
key shmid owner perms bytes nattch status
|
||||
0x01128e25 0 root 600 1000 7
|
||||
```
|
||||
|
||||
Now run a regular container, and it correctly does NOT see the shared memory segment from the host:
|
||||
|
||||
```
|
||||
$ kpod run -it shm ipcs -m
|
||||
|
||||
------ Shared Memory Segments --------
|
||||
key shmid owner perms bytes nattch status
|
||||
```
|
||||
|
||||
Run a container with the new `--ipc=host` option, and it now sees the shared memory segment from the host httpd:
|
||||
|
||||
```
|
||||
$ kpod run -it --ipc=host shm ipcs -m
|
||||
|
||||
------ Shared Memory Segments --------
|
||||
key shmid owner perms bytes nattch status
|
||||
0x01128e25 0 root 600 1000 7
|
||||
```
|
||||
Testing `--ipc=container:CONTAINERID` mode:
|
||||
|
||||
Start a container with a program to create a shared memory segment:
|
||||
```
|
||||
$ kpod run -it shm bash
|
||||
$ sudo shm/shm_server &
|
||||
$ sudo ipcs -m
|
||||
|
||||
------ Shared Memory Segments --------
|
||||
key shmid owner perms bytes nattch status
|
||||
0x0000162e 0 root 666 27 1
|
||||
```
|
||||
Create a 2nd container correctly shows no shared memory segment from 1st container:
|
||||
```
|
||||
$ kpod run shm ipcs -m
|
||||
|
||||
------ Shared Memory Segments --------
|
||||
key shmid owner perms bytes nattch status
|
||||
```
|
||||
|
||||
Create a 3rd container using the new --ipc=container:CONTAINERID option, now it shows the shared memory segment from the first:
|
||||
|
||||
```
|
||||
$ kpod run -it --ipc=container:ed735b2264ac shm ipcs -m
|
||||
$ sudo ipcs -m
|
||||
|
||||
------ Shared Memory Segments --------
|
||||
key shmid owner perms bytes nattch status
|
||||
0x0000162e 0 root 666 27 1
|
||||
```
|
||||
|
||||
## Mapping Ports for External Usage
|
||||
|
||||
The exposed port of an application can be mapped to a host port using the **-p**
|
||||
flag. For example, an httpd port 80 can be mapped to the host port 8080 using the
|
||||
following:
|
||||
|
||||
# kpod run -p 8080:80 -d -i -t fedora/httpd
|
||||
|
||||
## Creating and Mounting a Data Volume Container
|
||||
|
||||
Many applications require the sharing of persistent data across several
|
||||
containers. kpod allows you to create a Data Volume Container that other
|
||||
containers can mount from. For example, create a named container that contains
|
||||
directories /var/volume1 and /tmp/volume2. The image will need to contain these
|
||||
directories so a couple of RUN mkdir instructions might be required for you
|
||||
fedora-data image:
|
||||
|
||||
# kpod run --name=data -v /var/volume1 -v /tmp/volume2 -i -t fedora-data true
|
||||
# kpod run --volumes-from=data --name=fedora-container1 -i -t fedora bash
|
||||
|
||||
Multiple --volumes-from parameters will bring together multiple data volumes from
|
||||
multiple containers. And it's possible to mount the volumes that came from the
|
||||
DATA container in yet another container via the fedora-container1 intermediary
|
||||
container, allowing to abstract the actual data source from users of that data:
|
||||
|
||||
# kpod run --volumes-from=fedora-container1 --name=fedora-container2 -i -t fedora bash
|
||||
|
||||
## Mounting External Volumes
|
||||
|
||||
To mount a host directory as a container volume, specify the absolute path to
|
||||
the directory and the absolute path for the container directory separated by a
|
||||
colon:
|
||||
|
||||
# kpod run -v /var/db:/data1 -i -t fedora bash
|
||||
|
||||
When using SELinux, be aware that the host has no knowledge of container SELinux
|
||||
policy. Therefore, in the above example, if SELinux policy is enforced, the
|
||||
`/var/db` directory is not writable to the container. A "Permission Denied"
|
||||
message will occur and an avc: message in the host's syslog.
|
||||
|
||||
|
||||
To work around this, at time of writing this man page, the following command
|
||||
needs to be run in order for the proper SELinux policy type label to be attached
|
||||
to the host directory:
|
||||
|
||||
# chcon -Rt svirt_sandbox_file_t /var/db
|
||||
|
||||
|
||||
Now, writing to the /data1 volume in the container will be allowed and the
|
||||
changes will also be reflected on the host in /var/db.
|
||||
|
||||
## Using alternative security labeling
|
||||
|
||||
You can override the default labeling scheme for each container by specifying
|
||||
the `--security-opt` flag. For example, you can specify the MCS/MLS level, a
|
||||
requirement for MLS systems. Specifying the level in the following command
|
||||
allows you to share the same content between containers.
|
||||
|
||||
# kpod run --security-opt label=level:s0:c100,c200 -i -t fedora bash
|
||||
|
||||
An MLS example might be:
|
||||
|
||||
# kpod run --security-opt label=level:TopSecret -i -t rhel7 bash
|
||||
|
||||
To disable the security labeling for this container versus running with the
|
||||
`--permissive` flag, use the following command:
|
||||
|
||||
# kpod run --security-opt label=disable -i -t fedora bash
|
||||
|
||||
If you want a tighter security policy on the processes within a container,
|
||||
you can specify an alternate type for the container. You could run a container
|
||||
that is only allowed to listen on Apache ports by executing the following
|
||||
command:
|
||||
|
||||
# kpod run --security-opt label=type:svirt_apache_t -i -t centos bash
|
||||
|
||||
Note:
|
||||
|
||||
You would have to write policy defining a `svirt_apache_t` type.
|
||||
|
||||
## Setting device weight
|
||||
|
||||
If you want to set `/dev/sda` device weight to `200`, you can specify the device
|
||||
weight by `--blkio-weight-device` flag. Use the following command:
|
||||
|
||||
# kpod run -it --blkio-weight-device "/dev/sda:200" ubuntu
|
||||
|
||||
```
|
||||
$ kpod run -d busybox top
|
||||
```
|
||||
|
||||
## Setting Namespaced Kernel Parameters (Sysctls)
|
||||
|
||||
The `--sysctl` sets namespaced kernel parameters (sysctls) in the
|
||||
container. For example, to turn on IP forwarding in the containers
|
||||
network namespace, run this command:
|
||||
|
||||
$ kpod run --sysctl net.ipv4.ip_forward=1 someimage
|
||||
|
||||
Note:
|
||||
|
||||
Not all sysctls are namespaced. kpod does not support changing sysctls
|
||||
inside of a container that also modify the host system. As the kernel
|
||||
evolves we expect to see more sysctls become namespaced.
|
||||
|
||||
See the definition of the `--sysctl` option above for the current list of
|
||||
supported sysctls.
|
||||
|
||||
# HISTORY
|
||||
April 2014, Originally compiled by William Henry (whenry at redhat dot com)
|
||||
based on docker.com source material and internal work.
|
||||
June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
July 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
November 2015, updated by Sally O'Malley <somalley@redhat.com>
|
||||
October 2017, converted from Docker documentation to kpod by Dan Walsh for kpod <dwalsh@redhat.com>
|
|
@ -1,60 +0,0 @@
|
|||
% kpod(1) kpod-save - Simple tool to save an image to an archive
|
||||
% Urvashi Mohnani
|
||||
# kpod-save "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-save - Save an image to docker-archive or oci-archive
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod save**
|
||||
**NAME[:TAG]**
|
||||
[**--quiet**|**-q**]
|
||||
[**--format**]
|
||||
[**--output**|**-o**]
|
||||
[**--help**|**-h**]
|
||||
|
||||
## DESCRIPTION
|
||||
**kpod save** saves an image to either **docker-archive** or **oci-archive**
|
||||
on the local machine, default is **docker-archive**.
|
||||
**kpod save** writes to STDOUT by default and can be redirected to a file using the **output** flag.
|
||||
The **quiet** flag suppresses the output when set.
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod save [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod save [OPTIONS] NAME[:TAG]**
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--output, -o**
|
||||
Write to a file, default is STDOUT
|
||||
|
||||
**--format**
|
||||
Save image to **oci-archive**
|
||||
```
|
||||
--format oci-archive
|
||||
```
|
||||
|
||||
**--quiet, -q**
|
||||
Suppress the output
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
```
|
||||
# kpod save --quiet -o alpine.tar alpine:2.6
|
||||
```
|
||||
|
||||
```
|
||||
# kpod save > alpine-all.tar alpine
|
||||
```
|
||||
|
||||
```
|
||||
# kpod save -o oci-alpine.tar --format oci-archive alpine
|
||||
```
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-load(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -1,37 +0,0 @@
|
|||
% kpod(1) kpod-stats - Display a live stream of 1 or more containers' resource usage statistics
|
||||
% Ryan Cole
|
||||
# kpod-stats "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-stats - Display a live stream of 1 or more containers' resource usage statistics
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **stats** [*options* [...]] [container]
|
||||
|
||||
## DESCRIPTION
|
||||
Display a live stream of one or more containers' resource usage statistics
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--all, -a**
|
||||
|
||||
Show all containers. Only running containers are shown by default
|
||||
|
||||
**--no-stream**
|
||||
|
||||
Disable streaming stats and only pull the first result, default setting is false
|
||||
|
||||
**--format="TEMPLATE"**
|
||||
|
||||
Pretty-print images using a Go template
|
||||
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
TODO
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1)
|
||||
|
||||
## HISTORY
|
||||
July 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
|
|
@ -1,35 +0,0 @@
|
|||
% kpod(1) kpod-stop - Stop one or more containers
|
||||
% Brent Baude
|
||||
# kpod-stop "1" "September 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod stop - Stop one or more containers
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod stop [OPTIONS] CONTAINER [...]**
|
||||
|
||||
## DESCRIPTION
|
||||
Stops one or more containers. You may use container IDs or names as input. The **--timeout** switch
|
||||
allows you to specify the number of seconds to wait before forcibly stopping the container after the stop command
|
||||
is issued to the container. The default is 10 seconds.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
**--timeout, t**
|
||||
|
||||
Timeout to wait before forcibly stopping the container
|
||||
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod stop mywebserver
|
||||
|
||||
kpod stop 860a4b23
|
||||
|
||||
kpod stop --timeout 2 860a4b23
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-rm(1)
|
||||
|
||||
## HISTORY
|
||||
September 2018, Originally compiled by Brent Baude <bbaude@redhat.com>
|
|
@ -1,34 +0,0 @@
|
|||
% kpod(1) kpod-tag - Add tags to an image
|
||||
% Ryan Cole
|
||||
# kpod-tag "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod tag - Add an additional name to a local image
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod tag**
|
||||
[**--help**|**-h**]
|
||||
|
||||
## DESCRIPTION
|
||||
Assigns a new alias to an image in a registry. An alias refers to the entire image name, including the optional **TAG** after the ':'
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod [GLOBAL OPTIONS] tag [OPTIONS]**
|
||||
|
||||
## GLOBAL OPTIONS
|
||||
|
||||
**--help, -h**
|
||||
Print usage statement
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
kpod tag 0e3bbc2 fedora:latest
|
||||
|
||||
kpod tag httpd myregistryhost:5000/fedora/httpd:v2
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
July 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
|
|
@ -1,19 +0,0 @@
|
|||
% kpod(1) kpod-umount - Unmount a working container's root filesystem.
|
||||
% Dan Walsh
|
||||
# kpod-umount "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod umount - Unmount a working container's root file system
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** **umount** **containerID**
|
||||
|
||||
## DESCRIPTION
|
||||
Unmounts the specified container's root file system.
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod umount containerID
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-mount(1)
|
|
@ -1,24 +0,0 @@
|
|||
% kpod(1) kpod-unpause - Unpause one or more containers
|
||||
% Dan Walsh
|
||||
# kpod-unpause "1" "September 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod unpause - Unpause one or more containers
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod unpause [OPTIONS] CONTAINER [...]**
|
||||
|
||||
## DESCRIPTION
|
||||
Unpauses the processes in one or more containers. You may use container IDs or names as input.
|
||||
|
||||
## EXAMPLE
|
||||
|
||||
kpod unpause mywebserver
|
||||
|
||||
kpod unpause 860a4b23
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), kpod-pause(1)
|
||||
|
||||
## HISTORY
|
||||
September 2017, Originally compiled by Dan Walsh <dwalsh@redhat.com>
|
|
@ -1,24 +0,0 @@
|
|||
% kpod(1) kpod-version - Simple tool to view version information
|
||||
% Urvashi Mohnani
|
||||
# kpod-version "1" "July 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod-version - Display the KPOD Version Information
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod version**
|
||||
[**--help**|**-h**]
|
||||
|
||||
## DESCRIPTION
|
||||
Shows the the following information: Version, Go Version, Git Commit, Build Time,
|
||||
OS, and Architecture.
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
**kpod version**
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com>
|
|
@ -1,36 +0,0 @@
|
|||
% kpod(1) kpod-wait - Waits on a container
|
||||
% Brent Baude
|
||||
# kpod-wait "1" "September 2017" "kpod"
|
||||
|
||||
## NAME
|
||||
kpod wait - Waits on one or more containers to stop and prints exit code
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod wait**
|
||||
[**--help**|**-h**]
|
||||
|
||||
## DESCRIPTION
|
||||
Waits on one or more containers to stop. The container can be referred to by its
|
||||
name or ID. In the case of multiple containers, kpod will wait on each consecutively.
|
||||
After the container stops, the container's return code is printed.
|
||||
|
||||
**kpod [GLOBAL OPTIONS] wait **
|
||||
|
||||
## GLOBAL OPTIONS
|
||||
|
||||
**--help, -h**
|
||||
Print usage statement
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
kpod wait mywebserver
|
||||
|
||||
kpod wait 860a4b23
|
||||
|
||||
kpod wait mywebserver myftpserver
|
||||
|
||||
## SEE ALSO
|
||||
kpod(1), crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
September 2017, Originally compiled by Brent Baude<bbaude@redhat.com>
|
143
docs/kpod.1.md
143
docs/kpod.1.md
|
@ -1,143 +0,0 @@
|
|||
% kpod(1) kpod - Simple management tool for pods and images
|
||||
% Dan Walsh
|
||||
# kpod "1" "September 2016" "kpod"
|
||||
## NAME
|
||||
kpod - Simple management tool for containers and images
|
||||
|
||||
## SYNOPSIS
|
||||
**kpod** [*options*] COMMAND
|
||||
|
||||
# DESCRIPTION
|
||||
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.
|
||||
|
||||
**kpod [GLOBAL OPTIONS]**
|
||||
|
||||
## GLOBAL OPTIONS
|
||||
|
||||
**--help, -h**
|
||||
Print usage statement
|
||||
|
||||
**--config value, -c**=**"config.file"**
|
||||
Path of a config file detailing container server configuration options
|
||||
|
||||
**--log-level**
|
||||
log messages above specified level: debug, info, warn, error (default), fatal or panic
|
||||
|
||||
**--root**=**value**
|
||||
Path to the root directory in which data, including images, is stored
|
||||
|
||||
**--runroot**=**value**
|
||||
Path to the 'run directory' where all state information is stored
|
||||
|
||||
**--runtime**=**value**
|
||||
Path to the OCI compatible binary used to run containers
|
||||
|
||||
**--storage-driver, -s**=**value**
|
||||
Select which storage driver is used to manage storage of images and containers (default is overlay)
|
||||
|
||||
**--storage-opt**=**value**
|
||||
Used to pass an option to the storage driver
|
||||
|
||||
**--version, -v**
|
||||
Print the version
|
||||
|
||||
## COMMANDS
|
||||
|
||||
### create
|
||||
create a new container
|
||||
|
||||
### diff
|
||||
Inspect changes on a container or image's filesystem
|
||||
|
||||
### export
|
||||
Export container's filesystem contents as a tar archive
|
||||
|
||||
### history
|
||||
Shows the history of an image
|
||||
|
||||
### images
|
||||
List images in local storage
|
||||
|
||||
### info
|
||||
Displays system information
|
||||
|
||||
### inspect
|
||||
Display a container or image's configuration
|
||||
|
||||
### kill
|
||||
Kill the main process in one or more containers
|
||||
|
||||
### load
|
||||
Load an image from docker archive
|
||||
|
||||
### login
|
||||
Login to a container registry
|
||||
|
||||
### logout
|
||||
Logout of a container registry
|
||||
|
||||
### logs
|
||||
Display the logs of a container
|
||||
|
||||
### mount
|
||||
Mount a working container's root filesystem
|
||||
|
||||
### pause
|
||||
Pause one or more containers
|
||||
|
||||
### ps
|
||||
Prints out information about containers
|
||||
|
||||
### pull
|
||||
Pull an image from a registry
|
||||
|
||||
### push
|
||||
Push an image from local storage to elsewhere
|
||||
|
||||
### rename
|
||||
Rename a container
|
||||
|
||||
### rm
|
||||
Remove one or more containers
|
||||
|
||||
### rmi
|
||||
Removes one or more locally stored images
|
||||
|
||||
### run
|
||||
Run a command in a new container
|
||||
|
||||
### save
|
||||
Save an image to docker-archive or oci
|
||||
|
||||
### stats
|
||||
Display a live stream of one or more containers' resource usage statistics
|
||||
|
||||
### stop
|
||||
Stops one or more running containers.
|
||||
|
||||
### tag
|
||||
Add an additional name to a local image
|
||||
|
||||
### umount
|
||||
Unmount a working container's root file system
|
||||
|
||||
### unpause
|
||||
Unpause one or more containers
|
||||
|
||||
### version
|
||||
Display the version information
|
||||
|
||||
### wait
|
||||
Wait on one or more containers to stop and print their exit codes
|
||||
|
||||
## SEE ALSO
|
||||
crio(8), crio.conf(5)
|
||||
|
||||
## HISTORY
|
||||
Dec 2016, Originally compiled by Dan Walsh <dwalsh@redhat.com>
|
18446
kpod-images.json
18446
kpod-images.json
File diff suppressed because it is too large
Load diff
|
@ -1,210 +0,0 @@
|
|||
package libkpod
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
|
||||
"github.com/kubernetes-incubator/cri-o/libpod/driver"
|
||||
"github.com/kubernetes-incubator/cri-o/libpod/images"
|
||||
"github.com/kubernetes-incubator/cri-o/oci"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ContainerData handles the data used when inspecting a container
|
||||
type ContainerData struct {
|
||||
ID string
|
||||
Name string
|
||||
LogPath string
|
||||
Labels fields.Set
|
||||
Annotations fields.Set
|
||||
State *ContainerState
|
||||
Metadata *pb.ContainerMetadata
|
||||
BundlePath string
|
||||
StopSignal string
|
||||
FromImage string `json:"Image,omitempty"`
|
||||
FromImageID string `json:"ImageID"`
|
||||
MountPoint string `json:"Mountpoint,omitempty"`
|
||||
MountLabel string
|
||||
Mounts []specs.Mount
|
||||
AppArmorProfile string
|
||||
ImageAnnotations map[string]string `json:"Annotations,omitempty"`
|
||||
ImageCreatedBy string `json:"CreatedBy,omitempty"`
|
||||
Config v1.ImageConfig `json:"Config,omitempty"`
|
||||
SizeRw uint `json:"SizeRw,omitempty"`
|
||||
SizeRootFs uint `json:"SizeRootFs,omitempty"`
|
||||
Args []string
|
||||
ResolvConfPath string
|
||||
HostnamePath string
|
||||
HostsPath string
|
||||
GraphDriver driverData
|
||||
}
|
||||
|
||||
type driverData struct {
|
||||
Name string
|
||||
Data map[string]string
|
||||
}
|
||||
|
||||
// ContainerState represents the status of a container.
|
||||
type ContainerState struct {
|
||||
specs.State
|
||||
Created time.Time `json:"created"`
|
||||
Started time.Time `json:"started,omitempty"`
|
||||
Finished time.Time `json:"finished,omitempty"`
|
||||
ExitCode int32 `json:"exitCode"`
|
||||
OOMKilled bool `json:"oomKilled,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// GetContainerData gets the ContainerData for a container with the given name in the given store.
|
||||
// If size is set to true, it will also determine the size of the container
|
||||
func (c *ContainerServer) GetContainerData(name string, size bool) (*ContainerData, error) {
|
||||
ctr, err := c.inspectContainer(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading build container %q", name)
|
||||
}
|
||||
container, err := c.store.Container(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading container data")
|
||||
}
|
||||
|
||||
// The runtime configuration won't exist if the container has never been started by cri-o or kpod,
|
||||
// so treat a not-exist error as non-fatal.
|
||||
m := getBlankSpec()
|
||||
config, err := c.store.FromContainerDirectory(ctr.ID(), "config.json")
|
||||
if err != nil && !os.IsNotExist(errors.Cause(err)) {
|
||||
return nil, err
|
||||
}
|
||||
if len(config) > 0 {
|
||||
if err = json.Unmarshal(config, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if container.ImageID == "" {
|
||||
return nil, errors.Errorf("error reading container image data: container is not based on an image")
|
||||
}
|
||||
imageData, err := images.GetData(c.store, container.ImageID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading container image data")
|
||||
}
|
||||
|
||||
driverName, err := driver.GetDriverName(c.store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
topLayer, err := c.GetContainerTopLayerID(ctr.ID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layer, err := c.store.Layer(topLayer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driverMetadata, err := driver.GetDriverMetadata(c.store, topLayer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageName := ""
|
||||
if len(imageData.Tags) > 0 {
|
||||
imageName = imageData.Tags[0]
|
||||
} else if len(imageData.Digests) > 0 {
|
||||
imageName = imageData.Digests[0]
|
||||
}
|
||||
data := &ContainerData{
|
||||
ID: ctr.ID(),
|
||||
Name: ctr.Name(),
|
||||
LogPath: ctr.LogPath(),
|
||||
Labels: ctr.Labels(),
|
||||
Annotations: ctr.Annotations(),
|
||||
State: c.State(ctr),
|
||||
Metadata: ctr.Metadata(),
|
||||
BundlePath: ctr.BundlePath(),
|
||||
StopSignal: ctr.GetStopSignal(),
|
||||
Args: m.Process.Args,
|
||||
FromImage: imageName,
|
||||
FromImageID: container.ImageID,
|
||||
MountPoint: layer.MountPoint,
|
||||
ImageAnnotations: imageData.Annotations,
|
||||
ImageCreatedBy: imageData.CreatedBy,
|
||||
Config: imageData.Config,
|
||||
GraphDriver: driverData{
|
||||
Name: driverName,
|
||||
Data: driverMetadata,
|
||||
},
|
||||
MountLabel: m.Linux.MountLabel,
|
||||
Mounts: m.Mounts,
|
||||
AppArmorProfile: m.Process.ApparmorProfile,
|
||||
ResolvConfPath: "",
|
||||
HostnamePath: "",
|
||||
HostsPath: "",
|
||||
}
|
||||
|
||||
if size {
|
||||
sizeRootFs, err := c.GetContainerRootFsSize(data.ID)
|
||||
if err != nil {
|
||||
|
||||
return nil, errors.Wrapf(err, "error reading size for container %q", name)
|
||||
}
|
||||
data.SizeRootFs = uint(sizeRootFs)
|
||||
sizeRw, err := c.GetContainerRwSize(data.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading RWSize for container %q", name)
|
||||
}
|
||||
data.SizeRw = uint(sizeRw)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// Get an oci.Container and update its status
|
||||
func (c *ContainerServer) inspectContainer(container string) (*oci.Container, error) {
|
||||
ociCtr, err := c.LookupContainer(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// call runtime.UpdateStatus()
|
||||
err = c.Runtime().UpdateStatus(ociCtr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ociCtr, nil
|
||||
}
|
||||
|
||||
func getBlankSpec() specs.Spec {
|
||||
return specs.Spec{
|
||||
Process: &specs.Process{},
|
||||
Root: &specs.Root{},
|
||||
Mounts: []specs.Mount{},
|
||||
Hooks: &specs.Hooks{},
|
||||
Annotations: make(map[string]string),
|
||||
Linux: &specs.Linux{},
|
||||
Solaris: &specs.Solaris{},
|
||||
Windows: &specs.Windows{},
|
||||
}
|
||||
}
|
||||
|
||||
// State copies the crio container state to ContainerState type for kpod
|
||||
func (c *ContainerServer) State(ctr *oci.Container) *ContainerState {
|
||||
crioState := ctr.State()
|
||||
specState := specs.State{
|
||||
Version: crioState.Version,
|
||||
ID: crioState.ID,
|
||||
Status: crioState.Status,
|
||||
Pid: crioState.Pid,
|
||||
Bundle: crioState.Bundle,
|
||||
Annotations: crioState.Annotations,
|
||||
}
|
||||
cState := &ContainerState{
|
||||
Started: crioState.Started,
|
||||
Created: crioState.Created,
|
||||
Finished: crioState.Finished,
|
||||
}
|
||||
cState.State = specState
|
||||
return cState
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
cp "github.com/containers/image/copy"
|
||||
"github.com/containers/image/signature"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoPassword is returned if the user did not supply a password
|
||||
ErrNoPassword = errors.Wrapf(syscall.EINVAL, "password was not supplied")
|
||||
)
|
||||
|
||||
// GetCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters
|
||||
func GetCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, authFile string) *cp.Options {
|
||||
if srcDockerRegistry == nil {
|
||||
srcDockerRegistry = &DockerRegistryOptions{}
|
||||
}
|
||||
if destDockerRegistry == nil {
|
||||
destDockerRegistry = &DockerRegistryOptions{}
|
||||
}
|
||||
srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath, authFile)
|
||||
destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath, authFile)
|
||||
return &cp.Options{
|
||||
RemoveSignatures: signing.RemoveSignatures,
|
||||
SignBy: signing.SignBy,
|
||||
ReportWriter: reportWriter,
|
||||
SourceCtx: srcContext,
|
||||
DestinationCtx: destContext,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSystemContext Constructs a new containers/image/types.SystemContext{} struct from the given signaturePolicy path
|
||||
func GetSystemContext(signaturePolicyPath, authFilePath string) *types.SystemContext {
|
||||
sc := &types.SystemContext{}
|
||||
if signaturePolicyPath != "" {
|
||||
sc.SignaturePolicyPath = signaturePolicyPath
|
||||
}
|
||||
sc.AuthFilePath = authFilePath
|
||||
return sc
|
||||
}
|
||||
|
||||
// CopyStringStringMap deep copies a map[string]string and returns the result
|
||||
func CopyStringStringMap(m map[string]string) map[string]string {
|
||||
n := map[string]string{}
|
||||
for k, v := range m {
|
||||
n[k] = v
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// IsTrue determines whether the given string equals "true"
|
||||
func IsTrue(str string) bool {
|
||||
return str == "true"
|
||||
}
|
||||
|
||||
// IsFalse determines whether the given string equals "false"
|
||||
func IsFalse(str string) bool {
|
||||
return str == "false"
|
||||
}
|
||||
|
||||
// IsValidBool determines whether the given string equals "true" or "false"
|
||||
func IsValidBool(str string) bool {
|
||||
return IsTrue(str) || IsFalse(str)
|
||||
}
|
||||
|
||||
// GetPolicyContext creates a signature policy context for the given signature policy path
|
||||
func GetPolicyContext(path string) (*signature.PolicyContext, error) {
|
||||
policy, err := signature.DefaultPolicy(&types.SystemContext{SignaturePolicyPath: path})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signature.NewPolicyContext(policy)
|
||||
}
|
||||
|
||||
// ParseRegistryCreds takes a credentials string in the form USERNAME:PASSWORD
|
||||
// and returns a DockerAuthConfig
|
||||
func ParseRegistryCreds(creds string) (*types.DockerAuthConfig, error) {
|
||||
if creds == "" {
|
||||
return nil, errors.New("no credentials supplied")
|
||||
}
|
||||
if !strings.Contains(creds, ":") {
|
||||
return &types.DockerAuthConfig{
|
||||
Username: creds,
|
||||
Password: "",
|
||||
}, ErrNoPassword
|
||||
}
|
||||
v := strings.SplitN(creds, ":", 2)
|
||||
cfg := &types.DockerAuthConfig{
|
||||
Username: v[0],
|
||||
Password: v[1],
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package common
|
||||
|
||||
import "github.com/containers/image/types"
|
||||
|
||||
// DockerRegistryOptions encapsulates settings that affect how we connect or
|
||||
// authenticate to a remote registry.
|
||||
type DockerRegistryOptions struct {
|
||||
// DockerRegistryCreds is the user name and password to supply in case
|
||||
// we need to pull an image from a registry, and it requires us to
|
||||
// authenticate.
|
||||
DockerRegistryCreds *types.DockerAuthConfig
|
||||
// DockerCertPath is the location of a directory containing CA
|
||||
// certificates which will be used to verify the registry's certificate
|
||||
// (all files with names ending in ".crt"), and possibly client
|
||||
// certificates and private keys (pairs of files with the same name,
|
||||
// except for ".cert" and ".key" suffixes).
|
||||
DockerCertPath string
|
||||
// DockerInsecureSkipTLSVerify turns off verification of TLS
|
||||
// certificates and allows connecting to registries without encryption.
|
||||
DockerInsecureSkipTLSVerify bool
|
||||
}
|
||||
|
||||
// GetSystemContext constructs a new system context from the given signaturePolicy path and the
|
||||
// values in the DockerRegistryOptions
|
||||
func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath, authFile string) *types.SystemContext {
|
||||
sc := &types.SystemContext{
|
||||
SignaturePolicyPath: signaturePolicyPath,
|
||||
DockerAuthConfig: o.DockerRegistryCreds,
|
||||
DockerCertPath: o.DockerCertPath,
|
||||
DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify,
|
||||
AuthFilePath: authFile,
|
||||
}
|
||||
return sc
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
package common
|
|
@ -1,10 +0,0 @@
|
|||
package common
|
||||
|
||||
// SigningOptions encapsulates settings that control whether or not we strip or
|
||||
// add signatures to images when writing them.
|
||||
type SigningOptions struct {
|
||||
// RemoveSignatures directs us to remove any signatures which are already present.
|
||||
RemoveSignatures bool
|
||||
// SignBy is a key identifier of some kind, indicating that a signature should be generated using the specified private key and stored with the image.
|
||||
SignBy string
|
||||
}
|
|
@ -1,454 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containers/storage"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
crioAnnotations "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/ulule/deepcopier"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
// ContainerState represents the current state of a container
|
||||
type ContainerState int
|
||||
|
||||
const (
|
||||
// ContainerStateUnknown indicates that the container is in an error
|
||||
// state where information about it cannot be retrieved
|
||||
ContainerStateUnknown ContainerState = iota
|
||||
// ContainerStateConfigured indicates that the container has had its
|
||||
// storage configured but it has not been created in the OCI runtime
|
||||
ContainerStateConfigured ContainerState = iota
|
||||
// ContainerStateCreated indicates the container has been created in
|
||||
// the OCI runtime but not started
|
||||
ContainerStateCreated ContainerState = iota
|
||||
// ContainerStateRunning indicates the container is currently executing
|
||||
ContainerStateRunning ContainerState = iota
|
||||
// ContainerStateStopped indicates that the container was running but has
|
||||
// exited
|
||||
ContainerStateStopped ContainerState = iota
|
||||
// ContainerStatePaused indicates that the container has been paused
|
||||
ContainerStatePaused ContainerState = iota
|
||||
)
|
||||
|
||||
// Container is a single OCI container
|
||||
type Container struct {
|
||||
config *containerConfig
|
||||
|
||||
pod *Pod
|
||||
runningSpec *spec.Spec
|
||||
|
||||
state *containerRuntimeInfo
|
||||
|
||||
// TODO move to storage.Locker from sync.Mutex
|
||||
valid bool
|
||||
lock sync.Mutex
|
||||
runtime *Runtime
|
||||
}
|
||||
|
||||
// containerState contains the current state of the container
|
||||
// It is stored on disk in a tmpfs and recreated on reboot
|
||||
type containerRuntimeInfo struct {
|
||||
// The current state of the running container
|
||||
State ContainerState `json:"state"`
|
||||
// The path to the JSON OCI runtime spec for this container
|
||||
ConfigPath string `json:"configPath,omitempty"`
|
||||
// RunDir is a per-boot directory for container content
|
||||
RunDir string `json:"runDir,omitempty"`
|
||||
// Mounted indicates whether the container's storage has been mounted
|
||||
// for use
|
||||
Mounted bool `json:"-"`
|
||||
// MountPoint contains the path to the container's mounted storage
|
||||
Mountpoint string `json:"mountPoint,omitempty"`
|
||||
// StartedTime is the time the container was started
|
||||
StartedTime time.Time `json:"startedTime,omitempty"`
|
||||
// FinishedTime is the time the container finished executing
|
||||
FinishedTime time.Time `json:"finishedTime,omitempty"`
|
||||
// ExitCode is the exit code returned when the container stopped
|
||||
ExitCode int32 `json:"exitCode,omitempty"`
|
||||
|
||||
// TODO: Save information about image used in container if one is used
|
||||
}
|
||||
|
||||
// containerConfig contains all information that was used to create the
|
||||
// container. It may not be changed once created.
|
||||
// It is stored, read-only, on disk
|
||||
type containerConfig struct {
|
||||
Spec *spec.Spec `json:"spec"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
// RootfsFromImage indicates whether the container uses a root
|
||||
// filesystem from an image, or from a user-provided directory
|
||||
RootfsFromImage bool
|
||||
// Directory used as a root filesystem if not configured with an image
|
||||
RootfsDir string `json:"rootfsDir,omitempty"`
|
||||
// Information on the image used for the root filesystem
|
||||
RootfsImageID string `json:"rootfsImageID,omitempty"`
|
||||
RootfsImageName string `json:"rootfsImageName,omitempty"`
|
||||
UseImageConfig bool `json:"useImageConfig"`
|
||||
// Whether to keep container STDIN open
|
||||
Stdin bool
|
||||
// Static directory for container content that will persist across
|
||||
// reboot
|
||||
StaticDir string `json:"staticDir"`
|
||||
// Pod the container belongs to
|
||||
Pod string `json:"pod,omitempty"`
|
||||
// Labels is a set of key-value pairs providing additional information
|
||||
// about a container
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
// StopSignal is the signal that will be used to stop the container
|
||||
StopSignal uint `json:"stopSignal,omitempty"`
|
||||
// Shared namespaces with container
|
||||
SharedNamespaceCtr *string `json:"shareNamespacesWith,omitempty"`
|
||||
SharedNamespaceMap map[string]string `json:"sharedNamespaces"`
|
||||
// Time container was created
|
||||
CreatedTime time.Time `json:"createdTime"`
|
||||
|
||||
// TODO save log location here and pass into OCI code
|
||||
// TODO allow overriding of log path
|
||||
}
|
||||
|
||||
// ID returns the container's ID
|
||||
func (c *Container) ID() string {
|
||||
return c.config.ID
|
||||
}
|
||||
|
||||
// Name returns the container's name
|
||||
func (c *Container) Name() string {
|
||||
return c.config.Name
|
||||
}
|
||||
|
||||
// Spec returns the container's OCI runtime spec
|
||||
// The spec returned is the one used to create the container. The running
|
||||
// spec may differ slightly as mounts are added based on the image
|
||||
func (c *Container) Spec() *spec.Spec {
|
||||
spec := new(spec.Spec)
|
||||
deepcopier.Copy(c.config.Spec).To(spec)
|
||||
|
||||
return spec
|
||||
}
|
||||
|
||||
// Labels returns the container's labels
|
||||
func (c *Container) Labels() map[string]string {
|
||||
labels := make(map[string]string)
|
||||
for key, value := range c.config.Labels {
|
||||
labels[key] = value
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
// State returns the current state of the container
|
||||
func (c *Container) State() (ContainerState, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
// TODO uncomment when working
|
||||
// if err := c.runtime.ociRuntime.updateContainerStatus(c); err != nil {
|
||||
// return ContainerStateUnknown, err
|
||||
// }
|
||||
|
||||
return c.state.State, nil
|
||||
}
|
||||
|
||||
// The path to the container's root filesystem - where the OCI spec will be
|
||||
// placed, amongst other things
|
||||
func (c *Container) bundlePath() string {
|
||||
return c.state.RunDir
|
||||
}
|
||||
|
||||
// Retrieves the path of the container's attach socket
|
||||
func (c *Container) attachSocketPath() string {
|
||||
return filepath.Join(c.runtime.ociRuntime.socketsDir, c.ID(), "attach")
|
||||
}
|
||||
|
||||
// Make a new container
|
||||
func newContainer(rspec *spec.Spec) (*Container, error) {
|
||||
if rspec == nil {
|
||||
return nil, errors.Wrapf(ErrInvalidArg, "must provide a valid runtime spec to create container")
|
||||
}
|
||||
|
||||
ctr := new(Container)
|
||||
ctr.config = new(containerConfig)
|
||||
ctr.state = new(containerRuntimeInfo)
|
||||
|
||||
ctr.config.ID = stringid.GenerateNonCryptoID()
|
||||
ctr.config.Name = ctr.config.ID // TODO generate unique human-readable names
|
||||
|
||||
ctr.config.Spec = new(spec.Spec)
|
||||
deepcopier.Copy(rspec).To(ctr.config.Spec)
|
||||
|
||||
ctr.config.CreatedTime = time.Now()
|
||||
|
||||
return ctr, nil
|
||||
}
|
||||
|
||||
// Create container root filesystem for use
|
||||
func (c *Container) setupStorage() error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if !c.valid {
|
||||
return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID())
|
||||
}
|
||||
|
||||
if c.state.State != ContainerStateConfigured {
|
||||
return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Configured state to have storage set up", c.ID())
|
||||
}
|
||||
|
||||
// If we're configured to use a directory, perform that setup
|
||||
if !c.config.RootfsFromImage {
|
||||
// TODO implement directory-based root filesystems
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// Not using a directory, so call into containers/storage
|
||||
return c.setupImageRootfs()
|
||||
}
|
||||
|
||||
// Set up an image as root filesystem using containers/storage
|
||||
func (c *Container) setupImageRootfs() error {
|
||||
// Need both an image ID and image name, plus a bool telling us whether to use the image configuration
|
||||
if c.config.RootfsImageID == "" || c.config.RootfsImageName == "" {
|
||||
return errors.Wrapf(ErrInvalidArg, "must provide image ID and image name to use an image")
|
||||
}
|
||||
|
||||
// TODO SELinux mount label
|
||||
containerInfo, err := c.runtime.storageService.CreateContainerStorage(c.runtime.imageContext, c.config.RootfsImageName, c.config.RootfsImageID, c.config.Name, c.config.ID, "")
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating container storage")
|
||||
}
|
||||
|
||||
c.config.StaticDir = containerInfo.Dir
|
||||
c.state.RunDir = containerInfo.RunDir
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tear down a container's storage prior to removal
|
||||
func (c *Container) teardownStorage() error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if !c.valid {
|
||||
return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID())
|
||||
}
|
||||
|
||||
if c.state.State == ContainerStateRunning || c.state.State == ContainerStatePaused {
|
||||
return errors.Wrapf(ErrCtrStateInvalid, "cannot remove storage for container %s as it is running or paused", c.ID())
|
||||
}
|
||||
|
||||
if !c.config.RootfsFromImage {
|
||||
// TODO implement directory-based root filesystems
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
return c.teardownImageRootfs()
|
||||
}
|
||||
|
||||
// Completely remove image-based root filesystem for a container
|
||||
func (c *Container) teardownImageRootfs() error {
|
||||
if c.state.Mounted {
|
||||
if err := c.runtime.storageService.StopContainer(c.ID()); err != nil {
|
||||
return errors.Wrapf(err, "error unmounting container %s root filesystem", c.ID())
|
||||
}
|
||||
|
||||
c.state.Mounted = false
|
||||
}
|
||||
|
||||
if err := c.runtime.storageService.DeleteContainer(c.ID()); err != nil {
|
||||
return errors.Wrapf(err, "error removing container %s root filesystem", c.ID())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create creates a container in the OCI runtime
|
||||
func (c *Container) Create() (err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if !c.valid {
|
||||
return errors.Wrapf(ErrCtrRemoved, "container %s is not valid", c.ID())
|
||||
}
|
||||
|
||||
if c.state.State != ContainerStateConfigured {
|
||||
return errors.Wrapf(ErrCtrExists, "container %s has already been created in runtime", c.ID())
|
||||
}
|
||||
|
||||
// If using containers/storage, mount the container
|
||||
if !c.config.RootfsFromImage {
|
||||
// TODO implement directory-based root filesystems
|
||||
if !c.state.Mounted {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
} else {
|
||||
mountPoint, err := c.runtime.storageService.StartContainer(c.ID())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error mounting storage for container %s", c.ID())
|
||||
}
|
||||
c.state.Mounted = true
|
||||
c.state.Mountpoint = mountPoint
|
||||
|
||||
logrus.Debugf("Created root filesystem for container %s at %s", c.ID(), c.state.Mountpoint)
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if err2 := c.runtime.storageService.StopContainer(c.ID()); err2 != nil {
|
||||
logrus.Errorf("Error unmounting storage for container %s: %v", c.ID(), err2)
|
||||
}
|
||||
|
||||
c.state.Mounted = false
|
||||
c.state.Mountpoint = ""
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Make the OCI runtime spec we will use
|
||||
c.runningSpec = new(spec.Spec)
|
||||
deepcopier.Copy(c.config.Spec).To(c.runningSpec)
|
||||
c.runningSpec.Root.Path = c.state.Mountpoint
|
||||
c.runningSpec.Annotations[crioAnnotations.Created] = c.config.CreatedTime.Format(time.RFC3339Nano)
|
||||
c.runningSpec.Annotations["org.opencontainers.image.stopSignal"] = fmt.Sprintf("%d", c.config.StopSignal)
|
||||
|
||||
// Save the OCI spec to disk
|
||||
jsonPath := filepath.Join(c.bundlePath(), "config.json")
|
||||
fileJSON, err := json.Marshal(c.runningSpec)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error exporting runtime spec for container %s to JSON", c.ID())
|
||||
}
|
||||
if err := ioutil.WriteFile(jsonPath, fileJSON, 0644); err != nil {
|
||||
return errors.Wrapf(err, "error writing runtime spec JSON to file for container %s", c.ID())
|
||||
}
|
||||
c.state.ConfigPath = jsonPath
|
||||
|
||||
logrus.Debugf("Created OCI spec for container %s at %s", c.ID(), jsonPath)
|
||||
|
||||
// With the spec complete, do an OCI create
|
||||
// TODO set cgroup parent in a sane fashion
|
||||
if err := c.runtime.ociRuntime.createContainer(c, "/libpod_parent"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("Created container %s in runc", c.ID())
|
||||
|
||||
// TODO should flush this state to disk here
|
||||
c.state.State = ContainerStateCreated
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts a container
|
||||
func (c *Container) Start() error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if !c.valid {
|
||||
return ErrCtrRemoved
|
||||
}
|
||||
|
||||
// Container must be created or stopped to be started
|
||||
if !(c.state.State == ContainerStateCreated || c.state.State == ContainerStateStopped) {
|
||||
return errors.Wrapf(ErrCtrStateInvalid, "container %s must be in Created or Stopped state to be started", c.ID())
|
||||
}
|
||||
|
||||
if err := c.runtime.ociRuntime.startContainer(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logrus.Debugf("Started container %s", c.ID())
|
||||
|
||||
// TODO should flush state to disk here
|
||||
c.state.StartedTime = time.Now()
|
||||
c.state.State = ContainerStateRunning
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops a container
|
||||
func (c *Container) Stop() error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// Kill sends a signal to a container
|
||||
func (c *Container) Kill(signal uint) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// Exec starts a new process inside the container
|
||||
// Returns fully qualified URL of streaming server for executed process
|
||||
func (c *Container) Exec(cmd []string, tty bool, stdin bool) (string, error) {
|
||||
return "", ErrNotImplemented
|
||||
}
|
||||
|
||||
// Attach attaches to a container
|
||||
// Returns fully qualified URL of streaming server for the container
|
||||
func (c *Container) Attach(noStdin bool, keys string) error {
|
||||
// Check the validity of the provided keys first
|
||||
var err error
|
||||
detachKeys := []byte{}
|
||||
if len(keys) > 0 {
|
||||
detachKeys, err = term.ToBytes(keys)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "invalid detach keys")
|
||||
}
|
||||
}
|
||||
cStatus := c.state.State
|
||||
|
||||
if !(cStatus == ContainerStateRunning || cStatus == ContainerStateCreated) {
|
||||
return errors.Errorf("%s is not created or running", c.Name())
|
||||
}
|
||||
resize := make(chan remotecommand.TerminalSize)
|
||||
defer close(resize)
|
||||
err = c.attachContainerSocket(resize, noStdin, detachKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO
|
||||
// Re-enable this when mheon is done wth it
|
||||
//c.ContainerStateToDisk(c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mount mounts a container's filesystem on the host
|
||||
// The path where the container has been mounted is returned
|
||||
func (c *Container) Mount() (string, error) {
|
||||
return "", ErrNotImplemented
|
||||
}
|
||||
|
||||
// Pause pauses a container
|
||||
func (c *Container) Pause() error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// Unpause unpauses a container
|
||||
func (c *Container) Unpause() error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// Export exports a container's root filesystem as a tar archive
|
||||
// The archive will be saved as a file at the given path
|
||||
func (c *Container) Export(path string) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// Commit commits the changes between a container and its image, creating a new
|
||||
// image
|
||||
// If the container was not created from an image (for example,
|
||||
// WithRootFSFromPath will create a container from a directory on the system),
|
||||
// a new base image will be created from the contents of the container's
|
||||
// filesystem
|
||||
func (c *Container) Commit() (*storage.Image, error) {
|
||||
return nil, ErrNotImplemented
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/kubernetes-incubator/cri-o/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
)
|
||||
|
||||
/* Sync with stdpipe_t in conmon.c */
|
||||
const (
|
||||
AttachPipeStdin = 1
|
||||
AttachPipeStdout = 2
|
||||
AttachPipeStderr = 3
|
||||
)
|
||||
|
||||
// attachContainerSocket connects to the container's attach socket and deals with the IO
|
||||
func (c *Container) attachContainerSocket(resize <-chan remotecommand.TerminalSize, noStdIn bool, detachKeys []byte) error {
|
||||
inputStream := os.Stdin
|
||||
outputStream := os.Stdout
|
||||
errorStream := os.Stderr
|
||||
defer inputStream.Close()
|
||||
tty, err := strconv.ParseBool(c.runningSpec.Annotations["io.kubernetes.cri-o.TTY"])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to parse annotations in %s", c.ID)
|
||||
}
|
||||
if !tty {
|
||||
return errors.Errorf("no tty available for %s", c.ID())
|
||||
}
|
||||
|
||||
oldTermState, err := term.SaveState(inputStream.Fd())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to save terminal state")
|
||||
}
|
||||
|
||||
defer term.RestoreTerminal(inputStream.Fd(), oldTermState)
|
||||
|
||||
// Put both input and output into raw
|
||||
if !noStdIn {
|
||||
term.SetRawTerminal(inputStream.Fd())
|
||||
}
|
||||
|
||||
controlPath := filepath.Join(c.state.RunDir, "ctl")
|
||||
controlFile, err := os.OpenFile(controlPath, unix.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open container ctl file: %v")
|
||||
}
|
||||
|
||||
kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
|
||||
logrus.Debugf("Received a resize event: %+v", size)
|
||||
_, err := fmt.Fprintf(controlFile, "%d %d %d\n", 1, size.Height, size.Width)
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to write to control file to resize terminal: %v", err)
|
||||
}
|
||||
})
|
||||
logrus.Debug("connecting to socket ", c.attachSocketPath())
|
||||
|
||||
conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: c.attachSocketPath(), Net: "unixpacket"})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to connect to container's attach socket: %v")
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
receiveStdoutError := make(chan error)
|
||||
if outputStream != nil || errorStream != nil {
|
||||
go func() {
|
||||
receiveStdoutError <- redirectResponseToOutputStreams(outputStream, errorStream, conn)
|
||||
}()
|
||||
}
|
||||
|
||||
stdinDone := make(chan error)
|
||||
go func() {
|
||||
var err error
|
||||
if inputStream != nil && !noStdIn {
|
||||
_, err = utils.CopyDetachable(conn, inputStream, detachKeys)
|
||||
conn.CloseWrite()
|
||||
}
|
||||
stdinDone <- err
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-receiveStdoutError:
|
||||
return err
|
||||
case err := <-stdinDone:
|
||||
if _, ok := err.(utils.DetachError); ok {
|
||||
return nil
|
||||
}
|
||||
if outputStream != nil || errorStream != nil {
|
||||
return <-receiveStdoutError
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func redirectResponseToOutputStreams(outputStream, errorStream io.Writer, conn io.Reader) error {
|
||||
var err error
|
||||
buf := make([]byte, 8192+1) /* Sync with conmon STDIO_BUF_SIZE */
|
||||
for {
|
||||
nr, er := conn.Read(buf)
|
||||
if nr > 0 {
|
||||
var dst io.Writer
|
||||
switch buf[0] {
|
||||
case AttachPipeStdout:
|
||||
dst = outputStream
|
||||
case AttachPipeStderr:
|
||||
dst = errorStream
|
||||
default:
|
||||
logrus.Infof("Received unexpected attach type %+d", buf[0])
|
||||
}
|
||||
|
||||
if dst != nil {
|
||||
nw, ew := dst.Write(buf[1:nr])
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw+1 {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if er == io.EOF {
|
||||
break
|
||||
}
|
||||
if er != nil {
|
||||
err = er
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"github.com/containers/storage/pkg/archive"
|
||||
"github.com/kubernetes-incubator/cri-o/libpod/layers"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// GetDiff returns the differences between the two images, layers, or containers
|
||||
func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) {
|
||||
toLayer, err := r.getLayerID(to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fromLayer := ""
|
||||
if from != "" {
|
||||
fromLayer, err = r.getLayerID(from)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return r.store.Changes(fromLayer, toLayer)
|
||||
}
|
||||
|
||||
// GetLayerID gets a full layer id given a full or partial id
|
||||
// If the id matches a container or image, the id of the top layer is returned
|
||||
// If the id matches a layer, the top layer id is returned
|
||||
func (r *Runtime) getLayerID(id string) (string, error) {
|
||||
var toLayer string
|
||||
toImage, err := r.GetImage(id)
|
||||
if err != nil {
|
||||
toCtr, err := r.store.Container(id)
|
||||
if err != nil {
|
||||
toLayer, err = layers.FullID(r.store, id)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("layer, image, or container %s does not exist", id)
|
||||
}
|
||||
} else {
|
||||
toLayer = toCtr.LayerID
|
||||
}
|
||||
} else {
|
||||
toLayer = toImage.TopLayer
|
||||
}
|
||||
return toLayer, nil
|
||||
}
|
||||
|
||||
func (r *Runtime) getLayerParent(layerID string) (string, error) {
|
||||
layer, err := r.store.Layer(layerID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return layer.Parent, nil
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package driver
|
||||
|
||||
import cstorage "github.com/containers/storage"
|
||||
|
||||
// Data handles the data for a storage driver
|
||||
type Data struct {
|
||||
Name string
|
||||
Data map[string]string
|
||||
}
|
||||
|
||||
// GetDriverName returns the name of the driver for the given store
|
||||
func GetDriverName(store cstorage.Store) (string, error) {
|
||||
driver, err := store.GraphDriver()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return driver.String(), nil
|
||||
}
|
||||
|
||||
// GetDriverMetadata returns the metadata regarding the driver for the layer in the given store
|
||||
func GetDriverMetadata(store cstorage.Store, layerID string) (map[string]string, error) {
|
||||
driver, err := store.GraphDriver()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return driver.Metadata(layerID)
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoSuchCtr indicates the requested container does not exist
|
||||
ErrNoSuchCtr = errors.New("no such container")
|
||||
// ErrNoSuchPod indicates the requested pod does not exist
|
||||
ErrNoSuchPod = errors.New("no such pod")
|
||||
// ErrNoSuchImage indicates the requested image does not exist
|
||||
ErrNoSuchImage = errors.New("no such image")
|
||||
|
||||
// ErrCtrExists indicates a container with the same name or ID already
|
||||
// exists
|
||||
ErrCtrExists = errors.New("container already exists")
|
||||
// ErrPodExists indicates a pod with the same name or ID already exists
|
||||
ErrPodExists = errors.New("pod already exists")
|
||||
// ErrImageExists indicated an image with the same ID already exists
|
||||
ErrImageExists = errors.New("image already exists")
|
||||
|
||||
// ErrCtrStateInvalid indicates a container is in an improper state for
|
||||
// the requested operation
|
||||
ErrCtrStateInvalid = errors.New("container state improper")
|
||||
|
||||
// ErrRuntimeFinalized indicates that the runtime has already been
|
||||
// created and cannot be modified
|
||||
ErrRuntimeFinalized = errors.New("runtime has been finalized")
|
||||
// ErrCtrFinalized indicates that the container has already been created
|
||||
// and cannot be modified
|
||||
ErrCtrFinalized = errors.New("container has been finalized")
|
||||
// ErrPodFinalized indicates that the pod has already been created and
|
||||
// cannot be modified
|
||||
ErrPodFinalized = errors.New("pod has been finalized")
|
||||
|
||||
// ErrInvalidArg indicates that an invalid argument was passed
|
||||
ErrInvalidArg = errors.New("invalid argument")
|
||||
// ErrEmptyID indicates that an empty ID was passed
|
||||
ErrEmptyID = errors.New("name or ID cannot be empty")
|
||||
|
||||
// ErrInternal indicates an internal library error
|
||||
ErrInternal = errors.New("internal libpod error")
|
||||
|
||||
// ErrRuntimeStopped indicates that the runtime has already been shut
|
||||
// down and no further operations can be performed on it
|
||||
ErrRuntimeStopped = errors.New("runtime has already been stopped")
|
||||
// ErrCtrStopped indicates that the requested container is not running
|
||||
// and the requested operation cannot be performed until it is started
|
||||
ErrCtrStopped = errors.New("container is stopped")
|
||||
|
||||
// ErrCtrRemoved indicates that the container has already been removed
|
||||
// and no further operations can be performed on it
|
||||
ErrCtrRemoved = errors.New("container has already been removed")
|
||||
// ErrPodRemoved indicates that the pod has already been removed and no
|
||||
// further operations can be performed on it
|
||||
ErrPodRemoved = errors.New("pod has already been removed")
|
||||
|
||||
// ErrNotImplemented indicates that the requested functionality is not
|
||||
// yet present
|
||||
ErrNotImplemented = errors.New("not yet implemented")
|
||||
)
|
|
@ -1,203 +0,0 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/docker/reference"
|
||||
is "github.com/containers/image/storage"
|
||||
"github.com/containers/image/transports"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage"
|
||||
"github.com/kubernetes-incubator/cri-o/libpod/driver"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Data handles the data used when inspecting a container
|
||||
// nolint
|
||||
type Data struct {
|
||||
ID string
|
||||
Tags []string
|
||||
Digests []string
|
||||
Digest digest.Digest
|
||||
Comment string
|
||||
Created *time.Time
|
||||
Container string
|
||||
Author string
|
||||
Config ociv1.ImageConfig
|
||||
Architecture string
|
||||
OS string
|
||||
Annotations map[string]string
|
||||
CreatedBy string
|
||||
Size uint
|
||||
VirtualSize uint
|
||||
GraphDriver driver.Data
|
||||
RootFS ociv1.RootFS
|
||||
}
|
||||
|
||||
// ParseImageNames parses the names we've stored with an image into a list of
|
||||
// tagged references and a list of references which contain digests.
|
||||
func ParseImageNames(names []string) (tags, digests []string, err error) {
|
||||
for _, name := range names {
|
||||
if named, err := reference.ParseNamed(name); err == nil {
|
||||
if digested, ok := named.(reference.Digested); ok {
|
||||
canonical, err := reference.WithDigest(named, digested.Digest())
|
||||
if err == nil {
|
||||
digests = append(digests, canonical.String())
|
||||
}
|
||||
} else {
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.TagNameOnly(named)
|
||||
}
|
||||
if tagged, ok := named.(reference.Tagged); ok {
|
||||
namedTagged, err := reference.WithTag(named, tagged.Tag())
|
||||
if err == nil {
|
||||
tags = append(tags, namedTagged.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags, digests, nil
|
||||
}
|
||||
|
||||
func annotations(manifest []byte, manifestType string) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
switch manifestType {
|
||||
case ociv1.MediaTypeImageManifest:
|
||||
var m ociv1.Manifest
|
||||
if err := json.Unmarshal(manifest, &m); err == nil {
|
||||
for k, v := range m.Annotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
// GetData gets the Data for a container with the given name in the given store.
|
||||
func GetData(store storage.Store, name string) (*Data, error) {
|
||||
img, err := FindImage(store, name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading image %q", name)
|
||||
}
|
||||
|
||||
imgRef, err := FindImageRef(store, "@"+img.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading image %q", img.ID)
|
||||
}
|
||||
defer imgRef.Close()
|
||||
|
||||
tags, digests, err := ParseImageNames(img.Names)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing image names for %q", name)
|
||||
}
|
||||
|
||||
driverName, err := driver.GetDriverName(store)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading name of storage driver")
|
||||
}
|
||||
|
||||
topLayerID := img.TopLayer
|
||||
|
||||
driverMetadata, err := driver.GetDriverMetadata(store, topLayerID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error asking storage driver %q for metadata", driverName)
|
||||
}
|
||||
|
||||
layer, err := store.Layer(topLayerID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading information about layer %q", topLayerID)
|
||||
}
|
||||
size, err := store.DiffSize(layer.Parent, layer.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error determining size of layer %q", layer.ID)
|
||||
}
|
||||
|
||||
imgSize, err := imgRef.Size()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error determining size of image %q", transports.ImageName(imgRef.Reference()))
|
||||
}
|
||||
|
||||
manifest, manifestType, err := imgRef.Manifest()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading manifest for image %q", img.ID)
|
||||
}
|
||||
manifestDigest := digest.Digest("")
|
||||
if len(manifest) > 0 {
|
||||
manifestDigest = digest.Canonical.FromBytes(manifest)
|
||||
}
|
||||
annotations := annotations(manifest, manifestType)
|
||||
|
||||
config, err := imgRef.OCIConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading image configuration for %q", img.ID)
|
||||
}
|
||||
historyComment := ""
|
||||
historyCreatedBy := ""
|
||||
if len(config.History) > 0 {
|
||||
historyComment = config.History[len(config.History)-1].Comment
|
||||
historyCreatedBy = config.History[len(config.History)-1].CreatedBy
|
||||
}
|
||||
|
||||
return &Data{
|
||||
ID: img.ID,
|
||||
Tags: tags,
|
||||
Digests: digests,
|
||||
Digest: manifestDigest,
|
||||
Comment: historyComment,
|
||||
Created: config.Created,
|
||||
Author: config.Author,
|
||||
Config: config.Config,
|
||||
Architecture: config.Architecture,
|
||||
OS: config.OS,
|
||||
Annotations: annotations,
|
||||
CreatedBy: historyCreatedBy,
|
||||
Size: uint(size),
|
||||
VirtualSize: uint(size + imgSize),
|
||||
GraphDriver: driver.Data{
|
||||
Name: driverName,
|
||||
Data: driverMetadata,
|
||||
},
|
||||
RootFS: config.RootFS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FindImage searches for a *storage.Image with a matching the given name or ID in the given store.
|
||||
func FindImage(store storage.Store, image string) (*storage.Image, error) {
|
||||
var img *storage.Image
|
||||
ref, err := is.Transport.ParseStoreReference(store, image)
|
||||
if err == nil {
|
||||
img, err = is.Transport.GetStoreImage(store, ref)
|
||||
}
|
||||
if err != nil {
|
||||
img2, err2 := store.Image(image)
|
||||
if err2 != nil {
|
||||
if ref == nil {
|
||||
return nil, errors.Wrapf(err, "error parsing reference to image %q", image)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "unable to locate image %q", image)
|
||||
}
|
||||
img = img2
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// FindImageRef searches for and returns a new types.Image matching the given name or ID in the given store.
|
||||
func FindImageRef(store storage.Store, image string) (types.Image, error) {
|
||||
img, err := FindImage(store, image)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to locate image %q", image)
|
||||
}
|
||||
ref, err := is.Transport.ParseStoreReference(store, "@"+img.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID)
|
||||
}
|
||||
imgRef, err := ref.NewImage(nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error reading image %q", img.ID)
|
||||
}
|
||||
return imgRef, nil
|
||||
}
|
|
@ -1,273 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
"github.com/kubernetes-incubator/cri-o/pkg/registrar"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// An InMemoryState is a purely in-memory state store
|
||||
type InMemoryState struct {
|
||||
pods map[string]*Pod
|
||||
containers map[string]*Container
|
||||
podNameIndex *registrar.Registrar
|
||||
podIDIndex *truncindex.TruncIndex
|
||||
ctrNameIndex *registrar.Registrar
|
||||
ctrIDIndex *truncindex.TruncIndex
|
||||
}
|
||||
|
||||
// NewInMemoryState initializes a new, empty in-memory state
|
||||
func NewInMemoryState() (State, error) {
|
||||
state := new(InMemoryState)
|
||||
|
||||
state.pods = make(map[string]*Pod)
|
||||
state.containers = make(map[string]*Container)
|
||||
|
||||
state.podNameIndex = registrar.NewRegistrar()
|
||||
state.ctrNameIndex = registrar.NewRegistrar()
|
||||
|
||||
state.podIDIndex = truncindex.NewTruncIndex([]string{})
|
||||
state.ctrIDIndex = truncindex.NewTruncIndex([]string{})
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// Container retrieves a container from its full ID
|
||||
func (s *InMemoryState) Container(id string) (*Container, error) {
|
||||
if id == "" {
|
||||
return nil, ErrEmptyID
|
||||
}
|
||||
|
||||
ctr, ok := s.containers[id]
|
||||
if !ok {
|
||||
return nil, errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found", id)
|
||||
}
|
||||
|
||||
return ctr, nil
|
||||
}
|
||||
|
||||
// LookupContainer retrieves a container by full ID, unique partial ID, or name
|
||||
func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
|
||||
if idOrName == "" {
|
||||
return nil, ErrEmptyID
|
||||
}
|
||||
|
||||
fullID, err := s.ctrNameIndex.Get(idOrName)
|
||||
if err != nil {
|
||||
if err == registrar.ErrNameNotReserved {
|
||||
// What was passed is not a name, assume it's an ID
|
||||
fullID, err = s.ctrIDIndex.Get(idOrName)
|
||||
if err != nil {
|
||||
if err == truncindex.ErrNotExist {
|
||||
return nil, errors.Wrapf(ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName)
|
||||
}
|
||||
}
|
||||
|
||||
ctr, ok := s.containers[fullID]
|
||||
if !ok {
|
||||
// This should never happen
|
||||
return nil, errors.Wrapf(ErrInternal, "mismatch in container ID registry and containers map for ID %s", fullID)
|
||||
}
|
||||
|
||||
return ctr, nil
|
||||
}
|
||||
|
||||
// HasContainer checks if a container with the given ID is present in the state
|
||||
func (s *InMemoryState) HasContainer(id string) (bool, error) {
|
||||
if id == "" {
|
||||
return false, ErrEmptyID
|
||||
}
|
||||
|
||||
_, ok := s.containers[id]
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// AddContainer adds a container to the state
|
||||
// If the container belongs to a pod, the pod must already be present when the
|
||||
// container is added, and the container must be present in the pod
|
||||
func (s *InMemoryState) AddContainer(ctr *Container) error {
|
||||
if !ctr.valid {
|
||||
return errors.Wrapf(ErrCtrRemoved, "container with ID %s is not valid", ctr.ID())
|
||||
}
|
||||
|
||||
_, ok := s.containers[ctr.ID()]
|
||||
if ok {
|
||||
return errors.Wrapf(ErrCtrExists, "container with ID %s already exists in state", ctr.ID())
|
||||
}
|
||||
|
||||
if ctr.pod != nil {
|
||||
if _, ok := s.pods[ctr.pod.ID()]; !ok {
|
||||
return errors.Wrapf(ErrNoSuchPod, "pod %s does not exist, cannot add container %s", ctr.pod.ID(), ctr.ID())
|
||||
}
|
||||
|
||||
hasCtr, err := ctr.pod.HasContainer(ctr.ID())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error checking if container %s is present in pod %s", ctr.ID(), ctr.pod.ID())
|
||||
} else if !hasCtr {
|
||||
return errors.Wrapf(ErrNoSuchCtr, "container %s is not present in pod %s", ctr.ID(), ctr.pod.ID())
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.ctrNameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil {
|
||||
return errors.Wrapf(err, "error registering container name %s", ctr.Name())
|
||||
}
|
||||
|
||||
if err := s.ctrIDIndex.Add(ctr.ID()); err != nil {
|
||||
s.ctrNameIndex.Release(ctr.Name())
|
||||
return errors.Wrapf(err, "error registering container ID %s", ctr.ID())
|
||||
}
|
||||
|
||||
s.containers[ctr.ID()] = ctr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveContainer removes a container from the state
|
||||
// The container will only be removed from the state, not from the pod the container belongs to
|
||||
func (s *InMemoryState) RemoveContainer(ctr *Container) error {
|
||||
// Almost no validity checks are performed, to ensure we can kick
|
||||
// misbehaving containers out of the state
|
||||
|
||||
if _, ok := s.containers[ctr.ID()]; !ok {
|
||||
return errors.Wrapf(ErrNoSuchCtr, "no container exists in state with ID %s", ctr.ID())
|
||||
}
|
||||
|
||||
if err := s.ctrIDIndex.Delete(ctr.ID()); err != nil {
|
||||
return errors.Wrapf(err, "error removing container ID from index")
|
||||
}
|
||||
delete(s.containers, ctr.ID())
|
||||
s.ctrNameIndex.Release(ctr.Name())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllContainers retrieves all containers from the state
|
||||
func (s *InMemoryState) AllContainers() ([]*Container, error) {
|
||||
ctrs := make([]*Container, 0, len(s.containers))
|
||||
for _, ctr := range s.containers {
|
||||
ctrs = append(ctrs, ctr)
|
||||
}
|
||||
|
||||
return ctrs, nil
|
||||
}
|
||||
|
||||
// Pod retrieves a pod from the state from its full ID
|
||||
func (s *InMemoryState) Pod(id string) (*Pod, error) {
|
||||
if id == "" {
|
||||
return nil, ErrEmptyID
|
||||
}
|
||||
|
||||
pod, ok := s.pods[id]
|
||||
if !ok {
|
||||
return nil, errors.Wrapf(ErrNoSuchPod, "no pod with id %s found", id)
|
||||
}
|
||||
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// LookupPod retrieves a pod from the state from a full or unique partial ID or
|
||||
// a full name
|
||||
func (s *InMemoryState) LookupPod(idOrName string) (*Pod, error) {
|
||||
if idOrName == "" {
|
||||
return nil, ErrEmptyID
|
||||
}
|
||||
|
||||
fullID, err := s.podNameIndex.Get(idOrName)
|
||||
if err != nil {
|
||||
if err == registrar.ErrNameNotReserved {
|
||||
// What was passed is not a name, assume it's an ID
|
||||
fullID, err = s.podIDIndex.Get(idOrName)
|
||||
if err != nil {
|
||||
if err == truncindex.ErrNotExist {
|
||||
return nil, errors.Wrapf(ErrNoSuchPod, "no pod found with name or ID %s", idOrName)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName)
|
||||
}
|
||||
}
|
||||
|
||||
pod, ok := s.pods[fullID]
|
||||
if !ok {
|
||||
// This should never happen
|
||||
return nil, errors.Wrapf(ErrInternal, "mismatch in pod ID registry and pod map for ID %s", fullID)
|
||||
}
|
||||
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// HasPod checks if a pod with the given ID is present in the state
|
||||
func (s *InMemoryState) HasPod(id string) (bool, error) {
|
||||
if id == "" {
|
||||
return false, ErrEmptyID
|
||||
}
|
||||
|
||||
_, ok := s.pods[id]
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// AddPod adds a given pod to the state
|
||||
// Only empty pods can be added to the state
|
||||
func (s *InMemoryState) AddPod(pod *Pod) error {
|
||||
if !pod.valid {
|
||||
return errors.Wrapf(ErrPodRemoved, "pod %s is not valid and cannot be added", pod.ID())
|
||||
}
|
||||
|
||||
if _, ok := s.pods[pod.ID()]; ok {
|
||||
return errors.Wrapf(ErrPodExists, "pod with ID %s already exists in state", pod.ID())
|
||||
}
|
||||
|
||||
if len(pod.containers) != 0 {
|
||||
return errors.Wrapf(ErrInternal, "only empty pods can be added to the state")
|
||||
}
|
||||
|
||||
if err := s.podNameIndex.Reserve(pod.Name(), pod.ID()); err != nil {
|
||||
return errors.Wrapf(err, "error registering pod name %s", pod.Name())
|
||||
}
|
||||
|
||||
if err := s.podIDIndex.Add(pod.ID()); err != nil {
|
||||
s.podNameIndex.Release(pod.Name())
|
||||
return errors.Wrapf(err, "error registering pod ID %s", pod.ID())
|
||||
}
|
||||
|
||||
s.pods[pod.ID()] = pod
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemovePod removes a given pod from the state
|
||||
// Containers within the pod will not be removed or changed
|
||||
func (s *InMemoryState) RemovePod(pod *Pod) error {
|
||||
// Don't make many validity checks to ensure we can kick badly formed
|
||||
// pods out of the state
|
||||
|
||||
if _, ok := s.pods[pod.ID()]; !ok {
|
||||
return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID())
|
||||
}
|
||||
|
||||
if err := s.podIDIndex.Delete(pod.ID()); err != nil {
|
||||
return errors.Wrapf(err, "error removing pod ID %s from index", pod.ID())
|
||||
}
|
||||
delete(s.pods, pod.ID())
|
||||
s.podNameIndex.Release(pod.Name())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllPods retrieves all pods currently in the state
|
||||
func (s *InMemoryState) AllPods() ([]*Pod, error) {
|
||||
pods := make([]*Pod, 0, len(s.pods))
|
||||
for _, pod := range s.pods {
|
||||
pods = append(pods, pod)
|
||||
}
|
||||
|
||||
return pods, nil
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package layers
|
||||
|
||||
import cstorage "github.com/containers/storage"
|
||||
|
||||
// FullID gets the full id of a layer given a partial id or name
|
||||
func FullID(store cstorage.Store, id string) (string, error) {
|
||||
layer, err := store.Layer(id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return layer.ID, nil
|
||||
}
|
273
libpod/oci.go
273
libpod/oci.go
|
@ -1,273 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/cgroups"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
// TODO import these functions into libpod and remove the import
|
||||
// Trying to keep libpod from depending on CRI-O code
|
||||
"github.com/kubernetes-incubator/cri-o/utils"
|
||||
)
|
||||
|
||||
// OCI code is undergoing heavy rewrite
|
||||
|
||||
const (
|
||||
// CgroupfsCgroupsManager represents cgroupfs native cgroup manager
|
||||
CgroupfsCgroupsManager = "cgroupfs"
|
||||
// SystemdCgroupsManager represents systemd native cgroup manager
|
||||
SystemdCgroupsManager = "systemd"
|
||||
|
||||
// ContainerCreateTimeout represents the value of container creating timeout
|
||||
ContainerCreateTimeout = 240 * time.Second
|
||||
)
|
||||
|
||||
// OCIRuntime represents an OCI-compatible runtime that libpod can call into
|
||||
// to perform container operations
|
||||
type OCIRuntime struct {
|
||||
name string
|
||||
path string
|
||||
conmonPath string
|
||||
conmonEnv []string
|
||||
cgroupManager string
|
||||
tmpDir string
|
||||
exitsDir string
|
||||
socketsDir string
|
||||
logSizeMax int64
|
||||
noPivot bool
|
||||
}
|
||||
|
||||
// syncInfo is used to return data from monitor process to daemon
|
||||
type syncInfo struct {
|
||||
Pid int `json:"pid"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Make a new OCI runtime with provided options
|
||||
func newOCIRuntime(name string, path string, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool) (*OCIRuntime, error) {
|
||||
runtime := new(OCIRuntime)
|
||||
runtime.name = name
|
||||
runtime.path = path
|
||||
runtime.conmonPath = conmonPath
|
||||
runtime.conmonEnv = conmonEnv
|
||||
runtime.cgroupManager = cgroupManager
|
||||
runtime.tmpDir = tmpDir
|
||||
runtime.logSizeMax = logSizeMax
|
||||
runtime.noPivot = noPivotRoot
|
||||
|
||||
runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits")
|
||||
runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket")
|
||||
|
||||
if cgroupManager != CgroupfsCgroupsManager && cgroupManager != SystemdCgroupsManager {
|
||||
return nil, errors.Wrapf(ErrInvalidArg, "invalid cgroup manager specified: %s", cgroupManager)
|
||||
}
|
||||
|
||||
// Create the exit files and attach sockets directories
|
||||
if err := os.MkdirAll(runtime.exitsDir, 0750); err != nil {
|
||||
// The directory is allowed to exist
|
||||
if !os.IsExist(err) {
|
||||
return nil, errors.Wrapf(err, "error creating OCI runtime exit files directory %s",
|
||||
runtime.exitsDir)
|
||||
}
|
||||
}
|
||||
if err := os.MkdirAll(runtime.socketsDir, 0750); err != nil {
|
||||
// The directory is allowed to exist
|
||||
if !os.IsExist(err) {
|
||||
return nil, errors.Wrapf(err, "error creating OCI runtime attach sockets directory %s",
|
||||
runtime.socketsDir)
|
||||
}
|
||||
}
|
||||
|
||||
return runtime, nil
|
||||
}
|
||||
|
||||
// newPipe creates a unix socket pair for communication
|
||||
func newPipe() (parent *os.File, child *os.File, err error) {
|
||||
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
|
||||
}
|
||||
|
||||
// Create systemd unit name for cgroup scopes
|
||||
func createUnitName(prefix string, name string) string {
|
||||
return fmt.Sprintf("%s-%s.scope", prefix, name)
|
||||
}
|
||||
|
||||
// CreateContainer creates a container in the OCI runtime
|
||||
// TODO terminal support for container
|
||||
// Presently just ignoring conmon opts related to it
|
||||
func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string) error {
|
||||
var stderrBuf bytes.Buffer
|
||||
|
||||
parentPipe, childPipe, err := newPipe()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating socket pair")
|
||||
}
|
||||
|
||||
childStartPipe, parentStartPipe, err := newPipe()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error creating socket pair for start pipe")
|
||||
}
|
||||
|
||||
defer parentPipe.Close()
|
||||
defer parentStartPipe.Close()
|
||||
|
||||
args := []string{}
|
||||
if r.cgroupManager == SystemdCgroupsManager {
|
||||
args = append(args, "-s")
|
||||
}
|
||||
args = append(args, "-c", ctr.ID())
|
||||
args = append(args, "-u", ctr.ID())
|
||||
args = append(args, "-r", r.path)
|
||||
args = append(args, "-b", ctr.bundlePath())
|
||||
args = append(args, "-p", filepath.Join(ctr.state.RunDir, "pidfile"))
|
||||
// TODO container log location should be configurable
|
||||
// The default also likely shouldn't be this
|
||||
args = append(args, "-l", filepath.Join(ctr.config.StaticDir, "ctr.log"))
|
||||
args = append(args, "--exit-dir", r.exitsDir)
|
||||
args = append(args, "--socket-dir-path", r.socketsDir)
|
||||
if ctr.config.Spec.Process.Terminal {
|
||||
args = append(args, "-t")
|
||||
} else if ctr.config.Stdin {
|
||||
args = append(args, "-i")
|
||||
}
|
||||
if r.logSizeMax >= 0 {
|
||||
args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax))
|
||||
}
|
||||
if r.noPivot {
|
||||
args = append(args, "--no-pivot")
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"args": args,
|
||||
}).Debugf("running conmon: %s", r.conmonPath)
|
||||
|
||||
cmd := exec.Command(r.conmonPath, args...)
|
||||
cmd.Dir = ctr.state.RunDir
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
// TODO this is probably a really bad idea for some uses
|
||||
// Make this configurable
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if ctr.config.Spec.Process.Terminal {
|
||||
cmd.Stderr = &stderrBuf
|
||||
}
|
||||
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe, childStartPipe)
|
||||
// 0, 1 and 2 are stdin, stdout and stderr
|
||||
cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3))
|
||||
cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", 4))
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
childPipe.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
// We don't need childPipe on the parent side
|
||||
childPipe.Close()
|
||||
childStartPipe.Close()
|
||||
|
||||
// Move conmon to specified cgroup
|
||||
if r.cgroupManager == SystemdCgroupsManager {
|
||||
logrus.Infof("Running conmon under slice %s and unitName %s", cgroupParent, createUnitName("libpod-conmon", ctr.ID()))
|
||||
if err = utils.RunUnderSystemdScope(cmd.Process.Pid, cgroupParent, createUnitName("libpod-conmon", ctr.ID())); err != nil {
|
||||
logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err)
|
||||
}
|
||||
} else {
|
||||
control, err := cgroups.New(cgroups.V1, cgroups.StaticPath(filepath.Join(cgroupParent, "/libpod-conmon-"+ctr.ID())), &spec.LinuxResources{})
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
|
||||
} else {
|
||||
// XXX: this defer does nothing as the cgroup can't be deleted cause
|
||||
// it contains the conmon pid in tasks
|
||||
// we need to remove this defer and delete the cgroup once conmon exits
|
||||
// maybe need a conmon monitor?
|
||||
defer control.Delete()
|
||||
if err := control.Add(cgroups.Process{Pid: cmd.Process.Pid}); err != nil {
|
||||
logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* We set the cgroup, now the child can start creating children */
|
||||
someData := []byte{0}
|
||||
_, err = parentStartPipe.Write(someData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
/* Wait for initial setup and fork, and reap child */
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO should do a defer r.deleteContainer(ctr) here if err != nil
|
||||
// Need deleteContainer to be working first, though...
|
||||
|
||||
// Wait to get container pid from conmon
|
||||
type syncStruct struct {
|
||||
si *syncInfo
|
||||
err error
|
||||
}
|
||||
ch := make(chan syncStruct)
|
||||
go func() {
|
||||
var si *syncInfo
|
||||
if err = json.NewDecoder(parentPipe).Decode(&si); err != nil {
|
||||
ch <- syncStruct{err: err}
|
||||
return
|
||||
}
|
||||
ch <- syncStruct{si: si}
|
||||
}()
|
||||
|
||||
select {
|
||||
case ss := <-ch:
|
||||
if ss.err != nil {
|
||||
return errors.Wrapf(ss.err, "error reading container (probably exited) json message")
|
||||
}
|
||||
logrus.Debugf("Received container pid: %d", ss.si.Pid)
|
||||
if ss.si.Pid == -1 {
|
||||
if ss.si.Message != "" {
|
||||
return errors.Wrapf(ErrInternal, "container create failed: %s", ss.si.Message)
|
||||
}
|
||||
return errors.Wrapf(ErrInternal, "container create failed")
|
||||
}
|
||||
case <-time.After(ContainerCreateTimeout):
|
||||
return errors.Wrapf(ErrInternal, "container creation timeout")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateContainerStatus retrieves the current status of the container from the
|
||||
// runtime
|
||||
func (r *OCIRuntime) updateContainerStatus(ctr *Container) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// startContainer starts the given container
|
||||
func (r *OCIRuntime) startContainer(ctr *Container) error {
|
||||
// TODO: streams should probably *not* be our STDIN/OUT/ERR - redirect to buffers?
|
||||
if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, r.path, "start", ctr.ID()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO record start time in container struct
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,382 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containers/storage"
|
||||
"github.com/containers/storage/pkg/idtools"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ctrNotImplemented = func(c *Container) error {
|
||||
return fmt.Errorf("NOT IMPLEMENTED")
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
// IPCNamespace represents the IPC namespace
|
||||
IPCNamespace = "ipc"
|
||||
// MountNamespace represents the mount namespace
|
||||
MountNamespace = "mount"
|
||||
// NetNamespace represents the network namespace
|
||||
NetNamespace = "net"
|
||||
// PIDNamespace represents the PID namespace
|
||||
PIDNamespace = "pid"
|
||||
// UserNamespace represents the user namespace
|
||||
UserNamespace = "user"
|
||||
// UTSNamespace represents the UTS namespace
|
||||
UTSNamespace = "uts"
|
||||
)
|
||||
|
||||
// Runtime Creation Options
|
||||
|
||||
// WithStorageConfig uses the given configuration to set up container storage
|
||||
// If this is not specified, the system default configuration will be used
|
||||
// instead
|
||||
func WithStorageConfig(config storage.StoreOptions) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.StorageConfig.RunRoot = config.RunRoot
|
||||
rt.config.StorageConfig.GraphRoot = config.GraphRoot
|
||||
rt.config.StorageConfig.GraphDriverName = config.GraphDriverName
|
||||
|
||||
rt.config.StorageConfig.GraphDriverOptions = make([]string, len(config.GraphDriverOptions))
|
||||
copy(rt.config.StorageConfig.GraphDriverOptions, config.GraphDriverOptions)
|
||||
|
||||
rt.config.StorageConfig.UIDMap = make([]idtools.IDMap, len(config.UIDMap))
|
||||
copy(rt.config.StorageConfig.UIDMap, config.UIDMap)
|
||||
|
||||
rt.config.StorageConfig.GIDMap = make([]idtools.IDMap, len(config.GIDMap))
|
||||
copy(rt.config.StorageConfig.GIDMap, config.GIDMap)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithImageConfig uses the given configuration to set up image handling
|
||||
// If this is not specified, the system default configuration will be used
|
||||
// instead
|
||||
func WithImageConfig(defaultTransport string, insecureRegistries, registries []string) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.ImageDefaultTransport = defaultTransport
|
||||
|
||||
rt.config.InsecureRegistries = make([]string, len(insecureRegistries))
|
||||
copy(rt.config.InsecureRegistries, insecureRegistries)
|
||||
|
||||
rt.config.Registries = make([]string, len(registries))
|
||||
copy(rt.config.Registries, registries)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSignaturePolicy specifies the path of a file which decides how trust is
|
||||
// managed for images we've pulled.
|
||||
// If this is not specified, the system default configuration will be used
|
||||
// instead
|
||||
func WithSignaturePolicy(path string) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.SignaturePolicyPath = path
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithOCIRuntime specifies an OCI runtime to use for running containers
|
||||
func WithOCIRuntime(runtimePath string) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.RuntimePath = runtimePath
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConmonPath specifies the path to the conmon binary which manages the
|
||||
// runtime
|
||||
func WithConmonPath(path string) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.ConmonPath = path
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConmonEnv specifies the environment variable list for the conmon process
|
||||
func WithConmonEnv(environment []string) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.ConmonEnvVars = make([]string, len(environment))
|
||||
copy(rt.config.ConmonEnvVars, environment)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCgroupManager specifies the manager implementation name which is used to
|
||||
// handle cgroups for containers
|
||||
// Current valid values are "cgroupfs" and "systemd"
|
||||
func WithCgroupManager(manager string) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.CgroupManager = manager
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithStaticDir sets the directory that static runtime files which persist
|
||||
// across reboots will be stored
|
||||
func WithStaticDir(dir string) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.StaticDir = dir
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithTmpDir sets the directory that temporary runtime files which are not
|
||||
// expected to survive across reboots will be stored
|
||||
// This should be located on a tmpfs mount (/tmp or /var/run for example)
|
||||
func WithTmpDir(dir string) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.TmpDir = dir
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSELinux enables SELinux on the container server
|
||||
func WithSELinux() RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.SelinuxEnabled = true
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPidsLimit specifies the maximum number of processes each container is
|
||||
// restricted to
|
||||
func WithPidsLimit(limit int64) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.PidsLimit = limit
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaxLogSize sets the maximum size of container logs
|
||||
// Positive sizes are limits in bytes, -1 is unlimited
|
||||
func WithMaxLogSize(limit int64) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.MaxLogSize = limit
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoPivotRoot sets the runtime to use MS_MOVE instead of PIVOT_ROOT when
|
||||
// starting containers
|
||||
func WithNoPivotRoot(noPivot bool) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.NoPivotRoot = true
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Container Creation Options
|
||||
|
||||
// WithRootFSFromPath uses the given path as a container's root filesystem
|
||||
// No further setup is performed on this path
|
||||
func WithRootFSFromPath(path string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return ErrCtrFinalized
|
||||
}
|
||||
|
||||
if ctr.config.RootfsDir != "" || ctr.config.RootfsImageID != "" || ctr.config.RootfsImageName != "" {
|
||||
return errors.Wrapf(ErrInvalidArg, "container already configured with root filesystem")
|
||||
}
|
||||
|
||||
ctr.config.RootfsDir = path
|
||||
ctr.config.RootfsFromImage = false
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithRootFSFromImage sets up a fresh root filesystem using the given image
|
||||
// If useImageConfig is specified, image volumes, environment variables, and
|
||||
// other configuration from the image will be added to the config
|
||||
// TODO: Replace image name and ID with a libpod.Image struct when that is finished
|
||||
func WithRootFSFromImage(imageID string, imageName string, useImageConfig bool) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return ErrCtrFinalized
|
||||
}
|
||||
|
||||
if ctr.config.RootfsDir != "" || ctr.config.RootfsImageID != "" || ctr.config.RootfsImageName != "" {
|
||||
return errors.Wrapf(ErrInvalidArg, "container already configured with root filesystem")
|
||||
}
|
||||
|
||||
ctr.config.RootfsImageID = imageID
|
||||
ctr.config.RootfsImageName = imageName
|
||||
ctr.config.UseImageConfig = useImageConfig
|
||||
ctr.config.RootfsFromImage = true
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithStdin keeps stdin on the container open to allow interaction
|
||||
func WithStdin() CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return ErrCtrFinalized
|
||||
}
|
||||
|
||||
ctr.config.Stdin = true
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSharedNamespaces sets a container to share namespaces with another
|
||||
// container. If the from container belongs to a pod, the new container will
|
||||
// be added to the pod.
|
||||
// By default no namespaces are shared. To share a namespace, add the Namespace
|
||||
// string constant to the map as a key
|
||||
func WithSharedNamespaces(from *Container, namespaces map[string]string) CtrCreateOption {
|
||||
return ctrNotImplemented
|
||||
}
|
||||
|
||||
// WithPod adds the container to a pod
|
||||
func (r *Runtime) WithPod(pod *Pod) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return ErrCtrFinalized
|
||||
}
|
||||
|
||||
if pod == nil {
|
||||
return ErrInvalidArg
|
||||
}
|
||||
|
||||
ctr.config.Pod = pod.ID()
|
||||
ctr.pod = pod
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLabels adds labels to the container
|
||||
func WithLabels(labels map[string]string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return ErrCtrFinalized
|
||||
}
|
||||
|
||||
ctr.config.Labels = make(map[string]string)
|
||||
for key, value := range labels {
|
||||
ctr.config.Labels[key] = value
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithName sets the container's name
|
||||
func WithName(name string) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return ErrCtrFinalized
|
||||
}
|
||||
|
||||
ctr.config.Name = name
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithStopSignal sets the signal that will be sent to stop the container
|
||||
func WithStopSignal(signal uint) CtrCreateOption {
|
||||
return func(ctr *Container) error {
|
||||
if ctr.valid {
|
||||
return ErrCtrFinalized
|
||||
}
|
||||
|
||||
if signal == 0 {
|
||||
return errors.Wrapf(ErrInvalidArg, "stop signal cannot be 0")
|
||||
} else if signal > 64 {
|
||||
return errors.Wrapf(ErrInvalidArg, "stop signal cannot be greater than 64 (SIGRTMAX)")
|
||||
}
|
||||
|
||||
ctr.config.StopSignal = signal
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Pod Creation Options
|
||||
|
||||
// WithPodName sets the name of the pod
|
||||
func WithPodName(name string) PodCreateOption {
|
||||
return func(pod *Pod) error {
|
||||
if pod.valid {
|
||||
return ErrPodFinalized
|
||||
}
|
||||
|
||||
pod.name = name
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
137
libpod/pod.go
137
libpod/pod.go
|
@ -1,137 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Pod represents a group of containers that may share namespaces
|
||||
type Pod struct {
|
||||
id string
|
||||
name string
|
||||
|
||||
containers map[string]*Container
|
||||
|
||||
valid bool
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// ID retrieves the pod's ID
|
||||
func (p *Pod) ID() string {
|
||||
return p.id
|
||||
}
|
||||
|
||||
// Name retrieves the pod's name
|
||||
func (p *Pod) Name() string {
|
||||
return p.name
|
||||
}
|
||||
|
||||
// Creates a new pod
|
||||
func newPod() (*Pod, error) {
|
||||
pod := new(Pod)
|
||||
pod.id = stringid.GenerateNonCryptoID()
|
||||
pod.name = pod.id // TODO generate human-readable name here
|
||||
|
||||
pod.containers = make(map[string]*Container)
|
||||
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// Adds a container to the pod
|
||||
// Does not check that container's pod ID is set correctly, or attempt to set
|
||||
// pod ID after adding
|
||||
func (p *Pod) addContainer(ctr *Container) error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
ctr.lock.Lock()
|
||||
defer ctr.lock.Unlock()
|
||||
|
||||
if !p.valid {
|
||||
return ErrPodRemoved
|
||||
}
|
||||
|
||||
if !ctr.valid {
|
||||
return ErrCtrRemoved
|
||||
}
|
||||
|
||||
if _, ok := p.containers[ctr.ID()]; ok {
|
||||
return errors.Wrapf(ErrCtrExists, "container with ID %s already exists in pod %s", ctr.ID(), p.id)
|
||||
}
|
||||
|
||||
p.containers[ctr.ID()] = ctr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Removes a container from the pod
|
||||
// Does not perform any checks on the container
|
||||
func (p *Pod) removeContainer(ctr *Container) error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
if !p.valid {
|
||||
return ErrPodRemoved
|
||||
}
|
||||
|
||||
if _, ok := p.containers[ctr.ID()]; !ok {
|
||||
return errors.Wrapf(ErrNoSuchCtr, "no container with id %s in pod %s", ctr.ID(), p.id)
|
||||
}
|
||||
|
||||
delete(p.containers, ctr.ID())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts all containers within a pod that are not already running
|
||||
func (p *Pod) Start() error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// Stop stops all containers within a pod that are not already stopped
|
||||
func (p *Pod) Stop() error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// Kill sends a signal to all running containers within a pod
|
||||
func (p *Pod) Kill(signal uint) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// HasContainer checks if a container is present in the pod
|
||||
func (p *Pod) HasContainer(id string) (bool, error) {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
if !p.valid {
|
||||
return false, ErrPodRemoved
|
||||
}
|
||||
|
||||
_, ok := p.containers[id]
|
||||
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// GetContainers retrieves the containers in the pod
|
||||
func (p *Pod) GetContainers() ([]*Container, error) {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
if !p.valid {
|
||||
return nil, ErrPodRemoved
|
||||
}
|
||||
|
||||
ctrs := make([]*Container, 0, len(p.containers))
|
||||
for _, ctr := range p.containers {
|
||||
ctrs = append(ctrs, ctr)
|
||||
}
|
||||
|
||||
return ctrs, nil
|
||||
}
|
||||
|
||||
// Status gets the status of all containers in the pod
|
||||
// TODO This should return a summary of the states of all containers in the pod
|
||||
func (p *Pod) Status() error {
|
||||
return ErrNotImplemented
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
is "github.com/containers/image/storage"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
// A RuntimeOption is a functional option which alters the Runtime created by
|
||||
// NewRuntime
|
||||
type RuntimeOption func(*Runtime) error
|
||||
|
||||
// Runtime is the core libpod runtime
|
||||
type Runtime struct {
|
||||
config *RuntimeConfig
|
||||
state State
|
||||
store storage.Store
|
||||
storageService *storageService
|
||||
imageContext *types.SystemContext
|
||||
ociRuntime *OCIRuntime
|
||||
valid bool
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// RuntimeConfig contains configuration options used to set up the runtime
|
||||
type RuntimeConfig struct {
|
||||
StorageConfig storage.StoreOptions
|
||||
ImageDefaultTransport string
|
||||
InsecureRegistries []string
|
||||
Registries []string
|
||||
SignaturePolicyPath string
|
||||
RuntimePath string
|
||||
ConmonPath string
|
||||
ConmonEnvVars []string
|
||||
CgroupManager string
|
||||
StaticDir string
|
||||
TmpDir string
|
||||
SelinuxEnabled bool
|
||||
PidsLimit int64
|
||||
MaxLogSize int64
|
||||
NoPivotRoot bool
|
||||
}
|
||||
|
||||
var (
|
||||
defaultRuntimeConfig = RuntimeConfig{
|
||||
// Leave this empty so containers/storage will use its defaults
|
||||
StorageConfig: storage.StoreOptions{},
|
||||
ImageDefaultTransport: "docker://",
|
||||
RuntimePath: "/usr/bin/runc",
|
||||
ConmonPath: "/usr/local/libexec/crio/conmon",
|
||||
ConmonEnvVars: []string{
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
},
|
||||
CgroupManager: "cgroupfs",
|
||||
StaticDir: "/var/lib/libpod",
|
||||
TmpDir: "/var/run/libpod",
|
||||
SelinuxEnabled: false,
|
||||
PidsLimit: 1024,
|
||||
MaxLogSize: -1,
|
||||
NoPivotRoot: false,
|
||||
}
|
||||
)
|
||||
|
||||
// NewRuntime creates a new container runtime
|
||||
// Options can be passed to override the default configuration for the runtime
|
||||
func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) {
|
||||
runtime = new(Runtime)
|
||||
runtime.config = new(RuntimeConfig)
|
||||
|
||||
// Copy the default configuration
|
||||
deepcopier.Copy(defaultRuntimeConfig).To(runtime.config)
|
||||
|
||||
// Overwrite it with user-given configuration options
|
||||
for _, opt := range options {
|
||||
if err := opt(runtime); err != nil {
|
||||
return nil, errors.Wrapf(err, "error configuring runtime")
|
||||
}
|
||||
}
|
||||
|
||||
// Set up containers/storage
|
||||
store, err := storage.GetStore(runtime.config.StorageConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime.store = store
|
||||
is.Transport.SetStore(store)
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// Don't forcibly shut down
|
||||
// We could be opening a store in use by another libpod
|
||||
_, err2 := runtime.store.Shutdown(false)
|
||||
if err2 != nil {
|
||||
logrus.Errorf("Error removing store for partially-created runtime: %s", err2)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set up a storage service for creating container root filesystems from
|
||||
// images
|
||||
storageService, err := getStorageService(runtime.store)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime.storageService = storageService
|
||||
|
||||
// Set up containers/image
|
||||
runtime.imageContext = &types.SystemContext{
|
||||
SignaturePolicyPath: runtime.config.SignaturePolicyPath,
|
||||
}
|
||||
|
||||
// Set up the state
|
||||
state, err := NewInMemoryState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime.state = state
|
||||
|
||||
// Make an OCI runtime to perform container operations
|
||||
ociRuntime, err := newOCIRuntime("runc", runtime.config.RuntimePath,
|
||||
runtime.config.ConmonPath, runtime.config.ConmonEnvVars,
|
||||
runtime.config.CgroupManager, runtime.config.TmpDir,
|
||||
runtime.config.MaxLogSize, runtime.config.NoPivotRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime.ociRuntime = ociRuntime
|
||||
|
||||
// Make the static files directory if it does not exist
|
||||
if err := os.MkdirAll(runtime.config.StaticDir, 0755); err != nil {
|
||||
// The directory is allowed to exist
|
||||
if !os.IsExist(err) {
|
||||
return nil, errors.Wrapf(err, "error creating runtime static files directory %s",
|
||||
runtime.config.StaticDir)
|
||||
}
|
||||
}
|
||||
|
||||
// Make the per-boot files directory if it does not exist
|
||||
if err := os.MkdirAll(runtime.config.TmpDir, 0755); err != nil {
|
||||
// The directory is allowed to exist
|
||||
if !os.IsExist(err) {
|
||||
return nil, errors.Wrapf(err, "error creating runtime temporary files directory %s",
|
||||
runtime.config.TmpDir)
|
||||
}
|
||||
}
|
||||
|
||||
// Mark the runtime as valid - ready to be used, cannot be modified
|
||||
// further
|
||||
runtime.valid = true
|
||||
|
||||
return runtime, nil
|
||||
}
|
||||
|
||||
// GetConfig returns a copy of the configuration used by the runtime
|
||||
func (r *Runtime) GetConfig() *RuntimeConfig {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.valid {
|
||||
return nil
|
||||
}
|
||||
|
||||
config := new(RuntimeConfig)
|
||||
|
||||
// Copy so the caller won't be able to modify the actual config
|
||||
deepcopier.Copy(r.config).To(config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// Shutdown shuts down the runtime and associated containers and storage
|
||||
// If force is true, containers and mounted storage will be shut down before
|
||||
// cleaning up; if force is false, an error will be returned if there are
|
||||
// still containers running or mounted
|
||||
func (r *Runtime) Shutdown(force bool) error {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if !r.valid {
|
||||
return ErrRuntimeStopped
|
||||
}
|
||||
|
||||
r.valid = false
|
||||
|
||||
_, err := r.store.Shutdown(force)
|
||||
return err
|
||||
}
|
|
@ -1,228 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"github.com/containers/storage"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Contains the public Runtime API for containers
|
||||
|
||||
// A CtrCreateOption is a functional option which alters the Container created
|
||||
// by NewContainer
|
||||
type CtrCreateOption func(*Container) error
|
||||
|
||||
// ContainerFilter is a function to determine whether a container is included
|
||||
// in command output. Containers to be outputted are tested using the function.
|
||||
// A true return will include the container, a false return will exclude it.
|
||||
type ContainerFilter func(*Container) bool
|
||||
|
||||
// NewContainer creates a new container from a given OCI config
|
||||
func (r *Runtime) NewContainer(spec *spec.Spec, options ...CtrCreateOption) (ctr *Container, err error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
if !r.valid {
|
||||
return nil, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
ctr, err = newContainer(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
if err := option(ctr); err != nil {
|
||||
return nil, errors.Wrapf(err, "error running container create option")
|
||||
}
|
||||
}
|
||||
|
||||
ctr.valid = true
|
||||
ctr.state.State = ContainerStateConfigured
|
||||
ctr.runtime = r
|
||||
|
||||
// Set up storage for the container
|
||||
if err := ctr.setupStorage(); err != nil {
|
||||
return nil, errors.Wrapf(err, "error configuring storage for container")
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if err2 := ctr.teardownStorage(); err2 != nil {
|
||||
logrus.Errorf("Error removing partially-created container root filesystem: %s", err2)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// If the container is in a pod, add it to the pod
|
||||
if ctr.pod != nil {
|
||||
if err := ctr.pod.addContainer(ctr); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding new container to pod %s", ctr.pod.ID())
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if err2 := ctr.pod.removeContainer(ctr); err2 != nil {
|
||||
logrus.Errorf("Error removing partially-created container from pod %s: %s", ctr.pod.ID(), err2)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := r.state.AddContainer(ctr); err != nil {
|
||||
// TODO: Might be worth making an effort to detect duplicate IDs
|
||||
// We can recover from that by generating a new ID for the
|
||||
// container
|
||||
return nil, errors.Wrapf(err, "error adding new container to state")
|
||||
}
|
||||
|
||||
return ctr, nil
|
||||
}
|
||||
|
||||
// RemoveContainer removes the given container
|
||||
// If force is specified, the container will be stopped first
|
||||
// Otherwise, RemoveContainer will return an error if the container is running
|
||||
func (r *Runtime) RemoveContainer(c *Container, force bool) error {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if !r.valid {
|
||||
return ErrRuntimeStopped
|
||||
}
|
||||
|
||||
if !c.valid {
|
||||
return ErrCtrRemoved
|
||||
}
|
||||
|
||||
// TODO check container status and unmount storage
|
||||
// TODO check that no other containers depend on this container's
|
||||
// namespaces
|
||||
status, err := c.State()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// A container cannot be removed if it is running
|
||||
if status == ContainerStateRunning {
|
||||
return errors.Wrapf(ErrCtrStateInvalid, "cannot remove container %s as it is running", c.ID())
|
||||
}
|
||||
|
||||
if err := r.state.RemoveContainer(c); err != nil {
|
||||
return errors.Wrapf(err, "error removing container from state")
|
||||
}
|
||||
|
||||
// Set container as invalid so it can no longer be used
|
||||
c.valid = false
|
||||
|
||||
// Remove container from pod, if it joined one
|
||||
if c.pod != nil {
|
||||
if err := c.pod.removeContainer(c); err != nil {
|
||||
return errors.Wrapf(err, "error removing container from pod %s", c.pod.ID())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetContainer retrieves a container by its ID
|
||||
func (r *Runtime) GetContainer(id string) (*Container, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.valid {
|
||||
return nil, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
return r.state.Container(id)
|
||||
}
|
||||
|
||||
// HasContainer checks if a container with the given ID is present
|
||||
func (r *Runtime) HasContainer(id string) (bool, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.valid {
|
||||
return false, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
return r.state.HasContainer(id)
|
||||
}
|
||||
|
||||
// LookupContainer looks up a container by its name or a partial ID
|
||||
// If a partial ID is not unique, an error will be returned
|
||||
func (r *Runtime) LookupContainer(idOrName string) (*Container, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.valid {
|
||||
return nil, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
return r.state.LookupContainer(idOrName)
|
||||
}
|
||||
|
||||
// GetContainers retrieves all containers from the state
|
||||
// Filters can be provided which will determine what containers are included in
|
||||
// the output. Multiple filters are handled by ANDing their output, so only
|
||||
// containers matching all filters are returned
|
||||
func (r *Runtime) GetContainers(filters ...ContainerFilter) ([]*Container, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.valid {
|
||||
return nil, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
ctrs, err := r.state.AllContainers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctrsFiltered := make([]*Container, 0, len(ctrs))
|
||||
|
||||
for _, ctr := range ctrs {
|
||||
include := true
|
||||
for _, filter := range filters {
|
||||
include = include && filter(ctr)
|
||||
}
|
||||
|
||||
if include {
|
||||
ctrsFiltered = append(ctrsFiltered, ctr)
|
||||
}
|
||||
}
|
||||
|
||||
return ctrsFiltered, nil
|
||||
}
|
||||
|
||||
// getContainersWithImage returns a list of containers referencing imageID
|
||||
func (r *Runtime) getContainersWithImage(imageID string) ([]storage.Container, error) {
|
||||
var matchingContainers []storage.Container
|
||||
containers, err := r.store.Containers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ctr := range containers {
|
||||
if ctr.ImageID == imageID {
|
||||
matchingContainers = append(matchingContainers, ctr)
|
||||
}
|
||||
}
|
||||
return matchingContainers, nil
|
||||
}
|
||||
|
||||
// removeMultipleContainers deletes a list of containers from the store
|
||||
// TODO refactor this to remove libpod Containers
|
||||
func (r *Runtime) removeMultipleContainers(containers []storage.Container) error {
|
||||
for _, ctr := range containers {
|
||||
if err := r.store.DeleteContainer(ctr.ID); err != nil {
|
||||
return errors.Wrapf(err, "could not remove container %q", ctr)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainerConfigToDisk saves a container's nonvolatile configuration to disk
|
||||
func (r *Runtime) containerConfigToDisk(ctr *Container) error {
|
||||
return ErrNotImplemented
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,122 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Contains the public Runtime API for pods
|
||||
|
||||
// A PodCreateOption is a functional option which alters the Pod created by
|
||||
// NewPod
|
||||
type PodCreateOption func(*Pod) error
|
||||
|
||||
// PodFilter is a function to determine whether a pod is included in command
|
||||
// output. Pods to be outputted are tested using the function. A true return
|
||||
// will include the pod, a false return will exclude it.
|
||||
type PodFilter func(*Pod) bool
|
||||
|
||||
// NewPod makes a new, empty pod
|
||||
func (r *Runtime) NewPod(options ...PodCreateOption) (*Pod, error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if !r.valid {
|
||||
return nil, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
pod, err := newPod()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error creating pod")
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
if err := option(pod); err != nil {
|
||||
return nil, errors.Wrapf(err, "error running pod create option")
|
||||
}
|
||||
}
|
||||
|
||||
pod.valid = true
|
||||
|
||||
if err := r.state.AddPod(pod); err != nil {
|
||||
return nil, errors.Wrapf(err, "error adding pod to state")
|
||||
}
|
||||
|
||||
return nil, ErrNotImplemented
|
||||
}
|
||||
|
||||
// RemovePod removes a pod and all containers in it
|
||||
// If force is specified, all containers in the pod will be stopped first
|
||||
// Otherwise, RemovePod will return an error if any container in the pod is running
|
||||
// Remove acts atomically, removing all containers or no containers
|
||||
func (r *Runtime) RemovePod(p *Pod, force bool) error {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
// GetPod retrieves a pod by its ID
|
||||
func (r *Runtime) GetPod(id string) (*Pod, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.valid {
|
||||
return nil, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
return r.state.Pod(id)
|
||||
}
|
||||
|
||||
// HasPod checks to see if a pod with the given ID exists
|
||||
func (r *Runtime) HasPod(id string) (bool, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.valid {
|
||||
return false, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
return r.state.HasPod(id)
|
||||
}
|
||||
|
||||
// LookupPod retrieves a pod by its name or a partial ID
|
||||
// If a partial ID is not unique, an error will be returned
|
||||
func (r *Runtime) LookupPod(idOrName string) (*Pod, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.valid {
|
||||
return nil, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
return r.state.LookupPod(idOrName)
|
||||
}
|
||||
|
||||
// Pods retrieves all pods
|
||||
// Filters can be provided which will determine which pods are included in the
|
||||
// output. Multiple filters are handled by ANDing their output, so only pods
|
||||
// matching all filters are returned
|
||||
func (r *Runtime) Pods(filters ...PodFilter) ([]*Pod, error) {
|
||||
r.lock.RLock()
|
||||
defer r.lock.RUnlock()
|
||||
|
||||
if !r.valid {
|
||||
return nil, ErrRuntimeStopped
|
||||
}
|
||||
|
||||
pods, err := r.state.AllPods()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
podsFiltered := make([]*Pod, 0, len(pods))
|
||||
for _, pod := range pods {
|
||||
include := true
|
||||
for _, filter := range filters {
|
||||
include = include && filter(pod)
|
||||
}
|
||||
|
||||
if include {
|
||||
podsFiltered = append(podsFiltered, pod)
|
||||
}
|
||||
}
|
||||
|
||||
return podsFiltered, nil
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package libpod
|
||||
|
||||
// State is a storage backend for libpod's current state
|
||||
type State interface {
|
||||
// Accepts full ID of container
|
||||
Container(id string) (*Container, error)
|
||||
// Accepts full or partial IDs (as long as they are unique) and names
|
||||
LookupContainer(idOrName string) (*Container, error)
|
||||
// Checks if a container with the given ID is present in the state
|
||||
HasContainer(id string) (bool, error)
|
||||
// Adds container to state
|
||||
// If the container belongs to a pod, that pod must already be present
|
||||
// in the state when the container is added, and the container must be
|
||||
// present in the pod
|
||||
AddContainer(ctr *Container) error
|
||||
// Removes container from state
|
||||
// The container will only be removed from the state, not from the pod
|
||||
// which the container belongs to
|
||||
RemoveContainer(ctr *Container) error
|
||||
// Retrieves all containers presently in state
|
||||
AllContainers() ([]*Container, error)
|
||||
|
||||
// Accepts full ID of pod
|
||||
Pod(id string) (*Pod, error)
|
||||
// Accepts full or partial IDs (as long as they are unique) and names
|
||||
LookupPod(idOrName string) (*Pod, error)
|
||||
// Checks if a pod with the given ID is present in the state
|
||||
HasPod(id string) (bool, error)
|
||||
// Adds pod to state
|
||||
// Only empty pods can be added to the state
|
||||
AddPod(pod *Pod) error
|
||||
// Removes pod from state
|
||||
// Containers within a pod will not be removed from the state, and will
|
||||
// not be changed to remove them from the now-removed pod
|
||||
RemovePod(pod *Pod) error
|
||||
// Retrieves all pods presently in state
|
||||
AllPods() ([]*Pod, error)
|
||||
}
|
|
@ -1,261 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
istorage "github.com/containers/image/storage"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type storageService struct {
|
||||
store storage.Store
|
||||
}
|
||||
|
||||
// getStorageService returns a storageService which can create container root
|
||||
// filesystems from images
|
||||
func getStorageService(store storage.Store) (*storageService, error) {
|
||||
return &storageService{store: store}, nil
|
||||
}
|
||||
|
||||
// ContainerInfo wraps a subset of information about a container: the locations
|
||||
// of its nonvolatile and volatile per-container directories, along with a copy
|
||||
// of the configuration blob from the image that was used to create the
|
||||
// container, if the image had a configuration.
|
||||
type ContainerInfo struct {
|
||||
Dir string
|
||||
RunDir string
|
||||
Config *v1.Image
|
||||
}
|
||||
|
||||
// RuntimeContainerMetadata is the structure that we encode as JSON and store
|
||||
// in the metadata field of storage.Container objects. It is used for
|
||||
// specifying attributes containers when they are being created, and allows a
|
||||
// container's MountLabel, and possibly other values, to be modified in one
|
||||
// read/write cycle via calls to storageService.ContainerMetadata,
|
||||
// RuntimeContainerMetadata.SetMountLabel, and
|
||||
// storageService.SetContainerMetadata.
|
||||
type RuntimeContainerMetadata struct {
|
||||
// The provided name and the ID of the image that was used to
|
||||
// instantiate the container.
|
||||
ImageName string `json:"image-name"` // Applicable to both PodSandboxes and Containers
|
||||
ImageID string `json:"image-id"` // Applicable to both PodSandboxes and Containers
|
||||
// The container's name, which for an infrastructure container is usually PodName + "-infra".
|
||||
ContainerName string `json:"name"` // Applicable to both PodSandboxes and Containers, mandatory
|
||||
CreatedAt int64 `json:"created-at"` // Applicable to both PodSandboxes and Containers
|
||||
MountLabel string `json:"mountlabel,omitempty"` // Applicable to both PodSandboxes and Containers
|
||||
}
|
||||
|
||||
// SetMountLabel updates the mount label held by a RuntimeContainerMetadata
|
||||
// object.
|
||||
func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) {
|
||||
metadata.MountLabel = mountLabel
|
||||
}
|
||||
|
||||
// CreateContainerStorage creates the storage end of things. We already have the container spec created
|
||||
// TO-DO We should be passing in an Image object in the future.
|
||||
func (r *storageService) CreateContainerStorage(systemContext *types.SystemContext, imageName, imageID, containerName, containerID, mountLabel string) (ContainerInfo, error) {
|
||||
var ref types.ImageReference
|
||||
if imageName == "" && imageID == "" {
|
||||
return ContainerInfo{}, ErrEmptyID
|
||||
}
|
||||
if containerName == "" {
|
||||
return ContainerInfo{}, ErrEmptyID
|
||||
}
|
||||
//// Check if we have the specified image.
|
||||
ref, err := istorage.Transport.ParseStoreReference(r.store, imageName)
|
||||
if err != nil {
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
img, err := istorage.Transport.GetStoreImage(r.store, ref)
|
||||
if err != nil {
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
// Pull out a copy of the image's configuration.
|
||||
image, err := ref.NewImage(systemContext)
|
||||
if err != nil {
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
defer image.Close()
|
||||
|
||||
imageConfig, err := image.OCIConfig()
|
||||
if err != nil {
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
|
||||
// Update the image name and ID.
|
||||
if imageName == "" && len(img.Names) > 0 {
|
||||
imageName = img.Names[0]
|
||||
}
|
||||
imageID = img.ID
|
||||
|
||||
// Build metadata to store with the container.
|
||||
metadata := RuntimeContainerMetadata{
|
||||
ImageName: imageName,
|
||||
ImageID: imageID,
|
||||
ContainerName: containerName,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
MountLabel: mountLabel,
|
||||
}
|
||||
mdata, err := json.Marshal(&metadata)
|
||||
if err != nil {
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
|
||||
// Build the container.
|
||||
names := []string{containerName}
|
||||
|
||||
container, err := r.store.CreateContainer(containerID, names, img.ID, "", string(mdata), nil)
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to create container %s(%s): %v", metadata.ContainerName, containerID, err)
|
||||
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
logrus.Debugf("created container %q", container.ID)
|
||||
|
||||
// If anything fails after this point, we need to delete the incomplete
|
||||
// container before returning.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if err2 := r.store.DeleteContainer(container.ID); err2 != nil {
|
||||
logrus.Infof("%v deleting partially-created container %q", err2, container.ID)
|
||||
|
||||
return
|
||||
}
|
||||
logrus.Infof("deleted partially-created container %q", container.ID)
|
||||
}
|
||||
}()
|
||||
|
||||
// Add a name to the container's layer so that it's easier to follow
|
||||
// what's going on if we're just looking at the storage-eye view of things.
|
||||
layerName := metadata.ContainerName + "-layer"
|
||||
names, err = r.store.Names(container.LayerID)
|
||||
if err != nil {
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
names = append(names, layerName)
|
||||
err = r.store.SetNames(container.LayerID, names)
|
||||
if err != nil {
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
|
||||
// Find out where the container work directories are, so that we can return them.
|
||||
containerDir, err := r.store.ContainerDirectory(container.ID)
|
||||
if err != nil {
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
logrus.Debugf("container %q has work directory %q", container.ID, containerDir)
|
||||
|
||||
containerRunDir, err := r.store.ContainerRunDirectory(container.ID)
|
||||
if err != nil {
|
||||
return ContainerInfo{}, err
|
||||
}
|
||||
logrus.Debugf("container %q has run directory %q", container.ID, containerRunDir)
|
||||
|
||||
return ContainerInfo{
|
||||
Dir: containerDir,
|
||||
RunDir: containerRunDir,
|
||||
Config: imageConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *storageService) DeleteContainer(idOrName string) error {
|
||||
if idOrName == "" {
|
||||
return ErrEmptyID
|
||||
}
|
||||
container, err := r.store.Container(idOrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.store.DeleteContainer(container.ID)
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to delete container %q: %v", container.ID, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *storageService) SetContainerMetadata(idOrName string, metadata RuntimeContainerMetadata) error {
|
||||
mdata, err := json.Marshal(&metadata)
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to encode metadata for %q: %v", idOrName, err)
|
||||
return err
|
||||
}
|
||||
return r.store.SetMetadata(idOrName, string(mdata))
|
||||
}
|
||||
|
||||
func (r *storageService) GetContainerMetadata(idOrName string) (RuntimeContainerMetadata, error) {
|
||||
metadata := RuntimeContainerMetadata{}
|
||||
mdata, err := r.store.Metadata(idOrName)
|
||||
if err != nil {
|
||||
return metadata, err
|
||||
}
|
||||
if err = json.Unmarshal([]byte(mdata), &metadata); err != nil {
|
||||
return metadata, err
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (r *storageService) StartContainer(idOrName string) (string, error) {
|
||||
container, err := r.store.Container(idOrName)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == storage.ErrContainerUnknown {
|
||||
return "", ErrNoSuchCtr
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
metadata := RuntimeContainerMetadata{}
|
||||
if err = json.Unmarshal([]byte(container.Metadata), &metadata); err != nil {
|
||||
return "", err
|
||||
}
|
||||
mountPoint, err := r.store.Mount(container.ID, metadata.MountLabel)
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to mount container %q: %v", container.ID, err)
|
||||
return "", err
|
||||
}
|
||||
logrus.Debugf("mounted container %q at %q", container.ID, mountPoint)
|
||||
return mountPoint, nil
|
||||
}
|
||||
|
||||
func (r *storageService) StopContainer(idOrName string) error {
|
||||
if idOrName == "" {
|
||||
return ErrEmptyID
|
||||
}
|
||||
container, err := r.store.Container(idOrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = r.store.Unmount(container.ID)
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to unmount container %q: %v", container.ID, err)
|
||||
return err
|
||||
}
|
||||
logrus.Debugf("unmounted container %q", container.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *storageService) GetWorkDir(id string) (string, error) {
|
||||
container, err := r.store.Container(id)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == storage.ErrContainerUnknown {
|
||||
return "", ErrNoSuchCtr
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return r.store.ContainerDirectory(container.ID)
|
||||
}
|
||||
|
||||
func (r *storageService) GetRunDir(id string) (string, error) {
|
||||
container, err := r.store.Container(id)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == storage.ErrContainerUnknown {
|
||||
return "", ErrNoSuchCtr
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
return r.store.ContainerRunDirectory(container.ID)
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package libpod
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// WriteFile writes a provided string to a provided path
|
||||
func WriteFile(content string, path string) error {
|
||||
baseDir := filepath.Dir(path)
|
||||
if baseDir != "" {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
f.WriteString(content)
|
||||
f.Sync()
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringInSlice determines if a string is in a string slice, returns bool
|
||||
func StringInSlice(s string, sl []string) bool {
|
||||
for _, i := range sl {
|
||||
if i == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue