Merge pull request #1 from mheon/master

Initial checkin
This commit is contained in:
Matthew Heon 2017-11-01 14:38:21 -04:00 committed by GitHub
commit f5019df3f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3656 changed files with 1316300 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
/.artifacts/
/_output/
/conmon/conmon.o
/docs/*.[158]
/docs/*.[158].gz
/crio.conf
*.o
*.orig
/pause/pause.o
/bin/
/test/bin2img/bin2img
/test/checkseccomp/checkseccomp
/test/copyimg/copyimg

62
.travis.yml Normal file
View File

@ -0,0 +1,62 @@
language: go
sudo: required
services:
- docker
before_install:
- sudo apt-get -qq update
- sudo apt-get -qq install btrfs-tools libdevmapper-dev libgpgme11-dev libapparmor-dev libseccomp-dev
- sudo apt-get -qq install autoconf automake bison e2fslibs-dev libfuse-dev libtool liblzma-dev gettext
install:
- make install.tools
- OSTREE_VERSION=v2017.9
- git clone https://github.com/ostreedev/ostree ${TRAVIS_BUILD_DIR}/ostree
- pushd ${TRAVIS_BUILD_DIR}/ostree
- git checkout $OSTREE_VERSION
- ./autogen.sh --prefix=/usr/local
- make all
- sudo make install
- popd
before_script:
- export PATH=$HOME/gopath/bin:$PATH
- export LD_LIBRARY_PATH=/usr/local/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
jobs:
include:
- stage: Build and Verify
script:
- make .gitvalidation
- make gofmt
- make lint
- make testunit
- make docs
- make
go: 1.8.x
- stage: Build and Verify
script:
- make .gitvalidation
- make gofmt
- make lint
- make testunit
- make docs
- make
go: 1.9.x
- script:
- make .gitvalidation
- make gofmt
- make lint
- make testunit
- make docs
- make
go: tip
- stage: Integration Test
script:
- make integration
go: 1.8.x
notifications:
irc: "chat.freenode.net#cri-o"

142
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,142 @@
# Contributing to Libpod
We'd love to have you join the community! Below summarizes the processes
that we follow.
## Topics
* [Reporting Issues](#reporting-issues)
* [Submitting Pull Requests](#submitting-pull-requests)
* [Communications](#communications)
* [Becoming a Maintainer](#becoming-a-maintainer)
## Reporting Issues
Before reporting an issue, check our backlog of
[open issues](https://github.com/projectatomic/libpod/issues)
to see if someone else has already reported it. If so, feel free to add
your scenario, or additional information, to the discussion. Or simply
"subscribe" to it to be notified when it is updated.
If you find a new issue with the project we'd love to hear about it! The most
important aspect of a bug report is that it includes enough information for
us to reproduce it. So, please include as much detail as possible and try
to remove the extra stuff that doesn't really relate to the issue itself.
The easier it is for us to reproduce it, the faster it'll be fixed!
Please don't include any private/sensitive information in your issue!
## Submitting Pull Requests
No Pull Request (PR) is too small! Typos, additional comments in the code,
new testcases, bug fixes, new features, more documentation, ... it's all
welcome!
While bug fixes can first be identified via an "issue", that is not required.
It's ok to just open up a PR with the fix, but make sure you include the same
information you would have included in an issue - like how to reproduce it.
PRs for new features should include some background on what use cases the
new code is trying to address. When possible and when it makes sense, try to break-up
larger PRs into smaller ones - it's easier to review smaller
code changes. But only if those smaller ones make sense as stand-alone PRs.
Regardless of the type of PR, all PRs should include:
* well documented code changes
* additional testcases. Ideally, they should fail w/o your code change applied
* documentation changes
Squash your commits into logical pieces of work that might want to be reviewed
separate from the rest of the PRs. But, squashing down to just one commit is ok
too since in the end the entire PR will be reviewed anyway. When in doubt,
squash.
PRs that fix issues should include a reference like `Closes #XXXX` in the
commit message so that github will automatically close the referenced issue
when the PR is merged.
<!--
All PRs require at least two LGTMs (Looks Good To Me) from maintainers.
-->
### Sign your PRs
The sign-off is a line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
## Communications
For general questions, or discussions, please use the
IRC group on `irc.freenode.net` called `cri-o`
that has been setup.
For discussions around issues/bugs and features, you can use the github
[issues](https://github.com/projectatomic/libpod/issues)
and
[PRs](https://github.com/projectatomic/libpod/pulls)
tracking system.
<!--
## Becoming a Maintainer
To become a maintainer you must first be nominated by an existing maintainer.
If a majority (>50%) of maintainers agree then the proposal is adopted and
you will be added to the list.
Removing a maintainer requires at least 75% of the remaining maintainers
approval, or if the person requests to be removed then it is automatic.
Normally, a maintainer will only be removed if they are considered to be
inactive for a long period of time or are viewed as disruptive to the community.
The current list of maintainers can be found in the
[MAINTAINERS](MAINTAINERS) file.
-->

117
Dockerfile Normal file
View File

@ -0,0 +1,117 @@
FROM golang:1.8
# libseccomp in jessie is not _quite_ new enough -- need backports version
RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/backports.list
RUN apt-get update && apt-get install -y \
apparmor \
autoconf \
automake \
bison \
build-essential \
curl \
e2fslibs-dev \
gawk \
gettext \
iptables \
pkg-config \
libaio-dev \
libcap-dev \
libfuse-dev \
libostree-dev \
libprotobuf-dev \
libprotobuf-c0-dev \
libseccomp2/jessie-backports \
libseccomp-dev/jessie-backports \
libtool \
libudev-dev \
protobuf-c-compiler \
protobuf-compiler \
python-minimal \
libglib2.0-dev \
libapparmor-dev \
btrfs-tools \
libdevmapper1.02.1 \
libdevmapper-dev \
libgpgme11-dev \
liblzma-dev \
netcat \
socat \
--no-install-recommends \
&& apt-get clean
# install bats
RUN cd /tmp \
&& git clone https://github.com/sstephenson/bats.git \
&& cd bats \
&& git reset --hard 03608115df2071fff4eaaff1605768c275e5f81f \
&& ./install.sh /usr/local
# install criu
ENV CRIU_VERSION 1.7
RUN mkdir -p /usr/src/criu \
&& curl -sSL https://github.com/xemul/criu/archive/v${CRIU_VERSION}.tar.gz | tar -v -C /usr/src/criu/ -xz --strip-components=1 \
&& cd /usr/src/criu \
&& make install-criu \
&& rm -rf /usr/src/criu
# Install runc
ENV RUNC_COMMIT 84a082bfef6f932de921437815355186db37aeb1
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
&& git fetch origin --tags \
&& git checkout -q "$RUNC_COMMIT" \
&& make static BUILDTAGS="seccomp selinux" \
&& cp runc /usr/bin/runc \
&& rm -rf "$GOPATH"
# Install CNI plugins
ENV CNI_COMMIT dcf7368eeab15e2affc6256f0bb1e84dd46a34de
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/containernetworking/plugins.git "$GOPATH/src/github.com/containernetworking/plugins" \
&& cd "$GOPATH/src/github.com/containernetworking/plugins" \
&& git checkout -q "$CNI_COMMIT" \
&& ./build.sh \
&& mkdir -p /opt/cni/bin \
&& cp bin/* /opt/cni/bin/ \
&& rm -rf "$GOPATH"
# Install custom CNI bridge test plugin
# XXX: this plugin is meant to be a replacement for the old "test_plugin_args.bash"
# we need this in testing because sandbox_run now gather IP address and the mock
# plugin wasn't able to properly setup the net ns.
# The bridge is based on the same commit as the one above.
#ENV CNI_COMMIT 6bfe036c38c8e1410f1acaa4b2ee16f1851472e4
ENV CNI_TEST_BRANCH custom-bridge
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/runcom/plugins.git "$GOPATH/src/github.com/containernetworking/plugins" \
&& cd "$GOPATH/src/github.com/containernetworking/plugins" \
&& git checkout -q "$CNI_TEST_BRANCH" \
&& ./build.sh \
&& mkdir -p /opt/cni/bin \
&& cp bin/bridge /opt/cni/bin/bridge-custom \
&& rm -rf "$GOPATH"
# Install crictl
ENV CRICTL_COMMIT 16e6fe4d7199c5689db4630a9330e6a8a12cecd1
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/kubernetes-incubator/cri-tools.git "$GOPATH/src/github.com/kubernetes-incubator/cri-tools" \
&& cd "$GOPATH/src/github.com/kubernetes-incubator/cri-tools" \
&& git checkout -q "$CRICTL_COMMIT" \
&& go install github.com/kubernetes-incubator/cri-tools/cmd/crictl \
&& cp "$GOPATH"/bin/crictl /usr/bin/ \
&& rm -rf "$GOPATH"
# Make sure we have some policy for pulling images
RUN mkdir -p /etc/containers
COPY test/policy.json /etc/containers/policy.json
COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml
WORKDIR /go/src/github.com/projectatomic/libpod
ADD . /go/src/github.com/projectatomic/libpod

1
KPOD_VERSION Normal file
View File

@ -0,0 +1 @@
0.1

219
Makefile Normal file
View File

@ -0,0 +1,219 @@
GO ?= go
EPOCH_TEST_COMMIT ?= 1cc5a27
PROJECT := github.com/projectatomic/libpod
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
GIT_BRANCH_CLEAN := $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g")
CRIO_IMAGE := crio_dev$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN))
CRIO_INSTANCE := crio_dev
PREFIX ?= ${DESTDIR}/usr/local
BINDIR ?= ${PREFIX}/bin
LIBEXECDIR ?= ${PREFIX}/libexec
MANDIR ?= ${PREFIX}/share/man
ETCDIR ?= ${DESTDIR}/etc
ETCDIR_CRIO ?= ${ETCDIR}/crio
BUILDTAGS ?= seccomp $(shell hack/btrfs_tag.sh) $(shell hack/libdm_tag.sh) $(shell hack/btrfs_installed_tag.sh) $(shell hack/ostree_tag.sh) $(shell hack/selinux_tag.sh)
BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions
OCIUMOUNTINSTALLDIR=$(PREFIX)/share/oci-umount/oci-umount.d
SELINUXOPT ?= $(shell test -x /usr/sbin/selinuxenabled && selinuxenabled && echo -Z)
PACKAGES ?= $(shell go list -tags "${BUILDTAGS}" ./... | grep -v github.com/projectatomic/libpod/vendor)
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
unexport GOBIN
endif
GOPKGDIR := $(GOPATH)/src/$(PROJECT)
GOPKGBASEDIR := $(shell dirname "$(GOPKGDIR)")
# Update VPATH so make finds .gopathok
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 docs
default: help
help:
@echo "Usage: make <target>"
@echo
@echo " * 'install' - Install binaries to system locations"
@echo " * 'binaries' - Build conmon and kpod"
@echo " * 'integration' - Execute integration tests"
@echo " * 'clean' - Clean artifacts"
@echo " * 'lint' - Execute the source code linter"
@echo " * 'gofmt' - Verify the source code gofmt"
.gopathok:
ifeq ("$(wildcard $(GOPKGDIR))","")
mkdir -p "$(GOPKGBASEDIR)"
ln -s "$(CURDIR)" "$(GOPKGBASEDIR)"
endif
touch "$(GOPATH)/.gopathok"
lint: .gopathok
@echo "checking lint"
@./.tool/lint
gofmt:
@./hack/verify-gofmt.sh
conmon:
$(MAKE) -C $@
test/bin2img/bin2img: .gopathok $(wildcard test/bin2img/*.go)
$(GO) build $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/bin2img
test/copyimg/copyimg: .gopathok $(wildcard test/copyimg/*.go)
$(GO) build $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/copyimg
test/checkseccomp/checkseccomp: .gopathok $(wildcard test/checkseccomp/*.go)
$(GO) build $(LDFLAGS) -tags "$(BUILDTAGS) containers_image_ostree_stub" -o $@ $(PROJECT)/test/checkseccomp
kpod: .gopathok $(shell hack/find-godeps.sh $(GOPKGDIR) cmd/kpod $(PROJECT))
$(GO) build $(LDFLAGS_KPOD) -tags "$(BUILDTAGS)" -o bin/$@ $(PROJECT)/cmd/kpod
clean:
ifneq ($(GOPATH),)
rm -f "$(GOPATH)/.gopathok"
endif
rm -rf _output
rm -f docs/*.1 docs/*.5 docs/*.8
rm -fr test/testdata/redis-image
find . -name \*~ -delete
find . -name \#\* -delete
rm -f bin/kpod
make -C conmon clean
rm -f test/bin2img/bin2img
rm -f test/copyimg/copyimg
rm -f test/checkseccomp/checkseccomp
crioimage:
docker build -t ${CRIO_IMAGE} .
dbuild: crioimage
docker run --name=${CRIO_INSTANCE} --privileged ${CRIO_IMAGE} -v ${PWD}:/go/src/${PROJECT} --rm make binaries
integration: crioimage
docker run -e STORAGE_OPTIONS="--storage-driver=vfs" -e TESTFLAGS -e TRAVIS -t --privileged --rm -v ${CURDIR}:/go/src/${PROJECT} ${CRIO_IMAGE} make localintegration
testunit:
$(GO) test -tags "$(BUILDTAGS)" -cover $(PACKAGES)
localintegration: clean binaries test-binaries
./test/test_runner.sh ${TESTFLAGS}
binaries: conmon kpod
test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp
MANPAGES_MD := $(wildcard docs/*.md)
MANPAGES := $(MANPAGES_MD:%.md=%)
docs/%.1: docs/%.1.md .gopathok
(go-md2man -in $< -out $@.tmp && touch $@.tmp && mv $@.tmp $@) || ($(GOPATH)/bin/go-md2man -in $< -out $@.tmp && touch $@.tmp && mv $@.tmp $@)
docs/%.5: docs/%.5.md .gopathok
(go-md2man -in $< -out $@.tmp && touch $@.tmp && mv $@.tmp $@) || ($(GOPATH)/bin/go-md2man -in $< -out $@.tmp && touch $@.tmp && mv $@.tmp $@)
docs/%.8: docs/%.8.md .gopathok
(go-md2man -in $< -out $@.tmp && touch $@.tmp && mv $@.tmp $@) || ($(GOPATH)/bin/go-md2man -in $< -out $@.tmp && touch $@.tmp && mv $@.tmp $@)
docs: $(MANPAGES)
install: .gopathok install.bin install.man
install.bin:
install ${SELINUXOPT} -D -m 755 bin/kpod $(BINDIR)/kpod
install ${SELINUXOPT} -D -m 755 bin/conmon $(LIBEXECDIR)/crio/conmon
install.man:
install ${SELINUXOPT} -d -m 755 $(MANDIR)/man1
install ${SELINUXOPT} -d -m 755 $(MANDIR)/man5
install ${SELINUXOPT} -d -m 755 $(MANDIR)/man8
install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES)) -t $(MANDIR)/man1
install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES)) -t $(MANDIR)/man5
install ${SELINUXOPT} -m 644 $(filter %.8,$(MANPAGES)) -t $(MANDIR)/man8
install.config:
install ${SELINUXOPT} -D -m 644 seccomp.json $(ETCDIR_CRIO)/seccomp.json
install ${SELINUXOPT} -D -m 644 crio-umount.conf $(OCIUMOUNTINSTALLDIR)/crio-umount.conf
install.completions:
install ${SELINUXOPT} -d -m 755 ${BASHINSTALLDIR}
install ${SELINUXOPT} -m 644 -D completions/bash/kpod ${BASHINSTALLDIR}
uninstall:
rm -f $(LIBEXECDIR)/crio/conmon
for i in $(filter %.1,$(MANPAGES)); do \
rm -f $(MANDIR)/man8/$$(basename $${i}); \
done
for i in $(filter %.5,$(MANPAGES)); do \
rm -f $(MANDIR)/man5/$$(basename $${i}); \
done
for i in $(filter %.8,$(MANPAGES)); do \
rm -f $(MANDIR)/man8/$$(basename $${i}); \
done
.PHONY: .gitvalidation
# When this is running in travis, it will only check the travis commit range
.gitvalidation: .gopathok
ifeq ($(TRAVIS),true)
GIT_CHECK_EXCLUDE="./vendor" $(GOPATH)/bin/git-validation -q -run DCO,short-subject,dangling-whitespace
else
GIT_CHECK_EXCLUDE="./vendor" $(GOPATH)/bin/git-validation -v -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..HEAD
endif
.PHONY: install.tools
install.tools: .install.gitvalidation .install.gometalinter .install.md2man
.install.gitvalidation: .gopathok
if [ ! -x "$(GOPATH)/bin/git-validation" ]; then \
go get -u github.com/vbatts/git-validation; \
fi
.install.gometalinter: .gopathok
if [ ! -x "$(GOPATH)/bin/gometalinter" ]; then \
go get -u github.com/alecthomas/gometalinter; \
cd $(GOPATH)/src/github.com/alecthomas/gometalinter; \
git checkout 23261fa046586808612c61da7a81d75a658e0814; \
go install github.com/alecthomas/gometalinter; \
$(GOPATH)/bin/gometalinter --install; \
fi
.install.md2man: .gopathok
if [ ! -x "$(GOPATH)/bin/go-md2man" ]; then \
go get -u github.com/cpuguy83/go-md2man; \
fi
.install.ostree: .gopathok
if ! pkg-config ostree-1 2> /dev/null ; then \
git clone https://github.com/ostreedev/ostree $(GOPATH)/src/github.com/ostreedev/ostree ; \
cd $(GOPATH)/src/github.com/ostreedev/ostree ; \
./autogen.sh --prefix=/usr/local; \
make all install; \
fi
.PHONY: \
binaries \
clean \
conmon \
default \
docs \
gofmt \
help \
install \
lint \
pause \
uninstall

7
OWNERS Normal file
View File

@ -0,0 +1,7 @@
assignees:
- mrunalp
- runcom
- rhatdan
- nalind
- mheon
- baude

1
README.md Normal file
View File

@ -0,0 +1 @@
Under construction!

16
cmd/kpod/README.md Normal file
View File

@ -0,0 +1,16 @@
# 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.

136
cmd/kpod/common.go Normal file
View File

@ -0,0 +1,136 @@
package main
import (
"os"
"reflect"
"regexp"
"strings"
is "github.com/containers/image/storage"
"github.com/containers/storage"
"github.com/fatih/camelcase"
"github.com/projectatomic/libpod/libkpod"
"github.com/projectatomic/libpod/libpod"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var (
stores = make(map[storage.Store]struct{})
)
const CrioConfigPath = "/etc/crio/crio.conf"
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))
}
func shutdownStores() {
for store := range stores {
if _, err := store.Shutdown(false); err != nil {
break
}
}
}
func getConfig(c *cli.Context) (*libkpod.Config, error) {
config := libkpod.DefaultConfig()
var configFile string
if c.GlobalIsSet("config") {
configFile = c.GlobalString("config")
} else if _, err := os.Stat(CrioConfigPath); err == nil {
configFile = CrioConfigPath
}
// load and merge the configfile from the commandline or use
// the default crio config file
if configFile != "" {
err := config.UpdateFromFile(configFile)
if err != nil {
return config, err
}
}
if c.GlobalIsSet("root") {
config.Root = c.GlobalString("root")
}
if c.GlobalIsSet("runroot") {
config.RunRoot = c.GlobalString("runroot")
}
if c.GlobalIsSet("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
}

51
cmd/kpod/common_test.go Normal file
View File

@ -0,0 +1,51 @@
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
}

128
cmd/kpod/diff.go Normal file
View File

@ -0,0 +1,128 @@
package main
import (
"fmt"
"github.com/containers/storage/pkg/archive"
"github.com/projectatomic/libpod/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
}

271
cmd/kpod/docker/types.go Normal file
View File

@ -0,0 +1,271 @@
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"`
}

106
cmd/kpod/export.go Normal file
View File

@ -0,0 +1,106 @@
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
}

143
cmd/kpod/formats/formats.go Normal file
View File

@ -0,0 +1,143 @@
package formats
import (
"encoding/json"
"fmt"
"os"
"strings"
"text/tabwriter"
"text/template"
"bytes"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
)
const (
// JSONString const to save on duplicate variable names
JSONString = "json"
// IDString const to save on duplicates for Go templates
IDString = "{{.ID}}"
)
// Writer interface for outputs
type Writer interface {
Out() error
}
// JSONStructArray for JSON output
type JSONStructArray struct {
Output []interface{}
}
// StdoutTemplateArray for Go template output
type StdoutTemplateArray struct {
Output []interface{}
Template string
Fields map[string]string
}
// JSONStruct for JSON output
type JSONStruct struct {
Output interface{}
}
// StdoutTemplate for Go template output
type StdoutTemplate struct {
Output interface{}
Template string
Fields map[string]string
}
// YAMLStruct for YAML output
type YAMLStruct struct {
Output interface{}
}
// Out method for JSON Arrays
func (j JSONStructArray) Out() error {
data, err := json.MarshalIndent(j.Output, "", " ")
if err != nil {
return err
}
// JSON returns a byte array with a literal null [110 117 108 108] in it
// if it is passed empty data. We used bytes.Compare to see if that is
// the case.
if diff := bytes.Compare(data, []byte("null")); diff == 0 {
data = []byte("[]")
}
// If the we did get NULL back, we should spit out {} which is
// at least valid JSON for the consumer.
fmt.Printf("%s\n", data)
return nil
}
// Out method for Go templates
func (t StdoutTemplateArray) Out() error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
if strings.HasPrefix(t.Template, "table") {
// replace any spaces with tabs in template so that tabwriter can align it
t.Template = strings.Replace(strings.TrimSpace(t.Template[5:]), " ", "\t", -1)
headerTmpl, err := template.New("header").Funcs(headerFunctions).Parse(t.Template)
if err != nil {
return errors.Wrapf(err, "Template parsing error")
}
err = headerTmpl.Execute(w, t.Fields)
if err != nil {
return err
}
fmt.Fprintln(w, "")
}
t.Template = strings.Replace(t.Template, " ", "\t", -1)
tmpl, err := template.New("image").Funcs(basicFunctions).Parse(t.Template)
if err != nil {
return errors.Wrapf(err, "Template parsing error")
}
for _, img := range t.Output {
basicTmpl := tmpl.Funcs(basicFunctions)
err = basicTmpl.Execute(w, img)
if err != nil {
return err
}
fmt.Fprintln(w, "")
}
return w.Flush()
}
// Out method for JSON struct
func (j JSONStruct) Out() error {
data, err := json.MarshalIndent(j.Output, "", " ")
if err != nil {
return err
}
fmt.Printf("%s\n", data)
return nil
}
//Out method for Go templates
func (t StdoutTemplate) Out() error {
tmpl, err := template.New("image").Parse(t.Template)
if err != nil {
return errors.Wrapf(err, "template parsing error")
}
err = tmpl.Execute(os.Stdout, t.Output)
if err != nil {
return err
}
fmt.Println()
return nil
}
// Out method for YAML
func (y YAMLStruct) Out() error {
var buf []byte
var err error
buf, err = yaml.Marshal(y.Output)
if err != nil {
return err
}
fmt.Println(string(buf))
return nil
}

View File

@ -0,0 +1,78 @@
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]
}

243
cmd/kpod/history.go Normal file
View File

@ -0,0 +1,243 @@
package main
import (
"reflect"
"strconv"
"strings"
"time"
"github.com/containers/image/types"
units "github.com/docker/go-units"
"github.com/projectatomic/libpod/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()
}

330
cmd/kpod/images.go Normal file
View File

@ -0,0 +1,330 @@
package main
import (
"fmt"
"reflect"
"strings"
"time"
"github.com/containers/image/types"
"github.com/containers/storage"
"github.com/docker/go-units"
"github.com/projectatomic/libpod/cmd/kpod/formats"
"github.com/projectatomic/libpod/libpod"
"github.com/projectatomic/libpod/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 Normal file
View File

@ -0,0 +1,200 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"runtime"
"github.com/docker/docker/pkg/system"
"github.com/projectatomic/libpod/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
}

120
cmd/kpod/inspect.go Normal file
View File

@ -0,0 +1,120 @@
package main
import (
"github.com/projectatomic/libpod/cmd/kpod/formats"
"github.com/projectatomic/libpod/libkpod"
"github.com/projectatomic/libpod/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
}

74
cmd/kpod/kill.go Normal file
View File

@ -0,0 +1,74 @@
package main
import (
"fmt"
"os"
"github.com/docker/docker/pkg/signal"
"github.com/projectatomic/libpod/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 Normal file
View File

@ -0,0 +1,116 @@
package main
import (
"io"
"io/ioutil"
"os"
"github.com/projectatomic/libpod/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
}

110
cmd/kpod/login.go Normal file
View File

@ -0,0 +1,110 @@
package main
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/containers/image/docker"
"github.com/containers/image/pkg/docker/config"
"github.com/projectatomic/libpod/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
}

69
cmd/kpod/logout.go Normal file
View File

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

92
cmd/kpod/logs.go Normal file
View File

@ -0,0 +1,92 @@
package main
import (
"fmt"
"time"
"github.com/projectatomic/libpod/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)
}
}

129
cmd/kpod/main.go Normal file
View File

@ -0,0 +1,129 @@
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{
diffCommand,
exportCommand,
historyCommand,
imagesCommand,
infoCommand,
inspectCommand,
killCommand,
loadCommand,
loginCommand,
logoutCommand,
logsCommand,
mountCommand,
pauseCommand,
psCommand,
pullCommand,
pushCommand,
renameCommand,
rmCommand,
rmiCommand,
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: "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)
}
}

121
cmd/kpod/mount.go Normal file
View File

@ -0,0 +1,121 @@
package main
import (
js "encoding/json"
"fmt"
of "github.com/projectatomic/libpod/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
}

58
cmd/kpod/pause.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"fmt"
"github.com/projectatomic/libpod/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 Normal file
View File

@ -0,0 +1,665 @@
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/projectatomic/libpod/cmd/kpod/formats"
"github.com/projectatomic/libpod/libkpod"
"github.com/projectatomic/libpod/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 Normal file
View File

@ -0,0 +1,118 @@
package main
import (
"fmt"
"io"
"os"
"golang.org/x/crypto/ssh/terminal"
"github.com/containers/image/types"
"github.com/projectatomic/libpod/libpod"
"github.com/projectatomic/libpod/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 Normal file
View File

@ -0,0 +1,132 @@
package main
import (
"fmt"
"io"
"os"
"github.com/containers/image/types"
"github.com/containers/storage/pkg/archive"
"github.com/projectatomic/libpod/libpod"
"github.com/projectatomic/libpod/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)
}

49
cmd/kpod/rename.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"github.com/projectatomic/libpod/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
}

69
cmd/kpod/rm.go Normal file
View File

@ -0,0 +1,69 @@
package main
import (
"fmt"
"github.com/projectatomic/libpod/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
}

56
cmd/kpod/rmi.go Normal file
View File

@ -0,0 +1,56 @@
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
}

98
cmd/kpod/save.go Normal file
View File

@ -0,0 +1,98 @@
package main
import (
"io"
"os"
"github.com/projectatomic/libpod/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
}

245
cmd/kpod/stats.go Normal file
View File

@ -0,0 +1,245 @@
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"text/template"
"time"
"github.com/docker/go-units"
tm "github.com/buger/goterm"
"github.com/projectatomic/libpod/libkpod"
"github.com/projectatomic/libpod/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
}
}

77
cmd/kpod/stop.go Normal file
View File

@ -0,0 +1,77 @@
package main
import (
"fmt"
"os"
"github.com/projectatomic/libpod/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
}

77
cmd/kpod/tag.go Normal file
View File

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

41
cmd/kpod/umount.go Normal file
View File

@ -0,0 +1,41 @@
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
}

58
cmd/kpod/unpause.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"fmt"
"github.com/projectatomic/libpod/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
}

48
cmd/kpod/version.go Normal file
View File

@ -0,0 +1,48 @@
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,
}

62
cmd/kpod/wait.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
"fmt"
"os"
"github.com/projectatomic/libpod/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
}

55
code-of-conduct.md Normal file
View File

@ -0,0 +1,55 @@
## Kubernetes Community Code of Conduct
### Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering
an open and welcoming community, we pledge to respect all people who contribute
through reporting issues, posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for
everyone, regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance, body size, race, ethnicity, age,
religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery.
* Personal attacks.
* Trolling or insulting/derogatory comments.
* Public or private harassment.
* Publishing other's private information, such as physical or electronic addresses,
without explicit permission.
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are not
aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers
commit themselves to fairly and consistently applying these principles to every aspect
of managing this project. Project maintainers who do not follow or enforce the Code of
Conduct may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a Kubernetes maintainer, Sarah Novotny <sarahnovotny@google.com>, and/or Dan Kohn <dan@linuxfoundation.org>.
This Code of Conduct is adapted from the Contributor Covenant
(http://contributor-covenant.org), version 1.2.0, available at
http://contributor-covenant.org/version/1/2/0/
### Kubernetes Events Code of Conduct
Kubernetes events are working conferences intended for professional networking and collaboration in the
Kubernetes community. Attendees are expected to behave according to professional standards and in accordance
with their employer's policies on appropriate workplace behavior.
While at Kubernetes events or related social networking opportunities, attendees should not engage in
discriminatory or offensive speech or actions regarding gender, sexuality, race, or religion. Speakers should
be especially aware of these concerns.
The Kubernetes team does not condone any statements by speakers contrary to these standards. The Kubernetes
team reserves the right to deny entrance and/or eject from an event (without refund) any individual found to
be engaging in discriminatory or offensive speech or actions.
Please bring any concerns to the immediate attention of the Kubernetes event staff.

564
completions/bash/kpod Normal file
View File

@ -0,0 +1,564 @@
#! /bin/bash
: ${PROG:=$(basename ${BASH_SOURCE})}
__kpod_list_images() {
COMPREPLY=($(compgen -W "$(kpod images -q)" -- $cur))
}
__kpod_list_containers() {
COMPREPLY=($(compgen -W "$(kpod ps -aq)" -- $cur))
}
_kpod_diff() {
local options_with_args="
--format
"
local boolean_options="
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_images
;;
esac
}
_kpod_export() {
local options_with_args="
--output
-o
"
local boolean_options="
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_images
;;
esac
}
_kpod_history() {
local options_with_args="
--format
"
local boolean_options="
--human -H
--no-trunc
--quiet -q
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_images
;;
esac
}
_kpod_info() {
local boolean_options="
--help
-h
--debug
"
local options_with_args="
--format
"
local all_options="$options_with_args $boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_images
;;
esac
}
_kpod_images() {
local boolean_options="
--help
-h
--quiet
-q
--noheading
-n
--no-trunc
--digests
--filter
-f
"
local options_with_args="
--format
"
local all_options="$options_with_args $boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
esac
}
_kpod_inspect() {
local boolean_options="
--help
-h
"
local options_with_args="
--format
-f
--type
-t
--size
"
local all_options="$options_with_args $boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
esac
}
_kpod_kill() {
local options_with_args="
--signal -s
"
local boolean_options="
--help
-h"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_logs() {
local options_with_args="
--since
--tail
"
local boolean_options="
--follow
-f
"
_complete_ "$options_with_args" "$boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_containers
;;
esac
}
_kpod_pull() {
local options_with_args="
--authfile
--creds
--cert-dir
--signature-policy
"
local boolean_options="
--all-tags -a
--quiet
-q
--tls-verify
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_unmount() {
_kpod_umount $@
}
_kpod_umount() {
local boolean_options="
--help
-h
"
local options_with_args="
"
local all_options="$options_with_args $boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
esac
}
_kpod_mount() {
local boolean_options="
--help
-h
--notruncate
"
local options_with_args="
--label
--format
"
local all_options="$options_with_args $boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
esac
}
_kpod_push() {
local boolean_options="
--disable-compression
-D
--quiet
-q
--remove-signatures
--tls-verify
"
local options_with_args="
--authfile
--cert-dir
--creds
--sign-by
--signature-policy
"
local all_options="$options_with_args $boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
esac
}
_kpod_rename() {
local boolean_options="
--help
-h
"
local options_with_args="
"
local all_options="$options_with_args $boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_containers
;;
esac
}
_kpod_rm() {
local boolean_options="
--force
-f
"
local options_with_args="
"
local all_options="$options_with_args $boolean_options"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_containers
;;
esac
}
_kpod_rmi() {
local boolean_options="
--help
-h
--force
-f
"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_images
;;
esac
}
_kpod_stats() {
local boolean_options="
--help
--all
-a
--no-stream
--format
"
case "$cur" in
-*)
COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
;;
*)
__kpod_list_containers
;;
esac
}
kpod_tag() {
local options_with_args="
"
local boolean_options="
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_version() {
local options_with_args="
"
local boolean_options="
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_save() {
local options_with_args="
--output -o
--format
"
local boolean_options="
--quiet -q
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_export() {
local options_with_args="
--output -o
"
local boolean_options="
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_pause() {
local options_with_args="
--help -h
"
local boolean_options=""
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_ps() {
local options_with_args="
--filter -f
--format
--last -n
"
local boolean_options="
--all -a
--latest -l
--no-trunc
--quiet -q
--size -s
--namespace --ns
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_stop() {
local options_with_args="
--timeout -t
"
local boolean_options=""
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_unpause() {
local options_with_args="
--help -h
"
local boolean_options=""
_complete_ "$options_with_args" "$boolean_options"
_kpod_wait() {
local options_with_args=""
local boolean_options="--help -h"
_complete_ "$options_with_args" "$boolean_options"
}
_complete_() {
local options_with_args=$1
local boolean_options="$2 -h --help"
case "$prev" in
$options_with_args)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) )
;;
esac
}
_kpod_load() {
local options_with_args="
--input -i
--signature-policy
"
local boolean_options="
--quiet -q
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_login() {
local options_with_args="
--username
-u
--password
-p
--authfile
"
local boolean_options="
--help
-h
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_logout() {
local options_with_args="
--authfile
"
local boolean_options="
--all
-a
--help
-h
"
_complete_ "$options_with_args" "$boolean_options"
}
_kpod_kpod() {
local options_with_args="
--config -c
--root
--runroot
--storage-driver
--storage-opt
--log-level
"
local boolean_options="
--help -h
--version -v
"
commands="
diff
export
history
images
info
inspect
kill
load
login
logout
logs
mount
pause
ps
pull
push
rename
rm
rmi
save
stats
stop
tag
umount
unmount
unpause
version
wait
"
case "$prev" in
$main_options_with_args_glob )
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) )
;;
*)
COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
;;
esac
}
_cli_bash_autocomplete() {
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
COMPREPLY=()
local cur prev words cword
_get_comp_words_by_ref -n : cur prev words cword
local command=${PROG} cpos=0
local counter=1
counter=1
while [ $counter -lt $cword ]; do
case "!${words[$counter]}" in
*)
command=$(echo "${words[$counter]}" | sed 's/-/_/g')
cpos=$counter
(( cpos++ ))
break
;;
esac
(( counter++ ))
done
local completions_func=_kpod_${command}
declare -F $completions_func >/dev/null && $completions_func
eval "$previous_extglob_setting"
return 0
}
complete -F _cli_bash_autocomplete $PROG

13
conmon/Makefile Normal file
View File

@ -0,0 +1,13 @@
src = $(wildcard *.c)
obj = $(src:.c=.o)
override LIBS += $(shell pkg-config --libs glib-2.0)
override CFLAGS += -std=c99 -Os -Wall -Wextra $(shell pkg-config --cflags glib-2.0)
conmon: $(obj)
mkdir -p ../bin/
$(CC) -o ../bin/$@ $^ $(CFLAGS) $(LIBS)
.PHONY: clean
clean:
rm -f $(obj) ../bin/conmon

149
conmon/cmsg.c Normal file
View File

@ -0,0 +1,149 @@
/*
* Copyright 2016 SUSE LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* NOTE: This code comes directly from runc/libcontainer/utils/cmsg.c. */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include "cmsg.h"
#define error(fmt, ...) \
({ \
fprintf(stderr, "nsenter: " fmt ": %m\n", ##__VA_ARGS__); \
errno = ECOMM; \
goto err; /* return value */ \
})
/*
* Sends a file descriptor along the sockfd provided. Returns the return
* value of sendmsg(2). Any synchronisation and preparation of state
* should be done external to this (we expect the other side to be in
* recvfd() in the code).
*/
ssize_t sendfd(int sockfd, struct file_t file)
{
struct msghdr msg = {0};
struct iovec iov[1] = {0};
struct cmsghdr *cmsg;
int *fdptr;
union {
char buf[CMSG_SPACE(sizeof(file.fd))];
struct cmsghdr align;
} u;
/*
* We need to send some other data along with the ancillary data,
* otherwise the other side won't recieve any data. This is very
* well-hidden in the documentation (and only applies to
* SOCK_STREAM). See the bottom part of unix(7).
*/
iov[0].iov_base = file.name;
iov[0].iov_len = strlen(file.name) + 1;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
fdptr = (int *) CMSG_DATA(cmsg);
memcpy(fdptr, &file.fd, sizeof(int));
return sendmsg(sockfd, &msg, 0);
}
/*
* Receives a file descriptor from the sockfd provided. Returns the file
* descriptor as sent from sendfd(). It will return the file descriptor
* or die (literally) trying. Any synchronisation and preparation of
* state should be done external to this (we expect the other side to be
* in sendfd() in the code).
*/
struct file_t recvfd(int sockfd)
{
struct msghdr msg = {0};
struct iovec iov[1] = {0};
struct cmsghdr *cmsg;
struct file_t file = {0};
int *fdptr;
int olderrno;
union {
char buf[CMSG_SPACE(sizeof(file.fd))];
struct cmsghdr align;
} u;
/* Allocate a buffer. */
/* TODO: Make this dynamic with MSG_PEEK. */
file.name = malloc(TAG_BUFFER);
if (!file.name)
error("recvfd: failed to allocate file.tag buffer\n");
/*
* We need to "recieve" the non-ancillary data even though we don't
* plan to use it at all. Otherwise, things won't work as expected.
* See unix(7) and other well-hidden documentation.
*/
iov[0].iov_base = file.name;
iov[0].iov_len = TAG_BUFFER;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = u.buf;
msg.msg_controllen = sizeof(u.buf);
ssize_t ret = recvmsg(sockfd, &msg, 0);
if (ret < 0)
goto err;
cmsg = CMSG_FIRSTHDR(&msg);
if (!cmsg)
error("recvfd: got NULL from CMSG_FIRSTHDR");
if (cmsg->cmsg_level != SOL_SOCKET)
error("recvfd: expected SOL_SOCKET in cmsg: %d", cmsg->cmsg_level);
if (cmsg->cmsg_type != SCM_RIGHTS)
error("recvfd: expected SCM_RIGHTS in cmsg: %d", cmsg->cmsg_type);
if (cmsg->cmsg_len != CMSG_LEN(sizeof(int)))
error("recvfd: expected correct CMSG_LEN in cmsg: %lu", cmsg->cmsg_len);
fdptr = (int *) CMSG_DATA(cmsg);
if (!fdptr || *fdptr < 0)
error("recvfd: recieved invalid pointer");
file.fd = *fdptr;
return file;
err:
olderrno = errno;
free(file.name);
errno = olderrno;
return (struct file_t){0};
}

38
conmon/cmsg.h Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright 2016 SUSE LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* NOTE: This code comes directly from runc/libcontainer/utils/cmsg.h. */
#pragma once
#if !defined(CMSG_H)
#define CMSG_H
#include <sys/types.h>
/* TODO: Implement this properly with MSG_PEEK. */
#define TAG_BUFFER 4096
/* This mirrors Go's (*os.File). */
struct file_t {
char *name;
int fd;
};
struct file_t recvfd(int sockfd);
ssize_t sendfd(int sockfd, struct file_t file);
#endif /* !defined(CMSG_H) */

1474
conmon/conmon.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
{
"cniVersion": "0.2.0",
"name": "crio-bridge",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.88.0.0/16",
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}

View File

@ -0,0 +1,4 @@
{
"cniVersion": "0.2.0",
"type": "loopback"
}

16
contrib/cni/README.md Normal file
View File

@ -0,0 +1,16 @@
## `contrib/cni` ##
There are a wide variety of different [CNI][cni] network configurations. This
directory just contains some example configurations that can be used as the
basis for your own configurations (distributions should package these files in
example directories).
To use these configurations, place them in `/etc/cni/net.d` (or the directory
specified by `crio.network.network_dir` in your `crio.conf`).
In addition, you need to install the [CNI plugins][cni] necessary into
`/opt/cni/bin` (or the directory specified by `crio.network.plugin_dir`). The
two plugins necessary for the example CNI configurations are `loopback` and
`bridge`.
[cni]: https://github.com/containernetworking/plugins

8
crio-umount.conf Normal file
View File

@ -0,0 +1,8 @@
# This contains a list of paths on host which will be unmounted inside
# container. (If they are mounted inside container).
# If there is a "/*" at the end, that means only mounts underneath that
# mounts (submounts) will be unmounted but top level mount will remain
# in place.
/var/run/containers/*
/var/lib/containers/storage/*

31
docs/kpod-attach.1.md Normal file
View File

@ -0,0 +1,31 @@
% 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)

46
docs/kpod-cp.1.md Normal file
View File

@ -0,0 +1,46 @@
% 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)

45
docs/kpod-diff.1.md Normal file
View File

@ -0,0 +1,45 @@
% 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>

44
docs/kpod-export.1.md Normal file
View File

@ -0,0 +1,44 @@
% 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>

106
docs/kpod-history.1.md Normal file
View File

@ -0,0 +1,106 @@
% 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>

60
docs/kpod-images.1.md Normal file
View File

@ -0,0 +1,60 @@
% 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>

36
docs/kpod-info.1.md Normal file
View File

@ -0,0 +1,36 @@
% 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)

171
docs/kpod-inspect.1.md Normal file
View File

@ -0,0 +1,171 @@
% 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)

33
docs/kpod-kill.1.md Normal file
View File

@ -0,0 +1,33 @@
% 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>

76
docs/kpod-load.1.md Normal file
View File

@ -0,0 +1,76 @@
% 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>

65
docs/kpod-login.1.md Normal file
View File

@ -0,0 +1,65 @@
% 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>

56
docs/kpod-logout.1.md Normal file
View File

@ -0,0 +1,56 @@
% 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>

61
docs/kpod-logs.1.md Normal file
View File

@ -0,0 +1,61 @@
% 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>

50
docs/kpod-mount.1.md Normal file
View File

@ -0,0 +1,50 @@
% 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)

24
docs/kpod-pause.1.md Normal file
View File

@ -0,0 +1,24 @@
% 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>

131
docs/kpod-ps.1.md Normal file
View File

@ -0,0 +1,131 @@
% 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>

135
docs/kpod-pull.1.md Normal file
View File

@ -0,0 +1,135 @@
% 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>

116
docs/kpod-push.1.md Normal file
View File

@ -0,0 +1,116 @@
% 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)

24
docs/kpod-rename.1.md Normal file
View File

@ -0,0 +1,24 @@
% 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>

31
docs/kpod-rm.1.md Normal file
View File

@ -0,0 +1,31 @@
% 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>

32
docs/kpod-rmi.1.md Normal file
View File

@ -0,0 +1,32 @@
% 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>

60
docs/kpod-save.1.md Normal file
View File

@ -0,0 +1,60 @@
% 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>

37
docs/kpod-stats.1.md Normal file
View File

@ -0,0 +1,37 @@
% 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>

35
docs/kpod-stop.1.md Normal file
View File

@ -0,0 +1,35 @@
% 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>

34
docs/kpod-tag.1.md Normal file
View File

@ -0,0 +1,34 @@
% 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>

19
docs/kpod-umount.1.md Normal file
View File

@ -0,0 +1,19 @@
% 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)

24
docs/kpod-unpause.1.md Normal file
View File

@ -0,0 +1,24 @@
% 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>

24
docs/kpod-version.1.md Normal file
View File

@ -0,0 +1,24 @@
% 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>

36
docs/kpod-wait.1.md Normal file
View File

@ -0,0 +1,36 @@
% 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>

137
docs/kpod.1.md Normal file
View File

@ -0,0 +1,137 @@
% 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
### 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
### 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>

BIN
docs/play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

7
hack/btrfs_installed_tag.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
cc -E - > /dev/null 2> /dev/null << EOF
#include <btrfs/ioctl.h>
EOF
if test $? -ne 0 ; then
echo exclude_graphdriver_btrfs
fi

7
hack/btrfs_tag.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
cc -E - > /dev/null 2> /dev/null << EOF
#include <btrfs/version.h>
EOF
if test $? -ne 0 ; then
echo btrfs_noversion
fi

41
hack/find-godeps.sh Executable file
View File

@ -0,0 +1,41 @@
#!/bin/bash
#
# $1 - base path of the source tree
# $2 - subpath under $1 to find *.go dependencies for
# $3 - package name (eg, github.com/organization/project)
set -o errexit
set -o nounset
set -o pipefail
# might be called from makefile before basepath is set up; just return
# empty deps. The make target will then ensure that GOPATH is set up
# correctly, and go build will build everything the first time around
# anyway. Next time we get here everything will be fine.
if [ ! -d "$1/$2" ]; then
exit 0
fi
function find-deps() {
local basepath=$1
local srcdir=$2
local pkgname=$3
local deps=
# gather imports from cri-o
pkgs=$(cd ${basepath}/${srcdir} && go list -f "{{.Imports}}" . | tr ' ' '\n' | tr -d '[]' | grep -v "/vendor/" | grep ${pkgname} | sed -e "s|${pkgname}/||g")
# add each Go import's sources to the deps list,
# and recursively get that imports's imports too
for dep in ${pkgs}; do
deps+="$(ls ${basepath}/${dep}/*.go | sed -e "s|${basepath}/||g") "
# add deps of this package too
deps+="$(find-deps ${basepath} ${dep} ${pkgname}) "
done
echo "${deps}" | sort | uniq
}
# add Go sources from the current package at the end
echo "$(find-deps "$1" "$2" "$3" | xargs) $(cd $1 && ls $2/*.go | xargs)"

15
hack/libdm_tag.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
tmpdir="$PWD/tmp.$RANDOM"
mkdir -p "$tmpdir"
trap 'rm -fr "$tmpdir"' EXIT
cc -o "$tmpdir"/libdm_tag -ldevmapper -x c - > /dev/null 2> /dev/null << EOF
#include <libdevmapper.h>
int main() {
struct dm_task *task;
dm_task_deferred_remove(task);
return 0;
}
EOF
if test $? -ne 0 ; then
echo libdm_no_deferred_remove
fi

4
hack/ostree_tag.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
if ! pkg-config ostree-1 2> /dev/null ; then
echo containers_image_ostree_stub
fi

4
hack/selinux_tag.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
if pkg-config libselinux 2> /dev/null ; then
echo selinux
fi

21
hack/verify-gofmt.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
set -o errexit
set -o nounset
set -o pipefail
find_files() {
find . -not \( \
\( \
-wholename '*/vendor/*' \
\) -prune \
\) -name '*.go'
}
GOFMT="gofmt -s"
bad_files=$(find_files | xargs $GOFMT -l)
if [[ -n "${bad_files}" ]]; then
echo "!!! '$GOFMT' needs to be run on the following files: "
echo "${bad_files}"
exit 1
fi

90
hooks.md Normal file
View File

@ -0,0 +1,90 @@
# OCI Hooks Configuration
[The OCI Runtime Specification defines POSIX-platform Hooks:](
https://github.com/opencontainers/runtime-spec/blob/master/config.md#posix-platform-hooks)
## POSIX-platform Hooks
For POSIX platforms, the configuration structure supports hooks for configuring custom actions related to the life cycle of the container.
hooks (object, OPTIONAL) MAY contain any of the following properties:
* prestart (array of objects, OPTIONAL) is an array of pre-start hooks. Entries in the array contain the following properties:
* path (string, REQUIRED) with similar semantics to [IEEE Std 1003.1-2008 execv's path][ieee-1003.1-2008-functions-exec]. This specification extends the IEEE standard in that path MUST be absolute.
* args (array of strings, OPTIONAL) with the same semantics as [IEEE Std 1003.1-2008 execv's argv][ieee-1003.1-2008-functions-exec].
* env (array of strings, OPTIONAL) with the same semantics as IEEE Std 1003.1-2008's environ.
* timeout (int, OPTIONAL) is the number of seconds before aborting the hook. If set, timeout MUST be greater than zero.
* poststart (array of objects, OPTIONAL) is an array of post-start hooks. Entries in the array have the same schema as pre-start entries.
* poststop (array of objects, OPTIONAL) is an array of post-stop hooks. Entries in the array have the same schema as pre-start entries.
Hooks allow users to specify programs to run before or after various lifecycle events. Hooks MUST be called in the listed order. The state of the container MUST be passed to hooks over stdin so that they may do work appropriate to the current state of the container.
### Prestart
The Prestart hooks MUST be called after the start operation is called but before the user-specified program command is executed. On Linux, for example, they are called after the container namespaces are created, so they provide an opportunity to customize the container (e.g. the network namespace could be specified in this hook).
### Poststart
The post-start hooks MUST be called after the user-specified process is executed but before the start operation returns. For example, this hook can notify the user that the container process is spawned.
### Poststop
The post-stop hooks MUST be called after the container is deleted but before the delete operation returns. Cleanup or debugging functions are examples of such a hook.
## CRI-O configuration files for automatically enabling Hooks
The way you enable the hooks above is by editing the OCI Specification to add your hook before running the oci runtime, like runc. But this is what `CRI-O` and `Kpod create` do for you, so we wanted a way for developers to drop configuration files onto the system, so that their hooks would be able to be plugged in.
One problem with hooks is that the runtime actually stalls execution of the container before running the hooks and stalls completion of the container, until all hooks complete. This can cause some performance issues. Also a lot of hooks just check if certain configuration is set and then exit early, without doing anything. For example the [oci-systemd-hook](https://github.com/projectatomic/oci-systemd-hook) only executes if the command is `init` or `systemd`, otherwise it just exits. This means if we automatically enable all hooks, every container will have to execute oci-systemd-hook, even if they don't run systemd inside of the container. Also since there are three stages, prestart, poststart, poststop each hook gets executed three times.
### Json Definition
We decided to add a json file for hook builders which allows them to tell CRI-O when to run the hook and in which stage.
CRI-O reads all json files in /usr/share/containers/oci/hooks.d/*.json and /etc/containers/oci/hooks.d and sets up the specified hooks to run. If the same name is in both directories, the one in /etc/containers/oci/hooks.d takes precedence.
The json configuration looks like this in GO
```
// HookParams is the structure returned from read the hooks configuration
type HookParams struct {
Hook string `json:"hook"`
Stage []string `json:"stages"`
Cmds []string `json:"cmds"`
Annotations []string `json:"annotations"`
HasBindMounts bool `json:"hasbindmounts"`
}
```
| Key | Description | Required/Optional |
| ------ |----------------------------------------------------------------------------------------------------------------------------------- | -------- |
| hook | Path to the hook | Required |
| stages | List of stages to run the hook in: Valid options are `prestart`, `poststart`, `poststop` | Required |
| cmds | List of regular expressions to match the command for running the container. If the command matches a regex, the hook will be run | Optional |
| annotations | List of regular expressions to match against the Annotations in the container runtime spec, if an Annotation matches the hook will be run|optional |
| hasbindmounts | Tells CRI-O to run the hook if the container has bind mounts from the host into the container | Optional |
### Example
```
cat /etc/containers/oci/hooks.d/oci-systemd-hook.json
{
"cmds": [".*/init$" , ".*/systemd$" ],
"hook": "/usr/libexec/oci/hooks.d/oci-systemd-hook",
"stages": [ "prestart", "poststop" ]
}
```
In the above example CRI-O will only run the oci-systemd-hook in the prestart and poststop stage, if the command ends with /init or /systemd
```
cat /etc/containers/oci/hooks.d/oci-systemd-hook.json
{
"hasbindmounts": true,
"hook": "/usr/libexec/oci/hooks.d/oci-umount",
"stages": [ "prestart" ]
}
```
In this example the oci-umount will only be run during the prestart phase if the container has volume/bind mounts from the host into the container.

18446
kpod-images.json Normal file

File diff suppressed because it is too large Load Diff

308
libkpod/config.go Normal file
View File

@ -0,0 +1,308 @@
package libkpod
import (
"bytes"
"io/ioutil"
"github.com/BurntSushi/toml"
"github.com/projectatomic/libpod/oci"
"github.com/opencontainers/selinux/go-selinux"
)
// Default paths if none are specified
const (
crioRoot = "/var/lib/containers/storage"
crioRunRoot = "/var/run/containers/storage"
conmonPath = "/usr/local/libexec/crio/conmon"
pauseImage = "kubernetes/pause"
pauseCommand = "/pause"
defaultTransport = "docker://"
seccompProfilePath = "/etc/crio/seccomp.json"
apparmorProfileName = "crio-default"
cniConfigDir = "/etc/cni/net.d/"
cniBinDir = "/opt/cni/bin/"
cgroupManager = oci.CgroupfsCgroupsManager
lockPath = "/run/crio.lock"
containerExitsDir = oci.ContainerExitsDir
)
// Config represents the entire set of configuration values that can be set for
// the server. This is intended to be loaded from a toml-encoded config file.
type Config struct {
RootConfig
RuntimeConfig
ImageConfig
NetworkConfig
}
// ImageVolumesType describes image volume handling strategies
type ImageVolumesType string
const (
// ImageVolumesMkdir option is for using mkdir to handle image volumes
ImageVolumesMkdir ImageVolumesType = "mkdir"
// ImageVolumesIgnore option is for ignoring image volumes altogether
ImageVolumesIgnore ImageVolumesType = "ignore"
// ImageVolumesBind option is for using bind mounted volumes
ImageVolumesBind ImageVolumesType = "bind"
)
const (
// DefaultPidsLimit is the default value for maximum number of processes
// allowed inside a container
DefaultPidsLimit = 1024
// DefaultLogSizeMax is the default value for the maximum log size
// allowed for a container. Negative values mean that no limit is imposed.
DefaultLogSizeMax = -1
)
// This structure is necessary to fake the TOML tables when parsing,
// while also not requiring a bunch of layered structs for no good
// reason.
// RootConfig represents the root of the "crio" TOML config table.
type RootConfig struct {
// Root is a path to the "root directory" where data not
// explicitly handled by other options will be stored.
Root string `toml:"root"`
// RunRoot is a path to the "run directory" where state information not
// explicitly handled by other options will be stored.
RunRoot string `toml:"runroot"`
// Storage is the name of the storage driver which handles actually
// storing the contents of containers.
Storage string `toml:"storage_driver"`
// StorageOption is a list of storage driver specific options.
StorageOptions []string `toml:"storage_option"`
// LogDir is the default log directory were all logs will go unless kubelet
// tells us to put them somewhere else.
LogDir string `toml:"log_dir"`
// FileLocking specifies whether to use file-based or in-memory locking
// File-based locking is required when multiple users of libkpod are
// present on the same system
FileLocking bool `toml:"file_locking"`
}
// RuntimeConfig represents the "crio.runtime" TOML config table.
type RuntimeConfig struct {
// Runtime is the OCI compatible runtime used for trusted container workloads.
// This is a mandatory setting as this runtime will be the default one and
// will also be used for untrusted container workloads if
// RuntimeUntrustedWorkload is not set.
Runtime string `toml:"runtime"`
// RuntimeUntrustedWorkload is the OCI compatible runtime used for untrusted
// container workloads. This is an optional setting, except if
// DefaultWorkloadTrust is set to "untrusted".
RuntimeUntrustedWorkload string `toml:"runtime_untrusted_workload"`
// DefaultWorkloadTrust is the default level of trust crio puts in container
// workloads. This can either be "trusted" or "untrusted" and the default
// is "trusted"
// Containers can be run through different container runtimes, depending on
// the trust hints we receive from kubelet:
// - If kubelet tags a container workload as untrusted, crio will try first
// to run it through the untrusted container workload runtime. If it is not
// set, crio will use the trusted runtime.
// - If kubelet does not provide any information about the container workload trust
// level, the selected runtime will depend on the DefaultWorkloadTrust setting.
// If it is set to "untrusted", then all containers except for the host privileged
// ones, will be run by the RuntimeUntrustedWorkload runtime. Host privileged
// containers are by definition trusted and will always use the trusted container
// runtime. If DefaultWorkloadTrust is set to "trusted", crio will use the trusted
// container runtime for all containers.
DefaultWorkloadTrust string `toml:"default_workload_trust"`
// NoPivot instructs the runtime to not use `pivot_root`, but instead use `MS_MOVE`
NoPivot bool `toml:"no_pivot"`
// Conmon is the path to conmon binary, used for managing the runtime.
Conmon string `toml:"conmon"`
// ConmonEnv is the environment variable list for conmon process.
ConmonEnv []string `toml:"conmon_env"`
// SELinux determines whether or not SELinux is used for pod separation.
SELinux bool `toml:"selinux"`
// SeccompProfile is the seccomp json profile path which is used as the
// default for the runtime.
SeccompProfile string `toml:"seccomp_profile"`
// ApparmorProfile is the apparmor profile name which is used as the
// default for the runtime.
ApparmorProfile string `toml:"apparmor_profile"`
// CgroupManager is the manager implementation name which is used to
// handle cgroups for containers.
CgroupManager string `toml:"cgroup_manager"`
// HooksDirPath location of oci hooks config files
HooksDirPath string `toml:"hooks_dir_path"`
// DefaultMounts is the list of mounts to be mounted for each container
// The format of each mount is "host-path:container-path"
DefaultMounts []string `toml:"default_mounts"`
// Hooks List of hooks to run with container
Hooks map[string]HookParams
// PidsLimit is the number of processes each container is restricted to
// by the cgroup process number controller.
PidsLimit int64 `toml:"pids_limit"`
// LogSizeMax is the maximum number of bytes after which the log file
// will be truncated. It can be expressed as a human-friendly string
// that is parsed to bytes.
// Negative values indicate that the log file won't be truncated.
LogSizeMax int64 `toml:"log_size_max"`
// ContainerExitsDir is the directory in which container exit files are
// written to by conmon.
ContainerExitsDir string `toml:"container_exits_dir"`
}
// ImageConfig represents the "crio.image" TOML config table.
type ImageConfig struct {
// DefaultTransport is a value we prefix to image names that fail to
// validate source references.
DefaultTransport string `toml:"default_transport"`
// PauseImage is the name of an image which we use to instantiate infra
// containers.
PauseImage string `toml:"pause_image"`
// PauseCommand is the path of the binary we run in an infra
// container that's been instantiated using PauseImage.
PauseCommand string `toml:"pause_command"`
// SignaturePolicyPath is the name of the file which decides what sort
// of policy we use when deciding whether or not to trust an image that
// we've pulled. Outside of testing situations, it is strongly advised
// that this be left unspecified so that the default system-wide policy
// will be used.
SignaturePolicyPath string `toml:"signature_policy"`
// InsecureRegistries is a list of registries that must be contacted w/o
// TLS verification.
InsecureRegistries []string `toml:"insecure_registries"`
// ImageVolumes controls how volumes specified in image config are handled
ImageVolumes ImageVolumesType `toml:"image_volumes"`
// Registries holds a list of registries used to pull unqualified images
Registries []string `toml:"registries"`
}
// NetworkConfig represents the "crio.network" TOML config table
type NetworkConfig struct {
// NetworkDir is where CNI network configuration files are stored.
NetworkDir string `toml:"network_dir"`
// PluginDir is where CNI plugin binaries are stored.
PluginDir string `toml:"plugin_dir"`
}
// tomlConfig is another way of looking at a Config, which is
// TOML-friendly (it has all of the explicit tables). It's just used for
// conversions.
type tomlConfig struct {
Crio struct {
RootConfig
Runtime struct{ RuntimeConfig } `toml:"runtime"`
Image struct{ ImageConfig } `toml:"image"`
Network struct{ NetworkConfig } `toml:"network"`
} `toml:"crio"`
}
func (t *tomlConfig) toConfig(c *Config) {
c.RootConfig = t.Crio.RootConfig
c.RuntimeConfig = t.Crio.Runtime.RuntimeConfig
c.ImageConfig = t.Crio.Image.ImageConfig
c.NetworkConfig = t.Crio.Network.NetworkConfig
}
func (t *tomlConfig) fromConfig(c *Config) {
t.Crio.RootConfig = c.RootConfig
t.Crio.Runtime.RuntimeConfig = c.RuntimeConfig
t.Crio.Image.ImageConfig = c.ImageConfig
t.Crio.Network.NetworkConfig = c.NetworkConfig
}
// UpdateFromFile populates the Config from the TOML-encoded file at the given path.
// Returns errors encountered when reading or parsing the files, or nil
// otherwise.
func (c *Config) UpdateFromFile(path string) error {
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
t := new(tomlConfig)
t.fromConfig(c)
_, err = toml.Decode(string(data), t)
if err != nil {
return err
}
t.toConfig(c)
return nil
}
// ToFile outputs the given Config as a TOML-encoded file at the given path.
// Returns errors encountered when generating or writing the file, or nil
// otherwise.
func (c *Config) ToFile(path string) error {
var w bytes.Buffer
e := toml.NewEncoder(&w)
t := new(tomlConfig)
t.fromConfig(c)
if err := e.Encode(*t); err != nil {
return err
}
return ioutil.WriteFile(path, w.Bytes(), 0644)
}
// DefaultConfig returns the default configuration for crio.
func DefaultConfig() *Config {
return &Config{
RootConfig: RootConfig{
Root: crioRoot,
RunRoot: crioRunRoot,
LogDir: "/var/log/crio/pods",
FileLocking: true,
},
RuntimeConfig: RuntimeConfig{
Runtime: "/usr/bin/runc",
RuntimeUntrustedWorkload: "",
DefaultWorkloadTrust: "trusted",
Conmon: conmonPath,
ConmonEnv: []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
},
SELinux: selinux.GetEnabled(),
SeccompProfile: seccompProfilePath,
ApparmorProfile: apparmorProfileName,
CgroupManager: cgroupManager,
PidsLimit: DefaultPidsLimit,
ContainerExitsDir: containerExitsDir,
HooksDirPath: DefaultHooksDirPath,
LogSizeMax: DefaultLogSizeMax,
},
ImageConfig: ImageConfig{
DefaultTransport: defaultTransport,
PauseImage: pauseImage,
PauseCommand: pauseCommand,
SignaturePolicyPath: "",
ImageVolumes: ImageVolumesMkdir,
},
NetworkConfig: NetworkConfig{
NetworkDir: cniConfigDir,
PluginDir: cniBinDir,
},
}
}

54
libkpod/config_test.go Normal file
View File

@ -0,0 +1,54 @@
package libkpod
import (
"io/ioutil"
"os"
"testing"
)
// TestConfigToFile ensures Config.ToFile(..) encodes and writes out
// a Config instance toa a file on disk.
func TestConfigToFile(t *testing.T) {
// Test with a default configuration
c := DefaultConfig()
tmpfile, err := ioutil.TempFile("", "config")
if err != nil {
t.Fatalf("Unable to create temporary file: %+v", err)
}
// Clean up temporary file
defer os.Remove(tmpfile.Name())
// Make the ToFile calls
err = c.ToFile(tmpfile.Name())
// Make sure no errors occurred while populating the file
if err != nil {
t.Fatalf("Unable to write to temporary file: %+v", err)
}
// Make sure the file is on disk
if _, err := os.Stat(tmpfile.Name()); os.IsNotExist(err) {
t.Fatalf("The config file was not written to disk: %+v", err)
}
}
// TestConfigUpdateFromFile ensures Config.UpdateFromFile(..) properly
// updates an already create Config instancec with new data.
func TestConfigUpdateFromFile(t *testing.T) {
// Test with a default configuration
c := DefaultConfig()
// Make the ToFile calls
err := c.UpdateFromFile("testdata/config.toml")
// Make sure no errors occurred while populating from the file
if err != nil {
t.Fatalf("Unable update config from file: %+v", err)
}
// Check fields that should have changed after UpdateFromFile
if c.Storage != "overlay2" {
t.Fatalf("Update failed. Storage did not change to overlay2")
}
if c.RuntimeConfig.PidsLimit != 2048 {
t.Fatalf("Update failed. RuntimeConfig.PidsLimit did not change to 2048")
}
}

157
libkpod/container.go Normal file
View File

@ -0,0 +1,157 @@
package libkpod
import (
"fmt"
cstorage "github.com/containers/storage"
"github.com/projectatomic/libpod/libkpod/sandbox"
"github.com/projectatomic/libpod/oci"
"github.com/projectatomic/libpod/pkg/registrar"
"github.com/pkg/errors"
)
// GetStorageContainer searches for a container with the given name or ID in the given store
func (c *ContainerServer) GetStorageContainer(container string) (*cstorage.Container, error) {
ociCtr, err := c.LookupContainer(container)
if err != nil {
return nil, err
}
return c.store.Container(ociCtr.ID())
}
// GetContainerTopLayerID gets the ID of the top layer of the given container
func (c *ContainerServer) GetContainerTopLayerID(containerID string) (string, error) {
ctr, err := c.GetStorageContainer(containerID)
if err != nil {
return "", err
}
return ctr.LayerID, nil
}
// GetContainerRwSize Gets the size of the mutable top layer of the container
func (c *ContainerServer) GetContainerRwSize(containerID string) (int64, error) {
container, err := c.store.Container(containerID)
if err != nil {
return 0, err
}
// Get the size of the top layer by calculating the size of the diff
// between the layer and its parent. The top layer of a container is
// the only RW layer, all others are immutable
layer, err := c.store.Layer(container.LayerID)
if err != nil {
return 0, err
}
return c.store.DiffSize(layer.Parent, layer.ID)
}
// GetContainerRootFsSize gets the size of the container's root filesystem
// A container FS is split into two parts. The first is the top layer, a
// mutable layer, and the rest is the RootFS: the set of immutable layers
// that make up the image on which the container is based
func (c *ContainerServer) GetContainerRootFsSize(containerID string) (int64, error) {
container, err := c.store.Container(containerID)
if err != nil {
return 0, err
}
// Ignore the size of the top layer. The top layer is a mutable RW layer
// and is not considered a part of the rootfs
rwLayer, err := c.store.Layer(container.LayerID)
if err != nil {
return 0, err
}
layer, err := c.store.Layer(rwLayer.Parent)
if err != nil {
return 0, err
}
size := int64(0)
for layer.Parent != "" {
layerSize, err := c.store.DiffSize(layer.Parent, layer.ID)
if err != nil {
return size, errors.Wrapf(err, "getting diffsize of layer %q and its parent %q", layer.ID, layer.Parent)
}
size += layerSize
layer, err = c.store.Layer(layer.Parent)
if err != nil {
return 0, err
}
}
// Get the size of the last layer. Has to be outside of the loop
// because the parent of the last layer is "", andlstore.Get("")
// will return an error
layerSize, err := c.store.DiffSize(layer.Parent, layer.ID)
return size + layerSize, err
}
// GetContainerFromRequest gets an oci container matching the specified full or partial id
func (c *ContainerServer) GetContainerFromRequest(cid string) (*oci.Container, error) {
if cid == "" {
return nil, fmt.Errorf("container ID should not be empty")
}
containerID, err := c.ctrIDIndex.Get(cid)
if err != nil {
return nil, fmt.Errorf("container with ID starting with %s not found: %v", cid, err)
}
ctr := c.GetContainer(containerID)
if ctr == nil {
return nil, fmt.Errorf("specified container not found: %s", containerID)
}
return ctr, nil
}
func (c *ContainerServer) getSandboxFromRequest(pid string) (*sandbox.Sandbox, error) {
if pid == "" {
return nil, fmt.Errorf("pod ID should not be empty")
}
podID, err := c.podIDIndex.Get(pid)
if err != nil {
return nil, fmt.Errorf("pod with ID starting with %s not found: %v", pid, err)
}
sb := c.GetSandbox(podID)
if sb == nil {
return nil, fmt.Errorf("specified pod not found: %s", podID)
}
return sb, nil
}
// LookupContainer returns the container with the given name or full or partial id
func (c *ContainerServer) LookupContainer(idOrName string) (*oci.Container, error) {
if idOrName == "" {
return nil, fmt.Errorf("container ID or name should not be empty")
}
ctrID, err := c.ctrNameIndex.Get(idOrName)
if err != nil {
if err == registrar.ErrNameNotReserved {
ctrID = idOrName
} else {
return nil, err
}
}
return c.GetContainerFromRequest(ctrID)
}
// LookupSandbox returns the pod sandbox with the given name or full or partial id
func (c *ContainerServer) LookupSandbox(idOrName string) (*sandbox.Sandbox, error) {
if idOrName == "" {
return nil, fmt.Errorf("container ID or name should not be empty")
}
podID, err := c.podNameIndex.Get(idOrName)
if err != nil {
if err == registrar.ErrNameNotReserved {
podID = idOrName
} else {
return nil, err
}
}
return c.getSandboxFromRequest(podID)
}

210
libkpod/container_data.go Normal file
View File

@ -0,0 +1,210 @@
package libkpod
import (
"encoding/json"
"os"
"time"
"k8s.io/apimachinery/pkg/fields"
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
"github.com/projectatomic/libpod/libpod/driver"
"github.com/projectatomic/libpod/libpod/images"
"github.com/projectatomic/libpod/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
}

775
libkpod/container_server.go Normal file
View File

@ -0,0 +1,775 @@
package libkpod
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/containers/image/types"
cstorage "github.com/containers/storage"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/truncindex"
"github.com/projectatomic/libpod/libkpod/sandbox"
"github.com/projectatomic/libpod/oci"
"github.com/projectatomic/libpod/pkg/annotations"
"github.com/projectatomic/libpod/pkg/registrar"
"github.com/projectatomic/libpod/pkg/storage"
"github.com/opencontainers/runc/libcontainer"
rspec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/selinux/go-selinux"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
)
// ContainerServer implements the ImageServer
type ContainerServer struct {
runtime *oci.Runtime
store cstorage.Store
storageImageServer storage.ImageServer
storageRuntimeServer storage.RuntimeServer
updateLock sync.RWMutex
ctrNameIndex *registrar.Registrar
ctrIDIndex *truncindex.TruncIndex
podNameIndex *registrar.Registrar
podIDIndex *truncindex.TruncIndex
hooks map[string]HookParams
imageContext *types.SystemContext
stateLock sync.Locker
state *containerServerState
config *Config
}
// Runtime returns the oci runtime for the ContainerServer
func (c *ContainerServer) Runtime() *oci.Runtime {
return c.runtime
}
// Hooks returns the oci hooks for the ContainerServer
func (c *ContainerServer) Hooks() map[string]HookParams {
return c.hooks
}
// Store returns the Store for the ContainerServer
func (c *ContainerServer) Store() cstorage.Store {
return c.store
}
// StorageImageServer returns the ImageServer for the ContainerServer
func (c *ContainerServer) StorageImageServer() storage.ImageServer {
return c.storageImageServer
}
// CtrNameIndex returns the Registrar for the ContainerServer
func (c *ContainerServer) CtrNameIndex() *registrar.Registrar {
return c.ctrNameIndex
}
// CtrIDIndex returns the TruncIndex for the ContainerServer
func (c *ContainerServer) CtrIDIndex() *truncindex.TruncIndex {
return c.ctrIDIndex
}
// PodNameIndex returns the index of pod names
func (c *ContainerServer) PodNameIndex() *registrar.Registrar {
return c.podNameIndex
}
// PodIDIndex returns the index of pod IDs
func (c *ContainerServer) PodIDIndex() *truncindex.TruncIndex {
return c.podIDIndex
}
// ImageContext returns the SystemContext for the ContainerServer
func (c *ContainerServer) ImageContext() *types.SystemContext {
return c.imageContext
}
// Config gets the configuration for the ContainerServer
func (c *ContainerServer) Config() *Config {
return c.config
}
// StorageRuntimeServer gets the runtime server for the ContainerServer
func (c *ContainerServer) StorageRuntimeServer() storage.RuntimeServer {
return c.storageRuntimeServer
}
// New creates a new ContainerServer with options provided
func New(config *Config) (*ContainerServer, error) {
store, err := cstorage.GetStore(cstorage.StoreOptions{
RunRoot: config.RunRoot,
GraphRoot: config.Root,
GraphDriverName: config.Storage,
GraphDriverOptions: config.StorageOptions,
})
if err != nil {
return nil, err
}
imageService, err := storage.GetImageService(store, config.DefaultTransport, config.InsecureRegistries, config.Registries)
if err != nil {
return nil, err
}
storageRuntimeService := storage.GetRuntimeService(imageService, config.PauseImage)
if err != nil {
return nil, err
}
runtime, err := oci.New(config.Runtime, config.RuntimeUntrustedWorkload, config.DefaultWorkloadTrust, config.Conmon, config.ConmonEnv, config.CgroupManager, config.ContainerExitsDir, config.LogSizeMax, config.NoPivot)
if err != nil {
return nil, err
}
var lock sync.Locker
if config.FileLocking {
fileLock, err := cstorage.GetLockfile(lockPath)
if err != nil {
return nil, fmt.Errorf("error obtaining lockfile: %v", err)
}
lock = fileLock
} else {
lock = new(sync.Mutex)
}
hooks := make(map[string]HookParams)
// If hooks directory is set in config use it
if config.HooksDirPath != "" {
if err := readHooks(config.HooksDirPath, hooks); err != nil {
return nil, err
}
// If user overrode default hooks, this means it is in a test, so don't
// use OverrideHooksDirPath
if config.HooksDirPath == DefaultHooksDirPath {
if err := readHooks(OverrideHooksDirPath, hooks); err != nil {
return nil, err
}
}
}
return &ContainerServer{
runtime: runtime,
store: store,
storageImageServer: imageService,
storageRuntimeServer: storageRuntimeService,
ctrNameIndex: registrar.NewRegistrar(),
ctrIDIndex: truncindex.NewTruncIndex([]string{}),
podNameIndex: registrar.NewRegistrar(),
podIDIndex: truncindex.NewTruncIndex([]string{}),
imageContext: &types.SystemContext{SignaturePolicyPath: config.SignaturePolicyPath},
hooks: hooks,
stateLock: lock,
state: &containerServerState{
containers: oci.NewMemoryStore(),
infraContainers: oci.NewMemoryStore(),
sandboxes: make(map[string]*sandbox.Sandbox),
processLevels: make(map[string]int),
},
config: config,
}, nil
}
// Update makes changes to the server's state (lists of pods and containers) to
// reflect the list of pods and containers that are stored on disk, possibly
// having been modified by other parties
func (c *ContainerServer) Update() error {
c.updateLock.Lock()
defer c.updateLock.Unlock()
containers, err := c.store.Containers()
if err != nil && !os.IsNotExist(errors.Cause(err)) {
logrus.Warnf("could not read containers and sandboxes: %v", err)
return err
}
newPods := map[string]*storage.RuntimeContainerMetadata{}
oldPods := map[string]string{}
removedPods := map[string]string{}
newPodContainers := map[string]*storage.RuntimeContainerMetadata{}
oldPodContainers := map[string]string{}
removedPodContainers := map[string]string{}
for _, container := range containers {
if c.HasSandbox(container.ID) {
// FIXME: do we need to reload/update any info about the sandbox?
oldPods[container.ID] = container.ID
oldPodContainers[container.ID] = container.ID
continue
}
if c.GetContainer(container.ID) != nil {
// FIXME: do we need to reload/update any info about the container?
oldPodContainers[container.ID] = container.ID
continue
}
// not previously known, so figure out what it is
metadata, err2 := c.storageRuntimeServer.GetContainerMetadata(container.ID)
if err2 != nil {
logrus.Errorf("error parsing metadata for %s: %v, ignoring", container.ID, err2)
continue
}
if metadata.Pod {
newPods[container.ID] = &metadata
} else {
newPodContainers[container.ID] = &metadata
}
}
c.ctrIDIndex.Iterate(func(id string) {
if _, ok := oldPodContainers[id]; !ok {
// this container's ID wasn't in the updated list -> removed
removedPodContainers[id] = id
} else {
ctr := c.GetContainer(id)
if ctr != nil {
// if the container exists, update its state
c.ContainerStateFromDisk(c.GetContainer(id))
}
}
})
for removedPodContainer := range removedPodContainers {
// forget this container
ctr := c.GetContainer(removedPodContainer)
if ctr == nil {
logrus.Warnf("bad state when getting container removed %+v", removedPodContainer)
continue
}
c.ReleaseContainerName(ctr.Name())
c.RemoveContainer(ctr)
if err = c.ctrIDIndex.Delete(ctr.ID()); err != nil {
return err
}
logrus.Debugf("forgetting removed pod container %s", ctr.ID())
}
c.PodIDIndex().Iterate(func(id string) {
if _, ok := oldPods[id]; !ok {
// this pod's ID wasn't in the updated list -> removed
removedPods[id] = id
}
})
for removedPod := range removedPods {
// forget this pod
sb := c.GetSandbox(removedPod)
if sb == nil {
logrus.Warnf("bad state when getting pod to remove %+v", removedPod)
continue
}
podInfraContainer := sb.InfraContainer()
c.ReleaseContainerName(podInfraContainer.Name())
c.RemoveContainer(podInfraContainer)
if err = c.ctrIDIndex.Delete(podInfraContainer.ID()); err != nil {
return err
}
sb.RemoveInfraContainer()
c.ReleasePodName(sb.Name())
c.RemoveSandbox(sb.ID())
if err = c.podIDIndex.Delete(sb.ID()); err != nil {
return err
}
logrus.Debugf("forgetting removed pod %s", sb.ID())
}
for sandboxID := range newPods {
// load this pod
if err = c.LoadSandbox(sandboxID); err != nil {
logrus.Warnf("could not load new pod sandbox %s: %v, ignoring", sandboxID, err)
} else {
logrus.Debugf("loaded new pod sandbox %s", sandboxID, err)
}
}
for containerID := range newPodContainers {
// load this container
if err = c.LoadContainer(containerID); err != nil {
logrus.Warnf("could not load new sandbox container %s: %v, ignoring", containerID, err)
} else {
logrus.Debugf("loaded new pod container %s", containerID, err)
}
}
return nil
}
// LoadSandbox loads a sandbox from the disk into the sandbox store
func (c *ContainerServer) LoadSandbox(id string) error {
config, err := c.store.FromContainerDirectory(id, "config.json")
if err != nil {
return err
}
var m rspec.Spec
if err = json.Unmarshal(config, &m); err != nil {
return err
}
labels := make(map[string]string)
if err = json.Unmarshal([]byte(m.Annotations[annotations.Labels]), &labels); err != nil {
return err
}
name := m.Annotations[annotations.Name]
name, err = c.ReservePodName(id, name)
if err != nil {
return err
}
defer func() {
if err != nil {
c.ReleasePodName(name)
}
}()
var metadata pb.PodSandboxMetadata
if err = json.Unmarshal([]byte(m.Annotations[annotations.Metadata]), &metadata); err != nil {
return err
}
ip := m.Annotations[annotations.IP]
processLabel, mountLabel, err := label.InitLabels(label.DupSecOpt(m.Process.SelinuxLabel))
if err != nil {
return err
}
kubeAnnotations := make(map[string]string)
if err = json.Unmarshal([]byte(m.Annotations[annotations.Annotations]), &kubeAnnotations); err != nil {
return err
}
privileged := isTrue(m.Annotations[annotations.PrivilegedRuntime])
trusted := isTrue(m.Annotations[annotations.TrustedSandbox])
sb, err := sandbox.New(id, name, m.Annotations[annotations.KubeName], filepath.Dir(m.Annotations[annotations.LogPath]), "", labels, kubeAnnotations, processLabel, mountLabel, &metadata, m.Annotations[annotations.ShmPath], "", privileged, trusted, m.Annotations[annotations.ResolvPath], "", nil)
if err != nil {
return err
}
sb.AddHostnamePath(m.Annotations[annotations.HostnamePath])
sb.AddIP(ip)
// We add a netNS only if we can load a permanent one.
// Otherwise, the sandbox will live in the host namespace.
netNsPath, err := configNetNsPath(m)
if err == nil {
nsErr := sb.NetNsJoin(netNsPath, sb.Name())
// If we can't load the networking namespace
// because it's closed, we just set the sb netns
// pointer to nil. Otherwise we return an error.
if nsErr != nil && nsErr != sandbox.ErrClosedNetNS {
return nsErr
}
}
c.AddSandbox(sb)
defer func() {
if err != nil {
c.RemoveSandbox(sb.ID())
}
}()
sandboxPath, err := c.store.ContainerRunDirectory(id)
if err != nil {
return err
}
sandboxDir, err := c.store.ContainerDirectory(id)
if err != nil {
return err
}
cname, err := c.ReserveContainerName(m.Annotations[annotations.ContainerID], m.Annotations[annotations.ContainerName])
if err != nil {
return err
}
defer func() {
if err != nil {
c.ReleaseContainerName(cname)
}
}()
created, err := time.Parse(time.RFC3339Nano, m.Annotations[annotations.Created])
if err != nil {
return err
}
scontainer, err := oci.NewContainer(m.Annotations[annotations.ContainerID], cname, sandboxPath, m.Annotations[annotations.LogPath], sb.NetNs(), labels, m.Annotations, kubeAnnotations, "", "", "", nil, id, false, false, false, privileged, trusted, sandboxDir, created, m.Annotations["org.opencontainers.image.stopSignal"])
if err != nil {
return err
}
scontainer.SetSpec(&m)
scontainer.SetMountPoint(m.Annotations[annotations.MountPoint])
if m.Annotations[annotations.Volumes] != "" {
containerVolumes := []oci.ContainerVolume{}
if err = json.Unmarshal([]byte(m.Annotations[annotations.Volumes]), &containerVolumes); err != nil {
return fmt.Errorf("failed to unmarshal container volumes: %v", err)
}
if containerVolumes != nil {
for _, cv := range containerVolumes {
scontainer.AddVolume(cv)
}
}
}
c.ContainerStateFromDisk(scontainer)
if err = label.ReserveLabel(processLabel); err != nil {
return err
}
sb.SetInfraContainer(scontainer)
if err = c.ctrIDIndex.Add(scontainer.ID()); err != nil {
return err
}
if err = c.podIDIndex.Add(id); err != nil {
return err
}
return nil
}
func configNetNsPath(spec rspec.Spec) (string, error) {
for _, ns := range spec.Linux.Namespaces {
if ns.Type != rspec.NetworkNamespace {
continue
}
if ns.Path == "" {
return "", fmt.Errorf("empty networking namespace")
}
return ns.Path, nil
}
return "", fmt.Errorf("missing networking namespace")
}
// LoadContainer loads a container from the disk into the container store
func (c *ContainerServer) LoadContainer(id string) error {
config, err := c.store.FromContainerDirectory(id, "config.json")
if err != nil {
return err
}
var m rspec.Spec
if err = json.Unmarshal(config, &m); err != nil {
return err
}
labels := make(map[string]string)
if err = json.Unmarshal([]byte(m.Annotations[annotations.Labels]), &labels); err != nil {
return err
}
name := m.Annotations[annotations.Name]
name, err = c.ReserveContainerName(id, name)
if err != nil {
return err
}
defer func() {
if err != nil {
c.ReleaseContainerName(name)
}
}()
var metadata pb.ContainerMetadata
if err = json.Unmarshal([]byte(m.Annotations[annotations.Metadata]), &metadata); err != nil {
return err
}
sb := c.GetSandbox(m.Annotations[annotations.SandboxID])
if sb == nil {
return fmt.Errorf("could not get sandbox with id %s, skipping", m.Annotations[annotations.SandboxID])
}
tty := isTrue(m.Annotations[annotations.TTY])
stdin := isTrue(m.Annotations[annotations.Stdin])
stdinOnce := isTrue(m.Annotations[annotations.StdinOnce])
containerPath, err := c.store.ContainerRunDirectory(id)
if err != nil {
return err
}
containerDir, err := c.store.ContainerDirectory(id)
if err != nil {
return err
}
img, ok := m.Annotations[annotations.Image]
if !ok {
img = ""
}
imgName, ok := m.Annotations[annotations.ImageName]
if !ok {
imgName = ""
}
imgRef, ok := m.Annotations[annotations.ImageRef]
if !ok {
imgRef = ""
}
kubeAnnotations := make(map[string]string)
if err = json.Unmarshal([]byte(m.Annotations[annotations.Annotations]), &kubeAnnotations); err != nil {
return err
}
created, err := time.Parse(time.RFC3339Nano, m.Annotations[annotations.Created])
if err != nil {
return err
}
ctr, err := oci.NewContainer(id, name, containerPath, m.Annotations[annotations.LogPath], sb.NetNs(), labels, m.Annotations, kubeAnnotations, img, imgName, imgRef, &metadata, sb.ID(), tty, stdin, stdinOnce, sb.Privileged(), sb.Trusted(), containerDir, created, m.Annotations["org.opencontainers.image.stopSignal"])
if err != nil {
return err
}
ctr.SetSpec(&m)
ctr.SetMountPoint(m.Annotations[annotations.MountPoint])
c.ContainerStateFromDisk(ctr)
c.AddContainer(ctr)
return c.ctrIDIndex.Add(id)
}
func isTrue(annotaton string) bool {
return annotaton == "true"
}
// ContainerStateFromDisk retrieves information on the state of a running container
// from the disk
func (c *ContainerServer) ContainerStateFromDisk(ctr *oci.Container) error {
if err := ctr.FromDisk(); err != nil {
return err
}
// ignore errors, this is a best effort to have up-to-date info about
// a given container before its state gets stored
c.runtime.UpdateStatus(ctr)
return nil
}
// ContainerStateToDisk writes the container's state information to a JSON file
// on disk
func (c *ContainerServer) ContainerStateToDisk(ctr *oci.Container) error {
// ignore errors, this is a best effort to have up-to-date info about
// a given container before its state gets stored
c.Runtime().UpdateStatus(ctr)
jsonSource, err := ioutils.NewAtomicFileWriter(ctr.StatePath(), 0644)
if err != nil {
return err
}
defer jsonSource.Close()
enc := json.NewEncoder(jsonSource)
return enc.Encode(c.runtime.ContainerStatus(ctr))
}
// ReserveContainerName holds a name for a container that is being created
func (c *ContainerServer) ReserveContainerName(id, name string) (string, error) {
if err := c.ctrNameIndex.Reserve(name, id); err != nil {
if err == registrar.ErrNameReserved {
id, err := c.ctrNameIndex.Get(name)
if err != nil {
logrus.Warnf("conflict, ctr name %q already reserved", name)
return "", err
}
return "", fmt.Errorf("conflict, name %q already reserved for ctr %q", name, id)
}
return "", fmt.Errorf("error reserving ctr name %s", name)
}
return name, nil
}
// ReleaseContainerName releases a container name from the index so that it can
// be used by other containers
func (c *ContainerServer) ReleaseContainerName(name string) {
c.ctrNameIndex.Release(name)
}
// ReservePodName holds a name for a pod that is being created
func (c *ContainerServer) ReservePodName(id, name string) (string, error) {
if err := c.podNameIndex.Reserve(name, id); err != nil {
if err == registrar.ErrNameReserved {
id, err := c.podNameIndex.Get(name)
if err != nil {
logrus.Warnf("conflict, pod name %q already reserved", name)
return "", err
}
return "", fmt.Errorf("conflict, name %q already reserved for pod %q", name, id)
}
return "", fmt.Errorf("error reserving pod name %q", name)
}
return name, nil
}
// ReleasePodName releases a pod name from the index so it can be used by other
// pods
func (c *ContainerServer) ReleasePodName(name string) {
c.podNameIndex.Release(name)
}
// Shutdown attempts to shut down the server's storage cleanly
func (c *ContainerServer) Shutdown() error {
_, err := c.store.Shutdown(false)
if err != nil && errors.Cause(err) != cstorage.ErrLayerUsedByContainer {
return err
}
return nil
}
type containerServerState struct {
containers oci.ContainerStorer
infraContainers oci.ContainerStorer
sandboxes map[string]*sandbox.Sandbox
// processLevels The number of sandboxes using the same SELinux MCS level. Need to release MCS Level, when count reaches 0
processLevels map[string]int
}
// AddContainer adds a container to the container state store
func (c *ContainerServer) AddContainer(ctr *oci.Container) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
sandbox := c.state.sandboxes[ctr.Sandbox()]
sandbox.AddContainer(ctr)
c.state.containers.Add(ctr.ID(), ctr)
}
// AddInfraContainer adds a container to the container state store
func (c *ContainerServer) AddInfraContainer(ctr *oci.Container) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
c.state.infraContainers.Add(ctr.ID(), ctr)
}
// GetContainer returns a container by its ID
func (c *ContainerServer) GetContainer(id string) *oci.Container {
c.stateLock.Lock()
defer c.stateLock.Unlock()
return c.state.containers.Get(id)
}
// GetInfraContainer returns a container by its ID
func (c *ContainerServer) GetInfraContainer(id string) *oci.Container {
c.stateLock.Lock()
defer c.stateLock.Unlock()
return c.state.infraContainers.Get(id)
}
// HasContainer checks if a container exists in the state
func (c *ContainerServer) HasContainer(id string) bool {
c.stateLock.Lock()
defer c.stateLock.Unlock()
ctr := c.state.containers.Get(id)
return ctr != nil
}
// RemoveContainer removes a container from the container state store
func (c *ContainerServer) RemoveContainer(ctr *oci.Container) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
sbID := ctr.Sandbox()
sb := c.state.sandboxes[sbID]
sb.RemoveContainer(ctr)
c.state.containers.Delete(ctr.ID())
}
// RemoveInfraContainer removes a container from the container state store
func (c *ContainerServer) RemoveInfraContainer(ctr *oci.Container) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
c.state.infraContainers.Delete(ctr.ID())
}
// listContainers returns a list of all containers stored by the server state
func (c *ContainerServer) listContainers() []*oci.Container {
c.stateLock.Lock()
defer c.stateLock.Unlock()
return c.state.containers.List()
}
// ListContainers returns a list of all containers stored by the server state
// that match the given filter function
func (c *ContainerServer) ListContainers(filters ...func(*oci.Container) bool) ([]*oci.Container, error) {
containers := c.listContainers()
if len(filters) == 0 {
return containers, nil
}
filteredContainers := make([]*oci.Container, 0, len(containers))
for _, container := range containers {
for _, filter := range filters {
if filter(container) {
filteredContainers = append(filteredContainers, container)
}
}
}
return filteredContainers, nil
}
// AddSandbox adds a sandbox to the sandbox state store
func (c *ContainerServer) AddSandbox(sb *sandbox.Sandbox) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
c.state.sandboxes[sb.ID()] = sb
c.state.processLevels[selinux.NewContext(sb.ProcessLabel())["level"]]++
}
// GetSandbox returns a sandbox by its ID
func (c *ContainerServer) GetSandbox(id string) *sandbox.Sandbox {
c.stateLock.Lock()
defer c.stateLock.Unlock()
return c.state.sandboxes[id]
}
// GetSandboxContainer returns a sandbox's infra container
func (c *ContainerServer) GetSandboxContainer(id string) *oci.Container {
c.stateLock.Lock()
defer c.stateLock.Unlock()
sb, ok := c.state.sandboxes[id]
if !ok {
return nil
}
return sb.InfraContainer()
}
// HasSandbox checks if a sandbox exists in the state
func (c *ContainerServer) HasSandbox(id string) bool {
c.stateLock.Lock()
defer c.stateLock.Unlock()
_, ok := c.state.sandboxes[id]
return ok
}
// RemoveSandbox removes a sandbox from the state store
func (c *ContainerServer) RemoveSandbox(id string) {
c.stateLock.Lock()
defer c.stateLock.Unlock()
processLabel := c.state.sandboxes[id].ProcessLabel()
delete(c.state.sandboxes, id)
level := selinux.NewContext(processLabel)["level"]
c.state.processLevels[level]--
if c.state.processLevels[level] == 0 {
label.ReleaseLabel(processLabel)
delete(c.state.processLevels, level)
}
}
// ListSandboxes lists all sandboxes in the state store
func (c *ContainerServer) ListSandboxes() []*sandbox.Sandbox {
c.stateLock.Lock()
defer c.stateLock.Unlock()
sbArray := make([]*sandbox.Sandbox, 0, len(c.state.sandboxes))
for _, sb := range c.state.sandboxes {
sbArray = append(sbArray, sb)
}
return sbArray
}
// LibcontainerStats gets the stats for the container with the given id from runc/libcontainer
func (c *ContainerServer) LibcontainerStats(ctr *oci.Container) (*libcontainer.Stats, error) {
// TODO: make this not hardcoded
// was: c.runtime.Path(ociContainer) but that returns /usr/bin/runc - how do we get /run/runc?
// runroot is /var/run/runc
// Hardcoding probably breaks ClearContainers compatibility
factory, err := loadFactory("/run/runc")
if err != nil {
return nil, err
}
container, err := factory.Load(ctr.ID())
if err != nil {
return nil, err
}
return container.Stats()
}

98
libkpod/hooks.go Normal file
View File

@ -0,0 +1,98 @@
package libkpod
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"syscall"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const (
// DefaultHooksDirPath Default directory containing hooks config files
DefaultHooksDirPath = "/usr/share/containers/oci/hooks.d"
// OverrideHooksDirPath Directory where admin can override the default configuration
OverrideHooksDirPath = "/etc/containers/oci/hooks.d"
)
// HookParams is the structure returned from read the hooks configuration
type HookParams struct {
Hook string `json:"hook"`
Stage []string `json:"stage"`
Cmds []string `json:"cmd"`
Annotations []string `json:"annotation"`
HasBindMounts bool `json:"hasbindmounts"`
}
// readHook reads hooks json files, verifies it and returns the json config
func readHook(hookPath string) (HookParams, error) {
var hook HookParams
raw, err := ioutil.ReadFile(hookPath)
if err != nil {
return hook, errors.Wrapf(err, "error Reading hook %q", hookPath)
}
if err := json.Unmarshal(raw, &hook); err != nil {
return hook, errors.Wrapf(err, "error Unmarshalling JSON for %q", hookPath)
}
if _, err := os.Stat(hook.Hook); err != nil {
return hook, errors.Wrapf(err, "unable to stat hook %q in hook config %q", hook.Hook, hookPath)
}
validStage := map[string]bool{"prestart": true, "poststart": true, "poststop": true}
for _, cmd := range hook.Cmds {
if _, err = regexp.Compile(cmd); err != nil {
return hook, errors.Wrapf(err, "invalid cmd regular expression %q defined in hook config %q", cmd, hookPath)
}
}
for _, cmd := range hook.Annotations {
if _, err = regexp.Compile(cmd); err != nil {
return hook, errors.Wrapf(err, "invalid cmd regular expression %q defined in hook config %q", cmd, hookPath)
}
}
for _, stage := range hook.Stage {
if !validStage[stage] {
return hook, errors.Wrapf(err, "unknown stage %q defined in hook config %q", stage, hookPath)
}
}
return hook, nil
}
// readHooks reads hooks json files in directory to setup OCI Hooks
// adding hooks to the passedin hooks map.
func readHooks(hooksPath string, hooks map[string]HookParams) error {
if _, err := os.Stat(hooksPath); err != nil {
if os.IsNotExist(err) {
logrus.Warnf("hooks path: %q does not exist", hooksPath)
return nil
}
return errors.Wrapf(err, "unable to stat hooks path %q", hooksPath)
}
files, err := ioutil.ReadDir(hooksPath)
if err != nil {
return err
}
for _, file := range files {
if !strings.HasSuffix(file.Name(), ".json") {
continue
}
hook, err := readHook(filepath.Join(hooksPath, file.Name()))
if err != nil {
return err
}
for key, h := range hooks {
// hook.Hook can only be defined in one hook file, unless it has the
// same name in the override path.
if hook.Hook == h.Hook && key != file.Name() {
return errors.Wrapf(syscall.EINVAL, "duplicate path, hook %q from %q already defined in %q", hook.Hook, hooksPath, key)
}
}
hooks[file.Name()] = hook
}
return nil
}

45
libkpod/kill.go Normal file
View File

@ -0,0 +1,45 @@
package libkpod
import (
"github.com/docker/docker/pkg/signal"
"github.com/projectatomic/libpod/oci"
"github.com/projectatomic/libpod/utils"
"github.com/pkg/errors"
"os"
"syscall"
)
// Reverse lookup signal string from its map
func findStringInSignalMap(killSignal syscall.Signal) (string, error) {
for k, v := range signal.SignalMap {
if v == killSignal {
return k, nil
}
}
return "", errors.Errorf("unable to convert signal to string")
}
// ContainerKill sends the user provided signal to the containers primary process.
func (c *ContainerServer) ContainerKill(container string, killSignal syscall.Signal) (string, error) { // nolint
ctr, err := c.LookupContainer(container)
if err != nil {
return "", errors.Wrapf(err, "failed to find container %s", container)
}
c.runtime.UpdateStatus(ctr)
cStatus := c.runtime.ContainerStatus(ctr)
// If the container is not running, error and move on.
if cStatus.Status != oci.ContainerStateRunning {
return "", errors.Errorf("cannot kill container %s: it is not running", container)
}
signalString, err := findStringInSignalMap(killSignal)
if err != nil {
return "", err
}
if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, c.runtime.Path(ctr), "kill", ctr.ID(), signalString); err != nil {
return "", err
}
c.ContainerStateToDisk(ctr)
return ctr.ID(), nil
}

80
libkpod/logs.go Normal file
View File

@ -0,0 +1,80 @@
package libkpod
import (
"path"
"strings"
"time"
"github.com/hpcloud/tail"
)
// LogOptions contains all of the options for displaying logs in kpod
type LogOptions struct {
Details bool
Follow bool
SinceTime time.Time
Tail uint64
}
// GetLogs gets each line of a log file and, if it matches the criteria in logOptions, sends it down logChan
func (c *ContainerServer) GetLogs(container string, logChan chan string, opts LogOptions) error {
defer close(logChan)
// Get the full ID of the container
ctr, err := c.LookupContainer(container)
if err != nil {
return err
}
containerID := ctr.ID()
sandbox := ctr.Sandbox()
if sandbox == "" {
sandbox = containerID
}
// Read the log line by line and pass it into the pipe
logsFile := path.Join(c.config.LogDir, sandbox, containerID+".log")
seekInfo := &tail.SeekInfo{Offset: 0, Whence: 0}
if opts.Tail > 0 {
// seek to correct position in logs files
seekInfo.Offset = int64(opts.Tail)
seekInfo.Whence = 2
}
t, err := tail.TailFile(logsFile, tail.Config{Follow: false, ReOpen: false, Location: seekInfo})
for line := range t.Lines {
if since, err := logSinceTime(opts.SinceTime, line.Text); err != nil || !since {
continue
}
logMessage := line.Text[secondSpaceIndex(line.Text):]
if opts.Details {
// add additional information to line
}
logChan <- logMessage
}
return err
}
func logSinceTime(sinceTime time.Time, logStr string) (bool, error) {
timestamp := strings.Split(logStr, " ")[0]
logTime, err := time.Parse("2006-01-02T15:04:05.999999999-07:00", timestamp)
if err != nil {
return false, err
}
return logTime.After(sinceTime) || logTime.Equal(sinceTime), nil
}
// secondSpaceIndex returns the index of the second space in a string
// In a line of the logs, the first two tokens are a timestamp and stdout/stderr,
// followed by the message itself. This allows us to get the index of the message
// and avoid sending the other information back to the caller of GetLogs()
func secondSpaceIndex(line string) int {
index := strings.Index(line, " ")
if index == -1 {
return 0
}
index = strings.Index(line[index:], " ")
if index == -1 {
return 0
}
return index
}

Some files were not shown because too many files have changed in this diff Show More