Move python code from contrib to it's own repo python-podman

Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
Jhon Honce
2019-01-09 15:05:58 -07:00
parent 2169b9fe14
commit 45fb935fe4
103 changed files with 5 additions and 7767 deletions

View File

@ -25,14 +25,12 @@ pwd
# -t integration test
# -u unit test
# -v validate
# -p run python tests
build=0
install=0
integrationtest=0
unittest=0
validate=0
runpython=0
options=0
install_tools_made=0
@ -44,9 +42,6 @@ while getopts "biptuv" opt; do
i) install=1
options=1
;;
p) runpython=1
options=1
;;
t) integrationtest=1
options=1
;;
@ -124,18 +119,11 @@ if [ $install -eq 1 ]; then
make TAGS="${TAGS}" install.man PREFIX=/usr ETCDIR=/etc
make TAGS="${TAGS}" install.cni PREFIX=/usr ETCDIR=/etc
make TAGS="${TAGS}" install.systemd PREFIX=/usr ETCDIR=/etc
if [ $runpython -eq 1 ]; then
make TAGS="${TAGS}" install.python PREFIX=/usr ETCDIR=/etc
fi
fi
# Run integration tests
if [ $integrationtest -eq 1 ]; then
make TAGS="${TAGS}" test-binaries
make varlink_generate GOPATH=/go
if [ $runpython -eq 1 ]; then
make clientintegration GOPATH=/go
fi
make ginkgo GOPATH=/go $INTEGRATION_TEST_ENVS
fi

View File

@ -4,10 +4,8 @@ set -xeuo pipefail
DIST=${DIST:=Fedora}
CONTAINER_RUNTIME=${CONTAINER_RUNTIME:=docker}
IMAGE=fedorapodmanbuild
PYTHON=python3
if [[ ${DIST} != "Fedora" ]]; then
IMAGE=centospodmanbuild
PYTHON=python
fi
# Since CRIU 3.11 has been pushed to Fedora 28 the checkpoint/restore
@ -21,4 +19,4 @@ modprobe iptable_nat || :
${CONTAINER_RUNTIME} build -t ${IMAGE} -f Dockerfile.${DIST} . 2>build.log
# Run the tests
${CONTAINER_RUNTIME} run --rm --privileged --net=host -v $PWD:/go/src/github.com/containers/libpod:Z --workdir /go/src/github.com/containers/libpod -e CGROUP_MANAGER=cgroupfs -e PYTHON=$PYTHON -e STORAGE_OPTIONS="--storage-driver=vfs" -e CRIO_ROOT="/go/src/github.com/containers/libpod" -e PODMAN_BINARY="/usr/bin/podman" -e CONMON_BINARY="/usr/libexec/podman/conmon" -e DIST=$DIST -e CONTAINER_RUNTIME=$CONTAINER_RUNTIME $IMAGE sh ./.papr.sh -b -i -t
${CONTAINER_RUNTIME} run --rm --privileged --net=host -v $PWD:/go/src/github.com/containers/libpod:Z --workdir /go/src/github.com/containers/libpod -e CGROUP_MANAGER=cgroupfs -e STORAGE_OPTIONS="--storage-driver=vfs" -e CRIO_ROOT="/go/src/github.com/containers/libpod" -e PODMAN_BINARY="/usr/bin/podman" -e CONMON_BINARY="/usr/libexec/podman/conmon" -e DIST=$DIST -e CONTAINER_RUNTIME=$CONTAINER_RUNTIME $IMAGE sh ./.papr.sh -b -i -t

View File

@ -27,7 +27,6 @@ RUN apt-get update && apt-get install -y \
libudev-dev \
protobuf-c-compiler \
protobuf-compiler \
python-minimal \
libglib2.0-dev \
libapparmor-dev \
btrfs-tools \
@ -37,11 +36,6 @@ RUN apt-get update && apt-get install -y \
liblzma-dev \
netcat \
socat \
python3-pip \
python3-dateutil \
python3-setuptools \
python3-psutil \
python3-pytoml \
lsof \
xz-utils \
--no-install-recommends \
@ -129,9 +123,6 @@ COPY cni/87-podman-bridge.conflist /etc/cni/net.d/87-podman-bridge.conflist
# Make sure we have some policy for pulling images
RUN mkdir -p /etc/containers && curl https://raw.githubusercontent.com/projectatomic/registries/master/registries.fedora -o /etc/containers/registries.conf
# Install python3 varlink module from pypi
RUN pip3 install varlink
COPY test/policy.json /etc/containers/policy.json
COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml

View File

@ -19,10 +19,6 @@ RUN yum -y install btrfs-progs-devel \
runc \
make \
ostree-devel \
python \
python3-dateutil \
python3-psutil \
python3-pytoml \
lsof \
which\
golang-github-cpuguy83-go-md2man \

View File

@ -20,10 +20,6 @@ RUN dnf -y install btrfs-progs-devel \
runc \
make \
ostree-devel \
python \
python3-dateutil \
python3-psutil \
python3-pytoml \
lsof \
which\
golang-github-cpuguy83-go-md2man \

View File

@ -26,8 +26,6 @@ ifneq (,$(findstring varlink,$(BUILDTAGS)))
endif
CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker)
HAS_PYTHON3 := $(shell command -v python3 2>/dev/null)
BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions
OCIUMOUNTINSTALLDIR=$(PREFIX)/share/oci-umount/oci-umount.d
@ -88,9 +86,6 @@ endif
lint: .gopathok varlink_generate
@echo "checking lint"
@./.tool/lint
# Not ready
# @$(MAKE) -C contrib/python/podman lint
# @$(MAKE) -C contrib/python/pypodman lint
gofmt:
find . -name '*.go' ! -path './vendor/*' -exec gofmt -s -w {} \+
@ -122,12 +117,6 @@ bin/podman.cross.%: .gopathok
GOARCH="$${TARGET##*.}" \
$(GO) build -ldflags '$(LDFLAGS_PODMAN)' -tags '$(BUILDTAGS_CROSS)' -o "$@" $(PROJECT)/cmd/podman
python:
ifdef HAS_PYTHON3
$(MAKE) -C contrib/python/podman python-podman
$(MAKE) -C contrib/python/pypodman python-pypodman
endif
clean:
rm -rf \
.gopathok \
@ -147,8 +136,6 @@ clean:
$(MANPAGES) ||:
find . -name \*~ -delete
find . -name \#\* -delete
$(MAKE) -C contrib/python/podman clean
$(MAKE) -C contrib/python/pypodman clean
libpodimage:
${CONTAINER_RUNTIME} build -t ${LIBPOD_IMAGE} .
@ -181,7 +168,7 @@ localunit: test/goecho/goecho varlink_generate
ginkgo:
ginkgo -v -tags "$(BUILDTAGS)" -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/.
localintegration: varlink_generate test-binaries clientintegration ginkgo
localintegration: varlink_generate test-binaries ginkgo
localsystem: .install.ginkgo .install.gomega
ginkgo -v -noColor test/system/
@ -189,10 +176,6 @@ localsystem: .install.ginkgo .install.gomega
system.test-binary: .install.ginkgo .install.gomega
$(GO) test -c ./test/system
clientintegration:
$(MAKE) -C contrib/python/podman integration
$(MAKE) -C contrib/python/pypodman integration
perftest:
$ cd contrib/perftest;go build
@ -230,7 +213,7 @@ changelog:
$(shell cat $(TMPFILE) >> changelog.txt)
$(shell rm $(TMPFILE))
install: .gopathok install.bin install.man install.cni install.systemd install.python
install: .gopathok install.bin install.man install.cni install.systemd
install.bin:
install ${SELINUXOPT} -d -m 755 $(BINDIR)
@ -268,10 +251,6 @@ install.systemd:
install ${SELINUXOPT} -m 644 contrib/varlink/io.podman.service ${SYSTEMDDIR}/io.podman.service
install ${SELINUXOPT} -m 644 contrib/varlink/podman.conf ${TMPFILESDIR}/podman.conf
install.python:
$(MAKE) DESTDIR=${DESTDIR} -C contrib/python/podman install
$(MAKE) DESTDIR=${DESTDIR} -C contrib/python/pypodman install
uninstall:
for i in $(filter %.1,$(MANPAGES)); do \
rm -f $(MANDIR)/man1/$$(basename $${i}); \
@ -279,8 +258,6 @@ uninstall:
for i in $(filter %.5,$(MANPAGES)); do \
rm -f $(MANDIR)/man5/$$(basename $${i}); \
done
$(MAKE) -C contrib/python/pypodman uninstall
$(MAKE) -C contrib/python/podman uninstall
.PHONY: .gitvalidation
.gitvalidation: .gopathok
@ -382,5 +359,3 @@ build-all-new-commits:
changelog \
validate \
install.libseccomp.sudo \
python \
clientintegration

View File

@ -1,5 +0,0 @@
build
dist
*.egg-info
*.pyc
__pycache__

View File

@ -1,564 +0,0 @@
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=0
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# Specify a configuration file.
#rcfile=
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=print-statement,
parameter-unpacking,
unpacking-in-except,
old-raise-syntax,
backtick,
long-suffix,
old-ne-operator,
old-octal-literal,
import-star-module-level,
non-ascii-bytes-literal,
raw-checker-failed,
bad-inline-option,
locally-disabled,
locally-enabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
apply-builtin,
basestring-builtin,
buffer-builtin,
cmp-builtin,
coerce-builtin,
execfile-builtin,
file-builtin,
long-builtin,
raw_input-builtin,
reduce-builtin,
standarderror-builtin,
unicode-builtin,
xrange-builtin,
coerce-method,
delslice-method,
getslice-method,
setslice-method,
no-absolute-import,
old-division,
dict-iter-method,
dict-view-method,
next-method-called,
metaclass-assignment,
indexing-exception,
raising-string,
reload-builtin,
oct-method,
hex-method,
nonzero-method,
cmp-method,
input-builtin,
round-builtin,
intern-builtin,
unichr-builtin,
map-builtin-not-iterating,
zip-builtin-not-iterating,
range-builtin-not-iterating,
filter-builtin-not-iterating,
using-cmp-argument,
eq-without-hash,
div-method,
idiv-method,
rdiv-method,
exception-message-attribute,
invalid-str-codec,
sys-max-int,
bad-python3-import,
deprecated-string-function,
deprecated-str-translate-call,
deprecated-itertools-function,
deprecated-types-field,
next-method-defined,
dict-items-not-iterating,
dict-keys-not-iterating,
dict-values-not-iterating,
deprecated-operator-function,
deprecated-urllib-function,
xreadlines-attribute,
deprecated-sys-function,
exception-escape,
comprehension-escape
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[REPORTS]
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
#msg-template=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package..
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,
dict-separator
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[BASIC]
# Naming style matching correct argument names.
#argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style.
argument-rgx=[a-z_][a-z0-9_]{1,30}$
argument-name-hint=[a-z_][a-z0-9_]{1,30}$
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=c,
e,
i,
j,
k,
r,
v,
ex,
Run,
_
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Naming style matching correct variable names.
#variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style.
variable-rgx=[a-z_][a-z0-9_]{2,30}$
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
[SIMILARITIES]
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
# Minimum lines number of a similarity.
min-similarity-lines=4
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[IMPORTS]
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled).
ext-import-graph=
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled).
import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
[DESIGN]
# Support argparse.Action constructor API
# Maximum number of arguments for function / method.
max-args=12
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement.
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=10
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception".
overgeneral-exceptions=Exception

View File

@ -1 +0,0 @@
v0.1.0, 2018-05-11 -- Initial release.

View File

@ -1,13 +0,0 @@
Copyright 2018 Red Hat, Inc
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.

View File

@ -1,3 +0,0 @@
prune test/
include README.md
include requirements.txt

View File

@ -1,40 +0,0 @@
PYTHON ?= $(shell command -v python3 2>/dev/null || command -v python)
DESTDIR ?= /
PODMAN_VERSION ?= '0.11.1.1'
.PHONY: python-podman
python-podman:
PODMAN_VERSION=$(PODMAN_VERSION) \
$(PYTHON) setup.py sdist bdist
.PHONY: lint
lint:
$(PYTHON) -m pylint podman
.PHONY: integration
integration:
test/test_runner.sh -v
.PHONY: install
install:
PODMAN_VERSION=$(PODMAN_VERSION) \
$(PYTHON) setup.py install --root ${DESTDIR}
.PHONY: upload
upload:
PODMAN_VERSION=$(PODMAN_VERSION) $(PYTHON) setup.py sdist bdist_wheel
twine upload --verbose --repository-url https://test.pypi.org/legacy/ dist/*
.PHONY: clobber
clobber: uninstall clean
.PHONY: uninstall
uninstall:
$(PYTHON) -m pip uninstall --yes podman ||:
.PHONY: clean
clean:
rm -rf podman.egg-info dist
find . -depth -name __pycache__ -exec rm -rf {} \;
find . -depth -name \*.pyc -exec rm -f {} \;
$(PYTHON) ./setup.py clean --all

View File

@ -1,44 +0,0 @@
# podman - pythonic library for working with varlink interface to Podman
## Status: Active Development
See [libpod](https://github.com/containers/libpod)
## Releases
To build the podman egg and install as user:
```sh
cd ~/libpod/contrib/python/podman
python3 setup.py clean -a && python3 setup.py sdist bdist
python3 setup.py install --user
```
## Code snippets/examples:
### Show images in storage
```python
import podman
with podman.Client() as client:
list(map(print, client.images.list()))
```
### Show containers created since midnight
```python
from datetime import datetime, time, timezone
import podman
midnight = datetime.combine(datetime.today(), time.min, tzinfo=timezone.utc)
with podman.Client() as client:
for c in client.containers.list():
created_at = podman.datetime_parse(c.createdat)
if created_at > midnight:
print('Container {}: image: {} created at: {}'.format(
c.id[:12], c.image[:32], podman.datetime_format(created_at)))
```

View File

@ -1,18 +0,0 @@
#!/usr/bin/env python3
"""Example: Run top on Alpine container."""
import podman
print('{}\n'.format(__doc__))
with podman.Client() as client:
id = client.images.pull('alpine:latest')
img = client.images.get(id)
cntr = img.create(detach=True, tty=True, command=['/usr/bin/top'])
cntr.attach(eot=4)
try:
cntr.start()
print()
except (BrokenPipeError, KeyboardInterrupt):
print('\nContainer disconnected.')

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python3
"""Example: Show containers grouped by image id."""
from itertools import groupby
import podman
print('{}\n'.format(__doc__))
with podman.Client() as client:
ctnrs = sorted(client.containers.list(), key=lambda k: k.imageid)
for key, grp in groupby(ctnrs, key=lambda k: k.imageid):
print('Image: {}'.format(key))
for c in grp:
print(' : container: {} created at: {}'.format(
c.id[:12], podman.datetime_format(c.createdat)))

View File

@ -1,10 +0,0 @@
#!/usr/bin/env python3
"""Example: Show all images on system."""
import podman
print('{}\n'.format(__doc__))
with podman.Client() as client:
for img in client.images.list():
print(img)

View File

@ -1,16 +0,0 @@
#!/usr/bin/env python3
"""Example: Pull Fedora and inspect image and container."""
import podman
print('{}\n'.format(__doc__))
with podman.Client() as client:
id = client.images.pull('registry.fedoraproject.org/fedora:28')
img = client.images.get(id)
print(img.inspect())
cntr = img.create()
print(cntr.inspect())
cntr.remove()

View File

@ -1,19 +0,0 @@
#!/usr/bin/env python3
"""Example: Show all containers created since midnight."""
from datetime import datetime, time, timezone
import podman
print('{}\n'.format(__doc__))
midnight = datetime.combine(datetime.today(), time.min, tzinfo=timezone.utc)
with podman.Client() as client:
for c in client.containers.list():
created_at = podman.datetime_parse(c.createdat)
if created_at > midnight:
print('{}: image: {} createdAt: {}'.format(
c.id[:12], c.image[:32], podman.datetime_format(created_at)))

View File

@ -1,32 +0,0 @@
#!/usr/bin/env python3
"""Example: Create new image from container."""
import sys
import podman
def print_history(details):
"""Format history data from an image, in a table."""
for i, r in enumerate(details):
print(
'{}: {} {} {}'.format(i, r.id[:12],
podman.datetime_format(r.created), r.tags),
sep='\n')
print("-" * 25)
print('{}\n'.format(__doc__))
with podman.Client() as client:
ctnr = next(
(c for c in client.containers.list() if 'alpine' in c['image']), None)
if ctnr:
print_history(client.images.get(ctnr.imageid).history())
# Make changes as we save the container to a new image
id = ctnr.commit('alpine-ash', changes=['CMD=/bin/ash'])
print_history(client.images.get(id).history())
else:
print('Unable to find "alpine" container.', file=sys.stderr)

View File

@ -1,43 +0,0 @@
#!/bin/bash
export PYTHONPATH=..
function examples {
for file in $@; do
python3 -c "import ast; f=open('"${file}"'); t=ast.parse(f.read()); print(ast.get_docstring(t) + ' -- "${file}"')"
done
}
while getopts "lh" arg; do
case $arg in
l ) examples $(ls eg_*.py); exit 0 ;;
h ) echo 1>&2 $0 [-l] [-h] filename ; exit 2 ;;
esac
done
shift $((OPTIND-1))
# podman needs to play some games with resources
if [[ $(id -u) != 0 ]]; then
echo 1>&2 $0 must be run as root.
exit 2
fi
if ! systemctl --quiet is-active io.podman.socket; then
echo 1>&2 'podman is not running. systemctl enable --now io.podman.socket'
exit 1
fi
function cleanup {
podman rm $1 >/dev/null 2>&1
}
# Setup storage with an image and container
podman pull alpine:latest >/tmp/podman.output 2>&1
CTNR=$(podman create alpine)
trap "cleanup $CTNR" EXIT
if [[ -f $1 ]]; then
python3 $1
else
python3 $1.py
fi

View File

@ -1,29 +0,0 @@
"""A client for communicating with a Podman server."""
import pkg_resources
from .client import Client
from .libs import FoldedString, datetime_format, datetime_parse
from .libs.errors import (ContainerNotFound, ErrorOccurred, ImageNotFound,
NoContainerRunning, NoContainersInPod,
PodContainerError, PodmanError, PodNotFound)
assert FoldedString
try:
__version__ = pkg_resources.get_distribution('podman').version
except Exception: # pylint: disable=broad-except
__version__ = '0.0.0'
__all__ = [
'Client',
'ContainerNotFound',
'datetime_format',
'datetime_parse',
'ErrorOccurred',
'ImageNotFound',
'NoContainerRunning',
'NoContainersInPod',
'PodContainerError',
'PodmanError',
'PodNotFound',
]

View File

@ -1,212 +0,0 @@
"""A client for communicating with a Podman varlink service."""
import errno
import logging
import os
from urllib.parse import urlparse
from varlink import Client as VarlinkClient
from varlink import VarlinkError
from .libs import cached_property
from .libs.containers import Containers
from .libs.errors import error_factory
from .libs.images import Images
from .libs.system import System
from .libs.tunnel import Context, Portal, Tunnel
from .libs.pods import Pods
class BaseClient():
"""Context manager for API workers to access varlink."""
def __init__(self, context):
"""Construct Client."""
self._client = None
self._iface = None
self._context = context
def __call__(self):
"""Support being called for old API."""
return self
@classmethod
def factory(cls,
uri=None,
interface='io.podman',
*args,
**kwargs):
"""Construct a Client based on input."""
log_level = os.environ.get('LOG_LEVEL')
if log_level is not None:
logging.basicConfig(level=logging.getLevelName(log_level.upper()))
if uri is None:
raise ValueError('uri is required and cannot be None')
if interface is None:
raise ValueError('interface is required and cannot be None')
unsupported = set(kwargs.keys()).difference(
('uri', 'interface', 'remote_uri', 'identity_file'))
if unsupported:
raise ValueError('Unknown keyword arguments: {}'.format(
', '.join(unsupported)))
local_path = urlparse(uri).path
if local_path == '':
raise ValueError('path is required for uri,'
' expected format "unix://path_to_socket"')
if kwargs.get('remote_uri') is None:
return LocalClient(Context(uri, interface))
required = ('{} is required, expected format'
' "ssh://user@hostname[:port]/path_to_socket".')
# Remote access requires the full tuple of information
if kwargs.get('remote_uri') is None:
raise ValueError(required.format('remote_uri'))
remote = urlparse(kwargs['remote_uri'])
if remote.username is None:
raise ValueError(required.format('username'))
if remote.path == '':
raise ValueError(required.format('path'))
if remote.hostname is None:
raise ValueError(required.format('hostname'))
return RemoteClient(
Context(
uri,
interface,
local_path,
remote.path,
remote.username,
remote.hostname,
remote.port,
kwargs.get('identity_file'),
))
class LocalClient(BaseClient):
"""Context manager for API workers to access varlink."""
def __enter__(self):
"""Enter context for LocalClient."""
self._client = VarlinkClient(address=self._context.uri)
self._iface = self._client.open(self._context.interface)
return self._iface
def __exit__(self, e_type, e, e_traceback):
"""Cleanup context for LocalClient."""
if hasattr(self._client, 'close'):
self._client.close()
self._iface.close()
if isinstance(e, VarlinkError):
raise error_factory(e)
class RemoteClient(BaseClient):
"""Context manager for API workers to access remote varlink."""
def __init__(self, context):
"""Construct RemoteCLient."""
super().__init__(context)
self._portal = Portal()
def __enter__(self):
"""Context manager for API workers to access varlink."""
tunnel = self._portal.get(self._context.uri)
if tunnel is None:
tunnel = Tunnel(self._context).bore()
self._portal[self._context.uri] = tunnel
try:
self._client = VarlinkClient(address=self._context.uri)
self._iface = self._client.open(self._context.interface)
return self._iface
except Exception:
tunnel.close()
raise
def __exit__(self, e_type, e, e_traceback):
"""Cleanup context for RemoteClient."""
if hasattr(self._client, 'close'):
self._client.close()
self._iface.close()
# set timer to shutdown ssh tunnel
# self._portal.get(self._context.uri).close()
if isinstance(e, VarlinkError):
raise error_factory(e)
class Client():
"""A client for communicating with a Podman varlink service.
Example:
>>> import podman
>>> c = podman.Client()
>>> c.system.versions
Example remote podman:
>>> import podman
>>> c = podman.Client(uri='unix:/tmp/podman.sock',
remote_uri='ssh://user@host/run/podman/io.podman',
identity_file='~/.ssh/id_rsa')
"""
def __init__(self,
uri='unix:/run/podman/io.podman',
interface='io.podman',
**kwargs):
"""Construct a podman varlink Client.
uri from default systemd unit file.
interface from io.podman.varlink, do not change unless
you are a varlink guru.
"""
self._client = BaseClient.factory(uri, interface, **kwargs)
address = "{}-{}".format(uri, interface)
# Quick validation of connection data provided
try:
if not System(self._client).ping():
raise ConnectionRefusedError(
errno.ECONNREFUSED,
('Failed varlink connection "{}"').format(address))
except FileNotFoundError:
raise ConnectionError(
errno.ECONNREFUSED,
('Failed varlink connection "{}".'
' Is podman socket or service running?').format(address))
def __enter__(self):
"""Return `self` upon entering the runtime context."""
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Raise any exception triggered within the runtime context."""
pass
@cached_property
def system(self):
"""Manage system model for podman."""
return System(self._client)
@cached_property
def images(self):
"""Manage images model for libpod."""
return Images(self._client)
@cached_property
def containers(self):
"""Manage containers model for libpod."""
return Containers(self._client)
@cached_property
def pods(self):
"""Manage pods model for libpod."""
return Pods(self._client)

View File

@ -1,75 +0,0 @@
"""Support files for podman API implementation."""
import collections
import datetime
import functools
from dateutil.parser import parse as dateutil_parse
__all__ = [
'cached_property',
'datetime_format',
'datetime_parse',
'fold_keys',
]
def cached_property(fn):
"""Decorate property to cache return value."""
return property(functools.lru_cache(maxsize=8)(fn))
class ConfigDict(collections.UserDict):
"""Silently ignore None values, only take key once."""
def __init__(self, **kwargs):
"""Construct dictionary."""
super().__init__(kwargs)
def __setitem__(self, key, value):
"""Store unique, not None values."""
if value is None:
return
if super().__contains__(key):
return
super().__setitem__(key, value)
class FoldedString(collections.UserString):
"""Foldcase sequences value."""
def __init__(self, seq):
super().__init__(seq)
self.data.casefold()
def fold_keys(): # noqa: D202
"""Fold case of dictionary keys."""
@functools.wraps(fold_keys)
def wrapped(mapping):
"""Fold case of dictionary keys."""
return {k.casefold(): v for (k, v) in mapping.items()}
return wrapped
def datetime_parse(string):
"""Convert timestamps to datetime.
tzinfo aware, if provided.
"""
return dateutil_parse(string.upper(), fuzzy=True)
def datetime_format(dt):
"""Format datetime in consistent style."""
if isinstance(dt, str):
return datetime_parse(dt).isoformat()
if isinstance(dt, datetime.datetime):
return dt.isoformat()
raise ValueError('Unable to format {}. Type {} not supported.'.format(
dt, type(dt)))

View File

@ -1,79 +0,0 @@
"""Exported method Container.attach()."""
import collections
import fcntl
import logging
import struct
import sys
import termios
class Mixin:
"""Publish attach() for inclusion in Container class."""
def attach(self, eot=4, stdin=None, stdout=None):
"""Attach to container's PID1 stdin and stdout.
stderr is ignored.
PseudoTTY work is done in start().
"""
if stdin is None:
stdin = sys.stdin.fileno()
elif hasattr(stdin, 'fileno'):
stdin = stdin.fileno()
if stdout is None:
stdout = sys.stdout.fileno()
elif hasattr(stdout, 'fileno'):
stdout = stdout.fileno()
with self._client() as podman:
attach = podman.GetAttachSockets(self._id)
# This is the UDS where all the IO goes
io_socket = attach['sockets']['io_socket']
assert len(io_socket) <= 107,\
'Path length for sockets too long. {} > 107'.format(
len(io_socket)
)
# This is the control socket where resizing events are sent to conmon
# attach['sockets']['control_socket']
self.pseudo_tty = collections.namedtuple(
'PseudoTTY',
['stdin', 'stdout', 'io_socket', 'control_socket', 'eot'])(
stdin,
stdout,
attach['sockets']['io_socket'],
attach['sockets']['control_socket'],
eot,
)
@property
def resize_handler(self):
"""Send the new window size to conmon."""
def wrapped(signum, frame): # pylint: disable=unused-argument
packed = fcntl.ioctl(self.pseudo_tty.stdout, termios.TIOCGWINSZ,
struct.pack('HHHH', 0, 0, 0, 0))
rows, cols, _, _ = struct.unpack('HHHH', packed)
logging.debug('Resize window(%dx%d) using %s', rows, cols,
self.pseudo_tty.control_socket)
# TODO: Need some kind of timeout in case pipe is blocked
with open(self.pseudo_tty.control_socket, 'w') as skt:
# send conmon window resize message
skt.write('1 {} {}\n'.format(rows, cols))
return wrapped
@property
def log_handler(self):
"""Send command to reopen log to conmon."""
def wrapped(signum, frame): # pylint: disable=unused-argument
with open(self.pseudo_tty.control_socket, 'w') as skt:
# send conmon reopen log message
skt.write('2\n')
return wrapped

View File

@ -1,82 +0,0 @@
"""Exported method Container.start()."""
import logging
import os
import select
import signal
import socket
import sys
import termios
import tty
CONMON_BUFSZ = 8192
class Mixin:
"""Publish start() for inclusion in Container class."""
def start(self):
"""Start container, return container on success.
Will block if container has been detached.
"""
with self._client() as podman:
logging.debug('Starting Container "%s"', self._id)
results = podman.StartContainer(self._id)
logging.debug('Started Container "%s"', results['container'])
if not hasattr(self, 'pseudo_tty') or self.pseudo_tty is None:
return self._refresh(podman)
logging.debug('Setting up PseudoTTY for Container "%s"',
results['container'])
try:
# save off the old settings for terminal
tcoldattr = termios.tcgetattr(self.pseudo_tty.stdin)
tty.setraw(self.pseudo_tty.stdin)
# initialize container's window size
self.resize_handler(None, sys._getframe(0))
# catch any resizing events and send the resize info
# to the control fifo "socket"
signal.signal(signal.SIGWINCH, self.resize_handler)
except termios.error:
tcoldattr = None
try:
# TODO: Is socket.SOCK_SEQPACKET supported in Windows?
with socket.socket(socket.AF_UNIX,
socket.SOCK_SEQPACKET) as skt:
# Prepare socket for use with conmon/container
skt.connect(self.pseudo_tty.io_socket)
sources = [skt, self.pseudo_tty.stdin]
while sources:
logging.debug('Waiting on sources: %s', sources)
readable, _, _ = select.select(sources, [], [])
if skt in readable:
data = skt.recv(CONMON_BUFSZ)
if data:
# Remove source marker when writing
os.write(self.pseudo_tty.stdout, data[1:])
else:
sources.remove(skt)
if self.pseudo_tty.stdin in readable:
data = os.read(self.pseudo_tty.stdin, CONMON_BUFSZ)
if data:
skt.sendall(data)
if self.pseudo_tty.eot in data:
sources.clear()
else:
sources.remove(self.pseudo_tty.stdin)
finally:
if tcoldattr:
termios.tcsetattr(self.pseudo_tty.stdin, termios.TCSADRAIN,
tcoldattr)
signal.signal(signal.SIGWINCH, signal.SIG_DFL)
return self._refresh(podman)

View File

@ -1,248 +0,0 @@
"""Models for manipulating containers and storage."""
import collections
import getpass
import json
import logging
import signal
import time
from . import fold_keys
from ._containers_attach import Mixin as AttachMixin
from ._containers_start import Mixin as StartMixin
class Container(AttachMixin, StartMixin, collections.UserDict):
"""Model for a container."""
def __init__(self, client, ident, data, refresh=True):
"""Construct Container Model."""
super(Container, self).__init__(data)
self._client = client
self._id = ident
if refresh:
with client() as podman:
self._refresh(podman)
else:
for k, v in self.data.items():
setattr(self, k, v)
if 'containerrunning' in self.data:
setattr(self, 'running', self.data['containerrunning'])
self.data['running'] = self.data['containerrunning']
assert self._id == data['id'],\
'Requested container id({}) does not match store id({})'.format(
self._id, data['id']
)
def _refresh(self, podman, tries=1):
try:
ctnr = podman.GetContainer(self._id)
except BrokenPipeError:
logging.debug('Failed GetContainer(%s) try %d/3', self._id, tries)
if tries > 3:
raise
else:
with self._client() as pman:
self._refresh(pman, tries + 1)
else:
super().update(ctnr['container'])
for k, v in self.data.items():
setattr(self, k, v)
if 'containerrunning' in self.data:
setattr(self, 'running', self.data['containerrunning'])
self.data['running'] = self.data['containerrunning']
return self
def refresh(self):
"""Refresh status fields for this container."""
with self._client() as podman:
return self._refresh(podman)
def processes(self):
"""Show processes running in container."""
with self._client() as podman:
results = podman.ListContainerProcesses(self._id)
yield from results['container']
def changes(self):
"""Retrieve container changes."""
with self._client() as podman:
results = podman.ListContainerChanges(self._id)
return results['container']
def kill(self, sig=signal.SIGTERM, wait=25):
"""Send signal to container.
default signal is signal.SIGTERM.
wait n of seconds, 0 waits forever.
"""
with self._client() as podman:
podman.KillContainer(self._id, sig)
timeout = time.time() + wait
while True:
self._refresh(podman)
if self.status != 'running': # pylint: disable=no-member
return self
if wait and timeout < time.time():
raise TimeoutError()
time.sleep(0.5)
def inspect(self):
"""Retrieve details about containers."""
with self._client() as podman:
results = podman.InspectContainer(self._id)
obj = json.loads(results['container'], object_hook=fold_keys())
return collections.namedtuple('ContainerInspect', obj.keys())(**obj)
def export(self, target):
"""Export container from store to tarball.
TODO: should there be a compress option, like images?
"""
with self._client() as podman:
results = podman.ExportContainer(self._id, target)
return results['tarfile']
def commit(self, image_name, **kwargs):
"""Create image from container.
Keyword arguments:
author -- change image's author
message -- change image's message, docker format only.
pause -- pause container during commit
change -- Additional properties to change
Change examples:
CMD=/usr/bin/zsh
ENTRYPOINT=/bin/sh date
ENV=TEST=test_containers.TestContainers.test_commit
EXPOSE=8888/tcp
LABEL=unittest=test_commit
USER=bozo:circus
VOLUME=/data
WORKDIR=/data/application
All changes overwrite existing values.
See inspect() to obtain current settings.
"""
author = kwargs.get('author', None) or getpass.getuser()
change = kwargs.get('change', None) or []
message = kwargs.get('message', None) or ''
pause = kwargs.get('pause', None) or True
for c in change:
if c.startswith('LABEL=') and c.count('=') < 2:
raise ValueError(
'LABEL should have the format: LABEL=label=value, not {}'.
format(c))
with self._client() as podman:
results = podman.Commit(self._id, image_name, change, author,
message, pause)
return results['image']
def stop(self, timeout=25):
"""Stop container, return id on success."""
with self._client() as podman:
podman.StopContainer(self._id, timeout)
return self._refresh(podman)
def remove(self, force=False):
"""Remove container, return id on success.
force=True, stop running container.
"""
with self._client() as podman:
results = podman.RemoveContainer(self._id, force)
return results['container']
def restart(self, timeout=25):
"""Restart container with timeout, return id on success."""
with self._client() as podman:
podman.RestartContainer(self._id, timeout)
return self._refresh(podman)
def rename(self, target): # pylint: disable=unused-argument
"""Rename container, return id on success."""
with self._client() as podman:
# TODO: Need arguments
results = podman.RenameContainer()
# TODO: fixup objects cached information
return results['container']
def resize_tty(self, width, height): # pylint: disable=unused-argument
"""Resize container tty."""
with self._client() as podman:
# TODO: magic re: attach(), arguments
podman.ResizeContainerTty()
def pause(self):
"""Pause container, return id on success."""
with self._client() as podman:
podman.PauseContainer(self._id)
return self._refresh(podman)
def unpause(self):
"""Unpause container, return id on success."""
with self._client() as podman:
podman.UnpauseContainer(self._id)
return self._refresh(podman)
def update_container(self, *args, **kwargs): \
# pylint: disable=unused-argument
"""TODO: Update container..., return id on success."""
with self._client() as podman:
podman.UpdateContainer()
return self._refresh(podman)
def wait(self):
"""Wait for container to finish, return 'returncode'."""
with self._client() as podman:
results = podman.WaitContainer(self._id)
return int(results['exitcode'])
def stats(self):
"""Retrieve resource stats from the container."""
with self._client() as podman:
results = podman.GetContainerStats(self._id)
obj = results['container']
return collections.namedtuple('StatDetail', obj.keys())(**obj)
def logs(self, *args, **kwargs): # pylint: disable=unused-argument
"""Retrieve container logs."""
with self._client() as podman:
results = podman.GetContainerLogs(self._id)
yield from results['container']
class Containers():
"""Model for Containers collection."""
def __init__(self, client):
"""Construct model for Containers collection."""
self._client = client
def list(self):
"""List of containers in the container store."""
with self._client() as podman:
results = podman.ListContainers()
for cntr in results['containers']:
yield Container(self._client, cntr['id'], cntr, refresh=False)
def delete_stopped(self):
"""Delete all stopped containers."""
with self._client() as podman:
results = podman.DeleteStoppedContainers()
return results['containers']
def get(self, id_):
"""Retrieve container details from store."""
with self._client() as podman:
cntr = podman.GetContainer(id_)
return Container(self._client, cntr['container']['id'],
cntr['container'])

View File

@ -1,77 +0,0 @@
"""Error classes and wrappers for VarlinkError."""
from varlink import VarlinkError
class VarlinkErrorProxy(VarlinkError):
"""Class to Proxy VarlinkError methods."""
def __init__(self, message, namespaced=False):
"""Construct proxy from Exception."""
super().__init__(message.as_dict(), namespaced)
self._message = message
self.__module__ = 'libpod'
def __getattr__(self, method):
"""Return attribute from proxied Exception."""
if hasattr(self._message, method):
return getattr(self._message, method)
try:
return self._message.parameters()[method]
except KeyError:
raise AttributeError('No such attribute: {}'.format(method))
class ContainerNotFound(VarlinkErrorProxy):
"""Raised when Client cannot find requested container."""
class ImageNotFound(VarlinkErrorProxy):
"""Raised when Client cannot find requested image."""
class PodNotFound(VarlinkErrorProxy):
"""Raised when Client cannot find requested image."""
class PodContainerError(VarlinkErrorProxy):
"""Raised when a container fails requested pod operation."""
class NoContainerRunning(VarlinkErrorProxy):
"""Raised when no container is running in pod."""
class NoContainersInPod(VarlinkErrorProxy):
"""Raised when Client fails to connect to runtime."""
class ErrorOccurred(VarlinkErrorProxy):
"""Raised when an error occurs during the execution.
See error() to see actual error text.
"""
class PodmanError(VarlinkErrorProxy):
"""Raised when Client fails to connect to runtime."""
ERROR_MAP = {
'io.podman.ContainerNotFound': ContainerNotFound,
'io.podman.ErrorOccurred': ErrorOccurred,
'io.podman.ImageNotFound': ImageNotFound,
'io.podman.NoContainerRunning': NoContainerRunning,
'io.podman.NoContainersInPod': NoContainersInPod,
'io.podman.PodContainerError': PodContainerError,
'io.podman.PodNotFound': PodNotFound,
'io.podman.RuntimeError': PodmanError,
}
def error_factory(exception):
"""Map Exceptions to a discrete type."""
try:
return ERROR_MAP[exception.error()](exception)
except KeyError:
return exception

View File

@ -1,164 +0,0 @@
"""Models for manipulating images in/to/from storage."""
import collections
import copy
import json
import logging
from . import ConfigDict, fold_keys
from .containers import Container
class Image(collections.UserDict):
"""Model for an Image."""
def __init__(self, client, id, data):
"""Construct Image Model."""
super().__init__(data)
for k, v in data.items():
setattr(self, k, v)
self._id = id
self._client = client
assert self._id == data['id'],\
'Requested image id({}) does not match store id({})'.format(
self._id, data['id']
)
@staticmethod
def _split_token(values=None, sep='='):
if not values:
return {}
return {
k: v1 for k, v1 in (v0.split(sep, 1) for v0 in values)
}
def create(self, *args, **kwargs):
"""Create container from image.
Pulls defaults from image.inspect()
"""
details = self.inspect()
config = ConfigDict(image_id=self._id, **kwargs)
config['command'] = details.config.get('cmd')
config['env'] = self._split_token(details.config.get('env'))
config['image'] = copy.deepcopy(details.repotags[0])
config['labels'] = copy.deepcopy(details.labels)
config['net_mode'] = 'bridge'
config['network'] = 'bridge'
logging.debug('Image %s: create config: %s', self._id, config)
with self._client() as podman:
id_ = podman.CreateContainer(config)['container']
cntr = podman.GetContainer(id_)
return Container(self._client, id_, cntr['container'])
container = create
def export(self, dest, compressed=False):
"""Write image to dest, return id on success."""
with self._client() as podman:
results = podman.ExportImage(self._id, dest, compressed)
return results['image']
def history(self):
"""Retrieve image history."""
with self._client() as podman:
for r in podman.HistoryImage(self._id)['history']:
yield collections.namedtuple('HistoryDetail', r.keys())(**r)
def inspect(self):
"""Retrieve details about image."""
with self._client() as podman:
results = podman.InspectImage(self._id)
obj = json.loads(results['image'], object_hook=fold_keys())
return collections.namedtuple('ImageInspect', obj.keys())(**obj)
def push(self, target, tlsverify=True):
"""Copy image to target, return id on success."""
with self._client() as podman:
results = podman.PushImage(self._id, target, tlsverify)
return results['image']
def remove(self, force=False):
"""Delete image, return id on success.
force=True, stop any running containers using image.
"""
with self._client() as podman:
results = podman.RemoveImage(self._id, force)
return results['image']
def tag(self, tag):
"""Tag image."""
with self._client() as podman:
results = podman.TagImage(self._id, tag)
return results['image']
class Images():
"""Model for Images collection."""
def __init__(self, client):
"""Construct model for Images collection."""
self._client = client
def list(self):
"""List all images in the libpod image store."""
with self._client() as podman:
results = podman.ListImages()
for img in results['images']:
yield Image(self._client, img['id'], img)
def build(self, dockerfile=None, tags=None, **kwargs):
"""Build container from image.
See podman-build.1.md for kwargs details.
"""
if dockerfile is None:
raise ValueError('"dockerfile" is a required argument.')
elif not hasattr(dockerfile, '__iter__'):
raise ValueError('"dockerfile" is required to be an iter.')
if tags is None:
raise ValueError('"tags" is a required argument.')
elif not hasattr(tags, '__iter__'):
raise ValueError('"tags" is required to be an iter.')
config = ConfigDict(dockerfile=dockerfile, tags=tags, **kwargs)
with self._client() as podman:
result = podman.BuildImage(config)
return self.get(result['image']['id']), \
(line for line in result['image']['logs'])
def delete_unused(self):
"""Delete Images not associated with a container."""
with self._client() as podman:
results = podman.DeleteUnusedImages()
return results['images']
def import_image(self, source, reference, message='', changes=None):
"""Read image tarball from source and save in image store."""
with self._client() as podman:
results = podman.ImportImage(source, reference, message, changes)
return results['image']
def pull(self, source):
"""Copy image from registry to image store."""
with self._client() as podman:
results = podman.PullImage(source)
return results['id']
def search(self, id_, limit=25):
"""Search registries for id."""
with self._client() as podman:
results = podman.SearchImage(id_, limit)
for img in results['images']:
yield collections.namedtuple('ImageSearch', img.keys())(**img)
def get(self, id_):
"""Get Image from id."""
with self._client() as podman:
result = podman.GetImage(id_)
return Image(self._client, result['image']['id'], result['image'])

View File

@ -1,163 +0,0 @@
"""Model for accessing details of Pods from podman service."""
import collections
import json
import signal
import time
from . import ConfigDict, FoldedString, fold_keys
class Pod(collections.UserDict):
"""Model for a Pod."""
def __init__(self, client, ident, data):
"""Construct Pod model."""
super().__init__(data)
self._ident = ident
self._client = client
with client() as podman:
self._refresh(podman)
def _refresh(self, podman):
pod = podman.GetPod(self._ident)
super().update(pod['pod'])
for k, v in self.data.items():
setattr(self, k, v)
return self
def inspect(self):
"""Retrieve details about pod."""
with self._client() as podman:
results = podman.InspectPod(self._ident)
obj = json.loads(results['pod'], object_hook=fold_keys())
obj['id'] = obj['config']['id']
return collections.namedtuple('PodInspect', obj.keys())(**obj)
def kill(self, signal_=signal.SIGTERM, wait=25):
"""Send signal to all containers in pod.
default signal is signal.SIGTERM.
wait n of seconds, 0 waits forever.
"""
with self._client() as podman:
podman.KillPod(self._ident, signal_)
timeout = time.time() + wait
while True:
# pylint: disable=maybe-no-member
self._refresh(podman)
running = FoldedString(self.status)
if running != 'running':
break
if wait and timeout < time.time():
raise TimeoutError()
time.sleep(0.5)
return self
def pause(self):
"""Pause all containers in the pod."""
with self._client() as podman:
podman.PausePod(self._ident)
return self._refresh(podman)
def refresh(self):
"""Refresh status fields for this pod."""
with self._client() as podman:
return self._refresh(podman)
def remove(self, force=False):
"""Remove pod and its containers returning pod ident.
force=True, stop any running container.
"""
with self._client() as podman:
results = podman.RemovePod(self._ident, force)
return results['pod']
def restart(self):
"""Restart all containers in the pod."""
with self._client() as podman:
podman.RestartPod(self._ident)
return self._refresh(podman)
def stats(self):
"""Stats on all containers in the pod."""
with self._client() as podman:
results = podman.GetPodStats(self._ident)
for obj in results['containers']:
yield collections.namedtuple('ContainerStats', obj.keys())(**obj)
def start(self):
"""Start all containers in the pod."""
with self._client() as podman:
podman.StartPod(self._ident)
return self._refresh(podman)
def stop(self):
"""Stop all containers in the pod."""
with self._client() as podman:
podman.StopPod(self._ident)
return self._refresh(podman)
def top(self):
"""Display stats for all containers."""
with self._client() as podman:
results = podman.TopPod(self._ident)
return results['pod']
def unpause(self):
"""Unpause all containers in the pod."""
with self._client() as podman:
podman.UnpausePod(self._ident)
return self._refresh(podman)
def wait(self):
"""Wait for all containers to exit."""
with self._client() as podman:
results = podman.WaitPod(self._ident)
return results['pod']
class Pods():
"""Model for accessing pods."""
def __init__(self, client):
"""Construct pod model."""
self._client = client
def create(self,
ident=None,
cgroupparent=None,
labels=None,
share=None,
infra=False):
"""Create a new empty pod."""
config = ConfigDict(
name=ident,
cgroupParent=cgroupparent,
labels=labels,
share=share,
infra=infra,
)
with self._client() as podman:
result = podman.CreatePod(config)
details = podman.GetPod(result['pod'])
return Pod(self._client, result['pod'], details['pod'])
def get(self, ident):
"""Get Pod from ident."""
with self._client() as podman:
result = podman.GetPod(ident)
return Pod(self._client, result['pod']['id'], result['pod'])
def list(self):
"""List all pods."""
with self._client() as podman:
results = podman.ListPods()
for pod in results['pods']:
yield Pod(self._client, pod['id'], pod)

View File

@ -1,40 +0,0 @@
"""Models for accessing details from varlink server."""
import collections
import pkg_resources
from . import cached_property
class System():
"""Model for accessing system resources."""
def __init__(self, client):
"""Construct system model."""
self._client = client
@cached_property
def versions(self):
"""Access versions."""
with self._client() as podman:
vers = podman.GetVersion()['version']
client = '0.0.0'
try:
client = pkg_resources.get_distribution('podman').version
except Exception: # pylint: disable=broad-except
pass
vers['client_version'] = client
return collections.namedtuple('Version', vers.keys())(**vers)
def info(self):
"""Return podman info."""
with self._client() as podman:
info = podman.GetInfo()['info']
return collections.namedtuple('Info', info.keys())(**info)
def ping(self):
"""Return True if server awake."""
with self._client() as podman:
response = podman.Ping()
return response['ping']['message'] == 'OK'

View File

@ -1,190 +0,0 @@
"""Cache for SSH tunnels."""
import collections
import getpass
import logging
import os
import subprocess
import threading
import time
import weakref
from contextlib import suppress
import psutil
Context = collections.namedtuple('Context', (
'uri',
'interface',
'local_socket',
'remote_socket',
'username',
'hostname',
'port',
'identity_file',
))
Context.__new__.__defaults__ = (None, ) * len(Context._fields)
class Portal(collections.MutableMapping):
"""Expiring container for tunnels."""
def __init__(self, sweap=25):
"""Construct portal, reap tunnels every sweap seconds."""
self.data = collections.OrderedDict()
self.sweap = sweap
self.ttl = sweap * 2
self.lock = threading.RLock()
self._schedule_reaper()
def __getitem__(self, key):
"""Given uri return tunnel and update TTL."""
with self.lock:
value, _ = self.data[key]
self.data[key] = (value, time.time() + self.ttl)
self.data.move_to_end(key)
return value
def __setitem__(self, key, value):
"""Store given tunnel keyed with uri."""
if not isinstance(value, Tunnel):
raise ValueError('Portals only support Tunnels.')
with self.lock:
self.data[key] = (value, time.time() + self.ttl)
self.data.move_to_end(key)
def __delitem__(self, key):
"""Remove and close tunnel from portal."""
with self.lock:
value, _ = self.data[key]
del self.data[key]
value.close()
del value
def __iter__(self):
"""Iterate tunnels."""
with self.lock:
values = self.data.values()
for tunnel, _ in values:
yield tunnel
def __len__(self):
"""Return number of tunnels in portal."""
with self.lock:
return len(self.data)
def _schedule_reaper(self):
timer = threading.Timer(interval=self.sweap, function=self.reap)
timer.setName('PortalReaper')
timer.setDaemon(True)
timer.start()
def reap(self):
"""Remove tunnels who's TTL has expired."""
now = time.time()
with self.lock:
reaped_data = self.data.copy()
for entry in reaped_data.items():
if entry[1][1] < now:
del self.data[entry[0]]
else:
# StopIteration as soon as possible
break
self._schedule_reaper()
class Tunnel():
"""SSH tunnel."""
def __init__(self, context):
"""Construct Tunnel."""
self.context = context
self._closed = True
@property
def closed(self):
"""Is tunnel closed."""
return self._closed
def bore(self):
"""Create SSH tunnel from given context."""
cmd = ['ssh', '-fNT']
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
cmd.append('-v')
else:
cmd.append('-q')
if self.context.port:
cmd.extend(('-p', str(self.context.port)))
cmd.extend(('-L', '{}:{}'.format(self.context.local_socket,
self.context.remote_socket)))
if self.context.identity_file:
cmd.extend(('-i', self.context.identity_file))
cmd.append('{}@{}'.format(self.context.username,
self.context.hostname))
logging.debug('Opening tunnel "%s", cmd "%s"', self.context.uri,
' '.join(cmd))
tunnel = subprocess.Popen(cmd, close_fds=True)
# The return value of Popen() has no long term value as that process
# has already exited by the time control is returned here. This is a
# side effect of the -f option. wait() will be called to clean up
# resources.
for _ in range(300):
# TODO: Make timeout configurable
if os.path.exists(self.context.local_socket) \
or tunnel.returncode is not None:
break
with suppress(subprocess.TimeoutExpired):
# waiting for either socket to be created
# or first child to exit
tunnel.wait(0.5)
else:
raise TimeoutError(
'Failed to create tunnel "{}", using: "{}"'.format(
self.context.uri, ' '.join(cmd)))
if tunnel.returncode is not None and tunnel.returncode != 0:
raise subprocess.CalledProcessError(tunnel.returncode,
' '.join(cmd))
tunnel.wait()
self._closed = False
weakref.finalize(self, self.close)
return self
def close(self):
"""Close SSH tunnel."""
logging.debug('Closing tunnel "%s"', self.context.uri)
if self._closed:
return
# Find all ssh instances for user with uri tunnel the hard way...
targets = [
p
for p in psutil.process_iter(attrs=['name', 'username', 'cmdline'])
if p.info['username'] == getpass.getuser()
and p.info['name'] == 'ssh'
and self.context.local_socket in ' '.join(p.info['cmdline'])
] # yapf: disable
# ask nicely for ssh to quit, reap results
for proc in targets:
proc.terminate()
_, alive = psutil.wait_procs(targets, timeout=300)
# kill off the uncooperative, then report any stragglers
for proc in alive:
proc.kill()
_, alive = psutil.wait_procs(targets, timeout=300)
for proc in alive:
logging.info('process %d survived SIGKILL, giving up.', proc.pid)
with suppress(OSError):
os.remove(self.context.local_socket)
self._closed = True

View File

@ -1,4 +0,0 @@
psutil
python-dateutil
setuptools>=39
varlink

View File

@ -1,38 +0,0 @@
#!/usr/bin/env python
import os
from setuptools import find_packages, setup
root = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(root, 'README.md')) as me:
readme = me.read()
with open(os.path.join(root, 'requirements.txt')) as r:
requirements = r.read().splitlines()
setup(
name='podman',
version=os.environ.get('PODMAN_VERSION', '0.0.0'),
description='A library for communicating with a Podman server',
author='Jhon Honce',
author_email='jhonce@redhat.com',
license='Apache Software License',
long_description=readme,
include_package_data=True,
install_requires=requirements,
packages=find_packages(exclude=['test']),
python_requires='>=3',
zip_safe=True,
url='http://github.com/containers/libpod',
keywords='varlink libpod podman',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python :: 3.4',
'Topic :: Software Development',
])

View File

@ -1,112 +0,0 @@
"""Base for podman tests."""
import contextlib
import functools
import itertools
import os
import subprocess
import time
import unittest
from varlink import VarlinkError
MethodNotImplemented = 'org.varlink.service.MethodNotImplemented'
class PodmanTestCase(unittest.TestCase):
"""Hide the sausage making of initializing storage."""
@classmethod
def setUpClass(cls):
"""Fixture to setup podman test case."""
if hasattr(PodmanTestCase, 'alpine_process'):
PodmanTestCase.tearDownClass()
def run_cmd(*args):
cmd = list(itertools.chain(*args))
try:
pid = subprocess.Popen(
cmd,
close_fds=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = pid.communicate()
except OSError as e:
print('{}: {}({})'.format(cmd, e.strerror, e.returncode))
except ValueError as e:
print('{}: {}'.format(cmd, e.message))
raise
else:
return out.strip()
tmpdir = os.environ.get('TMPDIR', '/tmp')
podman_args = [
'--storage-driver=vfs',
'--cgroup-manager=cgroupfs',
'--root={}/crio'.format(tmpdir),
'--runroot={}/crio-run'.format(tmpdir),
'--cni-config-dir={}/cni/net.d'.format(tmpdir),
]
run_podman = functools.partial(run_cmd, ['podman'], podman_args)
id = run_podman(['pull', 'alpine'])
setattr(PodmanTestCase, 'alpine_id', id)
run_podman(['pull', 'busybox'])
run_podman(['images'])
run_cmd(['rm', '-f', '{}/alpine_gold.tar'.format(tmpdir)])
run_podman([
'save', '--output', '{}/alpine_gold.tar'.format(tmpdir), 'alpine'
])
PodmanTestCase.alpine_log = open(
os.path.join('/tmp/', 'alpine.log'), 'w')
cmd = ['podman']
cmd.extend(podman_args)
# cmd.extend(['run', '-d', 'alpine', 'sleep', '500'])
cmd.extend(['run', '-dt', 'alpine', '/bin/sh'])
PodmanTestCase.alpine_process = subprocess.Popen(
cmd,
stdout=PodmanTestCase.alpine_log,
stderr=subprocess.STDOUT,
)
PodmanTestCase.busybox_log = open(
os.path.join('/tmp/', 'busybox.log'), 'w')
cmd = ['podman']
cmd.extend(podman_args)
cmd.extend(['create', 'busybox'])
PodmanTestCase.busybox_process = subprocess.Popen(
cmd,
stdout=PodmanTestCase.busybox_log,
stderr=subprocess.STDOUT,
)
# give podman time to start ctnr
time.sleep(2)
# Close our handle of file
PodmanTestCase.alpine_log.close()
PodmanTestCase.busybox_log.close()
@classmethod
def tearDownClass(cls):
"""Fixture to clean up after podman unittest."""
try:
PodmanTestCase.alpine_process.kill()
assert 0 == PodmanTestCase.alpine_process.wait(500)
delattr(PodmanTestCase, 'alpine_process')
PodmanTestCase.busybox_process.kill()
assert 0 == PodmanTestCase.busybox_process.wait(500)
except Exception as e:
print('Exception: {}'.format(e))
raise
@contextlib.contextmanager
def assertRaisesNotImplemented(self):
"""Sugar for unimplemented varlink methods."""
with self.assertRaisesRegex(VarlinkError, MethodNotImplemented):
yield

View File

@ -1,43 +0,0 @@
"""Decorator to retry failed method."""
import functools
import time
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, print_=None):
"""Retry calling the decorated function using an exponential backoff.
Specialized for our unittests
from:
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
:param ExceptionToCheck: the exception to check. may be a tuple of
exceptions to check
:type ExceptionToCheck: Exception or tuple
:param tries: number of times to try (not retry) before giving up
:type tries: int
:param delay: initial delay between retries in seconds
:type delay: int
:param backoff: backoff multiplier e.g. value of 2 will double the delay
each retry
:type backoff: int
"""
def deco_retry(f):
@functools.wraps(f)
def f_retry(*args, **kwargs):
mtries, mdelay = tries, delay
while mtries > 1:
try:
return f(*args, **kwargs)
except ExceptionToCheck as e:
if print_:
print_('{}, Retrying in {} seconds...'.format(
str(e), mdelay))
time.sleep(mdelay)
mtries -= 1
mdelay *= backoff
return f(*args, **kwargs)
return f_retry # true decorator
return deco_retry

View File

@ -1,35 +0,0 @@
from __future__ import absolute_import
import unittest
from unittest.mock import patch
import podman
from podman.client import BaseClient, Client, LocalClient, RemoteClient
class TestClient(unittest.TestCase):
def setUp(self):
pass
@patch('podman.libs.system.System.ping', return_value=True)
def test_local(self, mock_ping):
p = Client(
uri='unix:/run/podman',
interface='io.podman',
)
self.assertIsInstance(p._client, LocalClient)
self.assertIsInstance(p._client, BaseClient)
mock_ping.assert_called_once_with()
@patch('podman.libs.system.System.ping', return_value=True)
def test_remote(self, mock_ping):
p = Client(
uri='unix:/run/podman',
interface='io.podman',
remote_uri='ssh://user@hostname/run/podman/podman',
identity_file='~/.ssh/id_rsa')
self.assertIsInstance(p._client, BaseClient)
mock_ping.assert_called_once_with()

View File

@ -1,244 +0,0 @@
import os
import signal
import unittest
from pathlib import Path
from test.podman_testcase import PodmanTestCase
from test.retry_decorator import retry
import podman
class TestContainers(PodmanTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
def setUp(self):
self.tmpdir = os.environ['TMPDIR']
self.host = os.environ['PODMAN_HOST']
self.pclient = podman.Client(self.host)
self.loadCache()
def tearDown(self):
pass
def loadCache(self):
self.containers = list(self.pclient.containers.list())
self.alpine_ctnr = next(
iter([c for c in self.containers if 'alpine' in c['image']] or []),
None)
if self.alpine_ctnr and self.alpine_ctnr.status != 'running':
self.alpine_ctnr.start()
def test_list(self):
self.assertGreaterEqual(len(self.containers), 2)
self.assertIsNotNone(self.alpine_ctnr)
self.assertIn('alpine', self.alpine_ctnr.image)
def test_delete_stopped(self):
before = len(self.containers)
self.alpine_ctnr.stop()
target = self.alpine_ctnr.id
actual = self.pclient.containers.delete_stopped()
self.assertIn(target, actual)
self.loadCache()
after = len(self.containers)
self.assertLess(after, before)
TestContainers.setUpClass()
def test_get(self):
actual = self.pclient.containers.get(self.alpine_ctnr.id)
for k in ['id', 'status', 'ports']:
self.assertEqual(actual[k], self.alpine_ctnr[k])
with self.assertRaises(podman.ContainerNotFound):
self.pclient.containers.get("bozo")
def test_attach(self):
# StringIO does not support fileno() so we had to go old school
input = os.path.join(self.tmpdir, 'test_attach.stdin')
output = os.path.join(self.tmpdir, 'test_attach.stdout')
with open(input, 'w+') as mock_in, open(output, 'w+') as mock_out:
# double quote is indeed in the expected place
mock_in.write('echo H"ello, World"; exit\n')
mock_in.seek(0, 0)
ctnr = self.pclient.images.get(self.alpine_ctnr.image).container(
detach=True, tty=True)
ctnr.attach(stdin=mock_in.fileno(), stdout=mock_out.fileno())
ctnr.start()
mock_out.flush()
mock_out.seek(0, 0)
output = mock_out.read()
self.assertIn('Hello', output)
ctnr.remove(force=True)
def test_processes(self):
actual = list(self.alpine_ctnr.processes())
self.assertGreaterEqual(len(actual), 2)
def test_start_stop_wait(self):
ctnr = self.alpine_ctnr.stop()
self.assertFalse(ctnr['running'])
ctnr.start()
self.assertTrue(ctnr.running)
ctnr.stop()
self.assertFalse(ctnr['containerrunning'])
actual = ctnr.wait()
self.assertGreaterEqual(actual, 0)
def test_changes(self):
actual = self.alpine_ctnr.changes()
self.assertListEqual(
sorted(['changed', 'added', 'deleted']), sorted(
list(actual.keys())))
# TODO: brittle, depends on knowing history of ctnr
self.assertGreaterEqual(len(actual['changed']), 0)
self.assertGreaterEqual(len(actual['added']), 0)
self.assertEqual(len(actual['deleted']), 0)
def test_kill(self):
self.assertTrue(self.alpine_ctnr.running)
ctnr = self.alpine_ctnr.kill(signal.SIGKILL)
self.assertFalse(ctnr.running)
def test_inspect(self):
actual = self.alpine_ctnr.inspect()
self.assertEqual(actual.id, self.alpine_ctnr.id)
# TODO: Datetime values from inspect missing offset in CI instance
# self.assertEqual(
# datetime_parse(actual.created),
# datetime_parse(self.alpine_ctnr.createdat))
def test_export(self):
target = os.path.join(self.tmpdir, 'alpine_export_ctnr.tar')
actual = self.alpine_ctnr.export(target)
self.assertEqual(actual, target)
self.assertTrue(os.path.isfile(target))
self.assertGreater(os.path.getsize(target), 0)
def test_commit(self):
# TODO: Test for STOPSIGNAL when supported by OCI
# TODO: Test for message when supported by OCI
details = self.pclient.images.get(self.alpine_ctnr.image).inspect()
changes = ['ENV=' + i for i in details.config['env']]
changes.append('CMD=/usr/bin/zsh')
changes.append('ENTRYPOINT=/bin/sh date')
changes.append('ENV=TEST=test_containers.TestContainers.test_commit')
changes.append('EXPOSE=80')
changes.append('EXPOSE=8888')
changes.append('LABEL=unittest=test_commit')
changes.append('USER=bozo:circus')
changes.append('VOLUME=/data')
changes.append('WORKDIR=/data/application')
id = self.alpine_ctnr.commit(
'alpine3', author='Bozo the clown', change=changes, pause=True)
img = self.pclient.images.get(id)
self.assertIsNotNone(img)
details = img.inspect()
self.assertEqual(details.author, 'Bozo the clown')
self.assertListEqual(['/usr/bin/zsh'], details.config['cmd'])
self.assertListEqual(['/bin/sh date'],
details.config['entrypoint'])
self.assertIn('TEST=test_containers.TestContainers.test_commit',
details.config['env'])
self.assertTrue(
[e for e in details.config['env'] if 'PATH=' in e])
self.assertDictEqual({
'80': {},
'8888': {},
}, details.config['exposedports'])
self.assertDictEqual({'unittest': 'test_commit'}, details.labels)
self.assertEqual('bozo:circus', details.config['user'])
self.assertEqual({'/data': {}}, details.config['volumes'])
self.assertEqual('/data/application',
details.config['workingdir'])
def test_remove(self):
before = len(self.containers)
with self.assertRaises(podman.ErrorOccurred):
self.alpine_ctnr.remove()
self.assertEqual(
self.alpine_ctnr.id, self.alpine_ctnr.remove(force=True))
self.loadCache()
after = len(self.containers)
self.assertLess(after, before)
TestContainers.setUpClass()
def test_restart(self):
self.assertTrue(self.alpine_ctnr.running)
before = self.alpine_ctnr.runningfor
ctnr = self.alpine_ctnr.restart()
self.assertTrue(ctnr.running)
after = self.alpine_ctnr.runningfor
# TODO: restore check when restart zeros counter
# self.assertLess(after, before)
def test_rename(self):
with self.assertRaisesNotImplemented():
self.alpine_ctnr.rename('new_alpine')
def test_resize_tty(self):
with self.assertRaisesNotImplemented():
self.alpine_ctnr.resize_tty(132, 43)
def test_pause_unpause(self):
self.assertTrue(self.alpine_ctnr.running)
ctnr = self.alpine_ctnr.pause()
self.assertEqual(ctnr.status, 'paused')
ctnr = self.alpine_ctnr.unpause()
self.assertTrue(ctnr.running)
self.assertTrue(ctnr.status, 'running')
# creating cgoups can be flakey
@retry(podman.libs.errors.ErrorOccurred, tries=4, delay=2, print_=print)
def test_stats(self):
try:
self.assertTrue(self.alpine_ctnr.running)
actual = self.alpine_ctnr.stats()
self.assertEqual(self.alpine_ctnr.id, actual.id)
self.assertEqual(self.alpine_ctnr.names, actual.name)
except Exception:
info = Path('/proc/self/mountinfo')
with info.open() as fd:
print('{} {}'.format(self.alpine_ctnr.id, info))
print(fd.read())
def test_logs(self):
self.assertTrue(self.alpine_ctnr.running)
actual = list(self.alpine_ctnr.logs())
self.assertIsNotNone(actual)
if __name__ == '__main__':
unittest.main()

View File

@ -1,174 +0,0 @@
import itertools
import os
import unittest
from collections import Counter
from datetime import datetime, timezone
from test.podman_testcase import PodmanTestCase
import podman
from podman import FoldedString
class TestImages(PodmanTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
def setUp(self):
self.tmpdir = os.environ['TMPDIR']
self.host = os.environ['PODMAN_HOST']
self.pclient = podman.Client(self.host)
self.images = self.loadCache()
def tearDown(self):
pass
def loadCache(self):
with podman.Client(self.host) as pclient:
self.images = list(pclient.images.list())
self.alpine_image = next(
iter([
i for i in self.images
if 'docker.io/library/alpine:latest' in i['repoTags']
] or []), None)
return self.images
def test_list(self):
actual = self.loadCache()
self.assertGreaterEqual(len(actual), 2)
self.assertIsNotNone(self.alpine_image)
@unittest.skip('TODO: missing buildah json file')
def test_build(self):
path = os.path.join(self.tmpdir, 'ctnr', 'Dockerfile')
img, logs = self.pclient.images.build(
dockerfile=[path],
tags=['alpine-unittest'],
)
self.assertIsNotNone(img)
self.assertIn('localhost/alpine-unittest:latest', img.repoTags)
self.assertLess(
podman.datetime_parse(img.created), datetime.now(timezone.utc))
self.assertTrue(logs)
def test_create(self):
img_details = self.alpine_image.inspect()
actual = self.alpine_image.container(command=['sleep', '1h'])
self.assertIsNotNone(actual)
self.assertEqual(FoldedString(actual.status), 'configured')
ctnr = actual.start()
self.assertEqual(FoldedString(ctnr.status), 'running')
ctnr_details = ctnr.inspect()
for e in img_details.config['env']:
self.assertIn(e, ctnr_details.config['env'])
def test_export(self):
path = os.path.join(self.tmpdir, 'alpine_export.tar')
target = 'oci-archive:{}:latest'.format(path)
actual = self.alpine_image.export(target, False)
self.assertTrue(actual)
self.assertTrue(os.path.isfile(path))
def test_get(self):
actual = self.pclient.images.get(self.alpine_image.id)
self.assertEqual(actual, self.alpine_image)
def test_history(self):
records = []
bucket = Counter()
for record in self.alpine_image.history():
self.assertIn(record.id, (self.alpine_image.id, '<missing>'))
bucket[record.id] += 1
records.append(record)
self.assertGreater(bucket[self.alpine_image.id], 0)
self.assertEqual(sum(bucket.values()), len(records))
def test_inspect(self):
actual = self.alpine_image.inspect()
self.assertEqual(actual.id, self.alpine_image.id)
def test_push(self):
path = '{}/alpine_push'.format(self.tmpdir)
target = 'dir:{}'.format(path)
self.alpine_image.push(target, tlsverify=False)
self.assertTrue(os.path.isfile(os.path.join(path, 'manifest.json')))
self.assertTrue(os.path.isfile(os.path.join(path, 'version')))
def test_tag(self):
self.assertEqual(self.alpine_image.id,
self.alpine_image.tag('alpine:fubar'))
self.loadCache()
self.assertIn('localhost/alpine:fubar', self.alpine_image.repoTags)
def test_remove(self):
before = self.loadCache()
# assertRaises doesn't follow the import name :(
with self.assertRaises(podman.ErrorOccurred):
self.alpine_image.remove()
actual = self.alpine_image.remove(force=True)
self.assertEqual(self.alpine_image.id, actual)
after = self.loadCache()
self.assertLess(len(after), len(before))
TestImages.setUpClass()
self.loadCache()
def test_import_delete_unused(self):
before = self.loadCache()
# create unused image, so we have something to delete
source = os.path.join(self.tmpdir, 'alpine_gold.tar')
new_img = self.pclient.images.import_image(
source,
'alpine2:latest',
'unittest.test_import',
)
after = self.loadCache()
self.assertEqual(len(before) + 1, len(after))
self.assertIsNotNone(
next(iter([i for i in after if new_img in i['id']] or []), None))
actual = self.pclient.images.delete_unused()
self.assertIn(new_img, actual)
after = self.loadCache()
self.assertGreaterEqual(len(before), len(after))
TestImages.setUpClass()
self.loadCache()
def test_pull(self):
before = self.loadCache()
actual = self.pclient.images.pull('prom/busybox:latest')
after = self.loadCache()
self.assertEqual(len(before) + 1, len(after))
self.assertIsNotNone(
next(iter([i for i in after if actual in i['id']] or []), None))
def test_search(self):
actual = self.pclient.images.search('alpine', 25)
names, length = itertools.tee(actual)
for img in names:
self.assertIn('alpine', img.name)
self.assertTrue(0 < len(list(length)) <= 25)
if __name__ == '__main__':
unittest.main()

View File

@ -1,53 +0,0 @@
import datetime
import unittest
import podman
class TestLibs(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_parse(self):
expected = datetime.datetime.strptime(
'2018-05-08T14:12:53.797795-0700', '%Y-%m-%dT%H:%M:%S.%f%z')
for v in [
'2018-05-08T14:12:53.797795191-07:00',
'2018-05-08T14:12:53.797795-07:00',
'2018-05-08T14:12:53.797795-0700',
'2018-05-08 14:12:53.797795191 -0700 MST',
]:
actual = podman.datetime_parse(v)
self.assertEqual(actual, expected)
expected = datetime.datetime.strptime(
'2018-05-08T14:12:53.797795-0000', '%Y-%m-%dT%H:%M:%S.%f%z')
for v in [
'2018-05-08T14:12:53.797795191Z',
'2018-05-08T14:12:53.797795191z',
]:
actual = podman.datetime_parse(v)
self.assertEqual(actual, expected)
actual = podman.datetime_parse(datetime.datetime.now().isoformat())
self.assertIsNotNone(actual)
def test_parse_fail(self):
for v in [
'There is no time here.',
]:
with self.assertRaises(ValueError):
podman.datetime_parse(v)
def test_format(self):
expected = '2018-05-08T18:24:52.753227-07:00'
dt = podman.datetime_parse(expected)
actual = podman.datetime_format(dt)
self.assertEqual(actual, expected)
if __name__ == '__main__':
unittest.main()

View File

@ -1,66 +0,0 @@
import os
from test.podman_testcase import PodmanTestCase
import podman
from podman import FoldedString
pod = None
class TestPodsCtnrs(PodmanTestCase):
@classmethod
def setUpClass(cls):
# Populate storage
super().setUpClass()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
def setUp(self):
self.tmpdir = os.environ['TMPDIR']
self.host = os.environ['PODMAN_HOST']
self.pclient = podman.Client(self.host)
def test_010_populate(self):
global pod
pod = self.pclient.pods.create('pod1')
self.assertEqual('pod1', pod.name)
img = self.pclient.images.get('docker.io/library/alpine:latest')
ctnr = img.container(pod=pod.id)
pod.refresh()
self.assertEqual('1', pod.numberofcontainers)
self.assertEqual(ctnr.id, pod.containersinfo[0]['id'])
def test_015_one_shot(self):
global pod
details = pod.inspect()
state = FoldedString(details.containers[0]['state'])
self.assertEqual(state, 'configured')
pod = pod.start()
status = FoldedString(pod.containersinfo[0]['status'])
# Race on whether container is still running or finished
self.assertIn(status, ('stopped', 'exited', 'running'))
pod = pod.restart()
status = FoldedString(pod.containersinfo[0]['status'])
self.assertIn(status, ('stopped', 'exited', 'running'))
# Pod kill is broken, so use stop for now
killed = pod.stop()
self.assertEqual(pod, killed)
def test_999_remove(self):
global pod
ident = pod.remove(force=True)
self.assertEqual(ident, pod.id)
with self.assertRaises(StopIteration):
next(self.pclient.pods.list())

View File

@ -1,94 +0,0 @@
import os
import unittest
import podman
import varlink
ident = None
pod = None
class TestPodsNoCtnrs(unittest.TestCase):
def setUp(self):
self.tmpdir = os.environ['TMPDIR']
self.host = os.environ['PODMAN_HOST']
self.pclient = podman.Client(self.host)
def test_010_create(self):
global ident
actual = self.pclient.pods.create('pod0')
self.assertIsNotNone(actual)
ident = actual.id
def test_015_list(self):
global ident, pod
actual = next(self.pclient.pods.list())
self.assertEqual('pod0', actual.name)
self.assertEqual(ident, actual.id)
self.assertEqual('Created', actual.status)
self.assertEqual('0', actual.numberofcontainers)
self.assertFalse(actual.containersinfo)
pod = actual
def test_020_get(self):
global ident, pod
actual = self.pclient.pods.get(pod.id)
self.assertEqual('pod0', actual.name)
self.assertEqual(ident, actual.id)
self.assertEqual('Created', actual.status)
self.assertEqual('0', actual.numberofcontainers)
self.assertFalse(actual.containersinfo)
def test_025_inspect(self):
global ident, pod
details = pod.inspect()
self.assertEqual(ident, details.id)
self.assertEqual('pod0', details.config['name'])
self.assertIsNone(details.containers)
def test_030_ident_no_ctnrs(self):
global ident, pod
actual = pod.kill()
self.assertEqual(pod, actual)
actual = pod.pause()
self.assertEqual(pod, actual)
actual = pod.unpause()
self.assertEqual(pod, actual)
actual = pod.stop()
self.assertEqual(pod, actual)
def test_045_raises_no_ctnrs(self):
global ident, pod
with self.assertRaises(podman.NoContainersInPod):
pod.start()
with self.assertRaises(podman.NoContainersInPod):
pod.restart()
with self.assertRaises(podman.NoContainerRunning):
next(pod.stats())
with self.assertRaises(varlink.error.MethodNotImplemented):
pod.top()
with self.assertRaises(varlink.error.MethodNotImplemented):
pod.wait()
def test_999_remove(self):
global ident, pod
actual = pod.remove()
self.assertEqual(ident, actual)
with self.assertRaises(StopIteration):
next(self.pclient.pods.list())

View File

@ -1,156 +0,0 @@
#!/bin/bash
# podman needs to play some games with resources
if [[ $(id -u) != 0 ]]; then
echo >&2 $0 must be run as root.
exit 2
fi
# setup path to find new binaries _NOT_ system binaries
if [[ ! -x ../../../bin/podman ]]; then
echo 1>&2 Cannot find podman binary from libpod root directory. Run \"make binaries\"
exit 1
fi
export PATH=../../../bin:$PATH
function usage {
echo 1>&2 $0 '[-v] [-h] [test.<TestCase>|test.<TestCase>.<step>]'
}
while getopts "vh" arg; do
case $arg in
v ) VERBOSE='-v'; export LOG_LEVEL=debug ;;
h ) usage ; exit 0 ;;
\? ) usage ; exit 2 ;;
esac
done
shift $((OPTIND -1))
function cleanup {
set +xeuo pipefail
# aggressive cleanup as tests may crash leaving crap around
umount '^(shm|nsfs)'
umount '\/run\/netns'
if [[ $RETURNCODE -eq 0 ]]; then
rm -r "$1"
fi
}
# Create temporary directory for storage
export TMPDIR=`mktemp -d /tmp/podman.XXXXXXXXXX`
trap "cleanup $TMPDIR" EXIT
function umount {
set +xeuo pipefail
# xargs -r always ran once, so write any mount points to file first
mount |awk "/$1/"' { print $3 }' >${TMPDIR}/mounts
if [[ -s ${TMPDIR}/mounts ]]; then
xargs <${TMPDIR}/mounts -t umount
fi
}
function showlog {
[[ -s $1 ]] && cat <<-EOT
$1 =====
$(cat "$1")
EOT
}
# Need locations to store stuff
mkdir -p ${TMPDIR}/{podman,crio,crio-run,cni/net.d,ctnr,tunnel}
# Cannot be done in python unittest fixtures. EnvVar not picked up.
export REGISTRIES_CONFIG_PATH=${TMPDIR}/registry.conf
cat >$REGISTRIES_CONFIG_PATH <<-EOT
[registries.search]
registries = ['docker.io']
[registries.insecure]
registries = []
[registries.block]
registries = []
EOT
export CNI_CONFIG_PATH=${TMPDIR}/cni/net.d
cat >$CNI_CONFIG_PATH/87-podman-bridge.conflist <<-EOT
{
"cniVersion": "0.3.0",
"name": "podman",
"plugins": [{
"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"
}]
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
EOT
cat >$TMPDIR/ctnr/hello.sh <<-EOT
echo 'Hello, World'
exit 0
EOT
cat >$TMPDIR/ctnr/Dockerfile <<-EOT
FROM alpine:latest
COPY ./hello.sh /tmp/
RUN chmod 755 /tmp/hello.sh
ENTRYPOINT ["/tmp/hello.sh"]
EOT
export PODMAN_HOST="unix:${TMPDIR}/podman/io.podman"
PODMAN_ARGS="--storage-driver=vfs \
--root=${TMPDIR}/crio \
--runroot=${TMPDIR}/crio-run \
--cni-config-dir=$CNI_CONFIG_PATH \
--cgroup-manager=cgroupfs \
"
if [[ -n $VERBOSE ]]; then
PODMAN_ARGS="$PODMAN_ARGS --log-level=$LOG_LEVEL"
fi
PODMAN="podman $PODMAN_ARGS"
cat <<-EOT |tee /tmp/test_podman.output
$($PODMAN --version)
$PODMAN varlink --timeout=0 ${PODMAN_HOST}
==========================================
EOT
# Run podman in background without systemd for test purposes
$PODMAN varlink --timeout=0 ${PODMAN_HOST} >>/tmp/test_podman.output 2>&1 &
if [[ $? != 0 ]]; then
echo 1>&2 Failed to start podman
showlog /tmp/test_podman.output
fi
if [[ -z $1 ]]; then
export PYTHONPATH=.
python3 -m unittest discover -s . $VERBOSE
RETURNCODE=$?
else
export PYTHONPATH=.:./test
python3 -m unittest $1 $VERBOSE
RETURNCODE=$?
fi
pkill -9 podman
pkill -9 conmon
showlog /tmp/test_podman.output
showlog /tmp/alpine.log
showlog /tmp/busybox.log
exit $RETURNCODE

View File

@ -1,63 +0,0 @@
import os
import unittest
from urllib.parse import urlparse
import podman
import varlink
class TestSystem(unittest.TestCase):
def setUp(self):
self.host = os.environ['PODMAN_HOST']
self.tmpdir = os.environ['TMPDIR']
def tearDown(self):
pass
def test_bad_address(self):
with self.assertRaisesRegex(varlink.client.ConnectionError,
"Invalid address 'bad address'"):
podman.Client('bad address')
def test_ping(self):
with podman.Client(self.host) as pclient:
self.assertTrue(pclient.system.ping())
@unittest.skip("TODO: Need to setup ssh under Travis")
def test_remote_ping(self):
host = urlparse(self.host)
remote_uri = 'ssh://root@localhost{}'.format(host.path)
local_uri = 'unix:{}/tunnel/podman.sock'.format(self.tmpdir)
with podman.Client(
uri=local_uri,
remote_uri=remote_uri,
identity_file=os.path.expanduser('~/.ssh/id_rsa'),
) as remote_client:
self.assertTrue(remote_client.system.ping())
def test_versions(self):
with podman.Client(self.host) as pclient:
# Values change with each build so we cannot test too much
self.assertListEqual(
sorted([
'built', 'client_version', 'git_commit', 'go_version',
'os_arch', 'version'
]), sorted(list(pclient.system.versions._fields)))
pclient.system.versions
self.assertIsNot(podman.__version__, '0.0.0')
def test_info(self):
with podman.Client(self.host) as pclient:
actual = pclient.system.info()
# Values change too much to do exhaustive testing
self.assertIsNotNone(actual.podman['go_version'])
self.assertListEqual(
sorted([
'host', 'insecure_registries', 'podman', 'registries',
'store'
]), sorted(list(actual._fields)))
if __name__ == '__main__':
unittest.main()

View File

@ -1,86 +0,0 @@
from __future__ import absolute_import
import logging
import time
import unittest
from unittest.mock import MagicMock, patch
from podman.libs.tunnel import Context, Portal, Tunnel
class TestTunnel(unittest.TestCase):
def setUp(self):
self.tunnel_01 = MagicMock(spec=Tunnel)
self.tunnel_02 = MagicMock(spec=Tunnel)
def test_portal_ops(self):
portal = Portal(sweap=500)
portal['unix:/01'] = self.tunnel_01
portal['unix:/02'] = self.tunnel_02
self.assertEqual(portal.get('unix:/01'), self.tunnel_01)
self.assertEqual(portal.get('unix:/02'), self.tunnel_02)
del portal['unix:/02']
with self.assertRaises(KeyError):
portal['unix:/02']
self.assertEqual(len(portal), 1)
def test_portal_reaping(self):
portal = Portal(sweap=0.5)
portal['unix:/01'] = self.tunnel_01
portal['unix:/02'] = self.tunnel_02
self.assertEqual(len(portal), 2)
for entry in portal:
self.assertIn(entry, (self.tunnel_01, self.tunnel_02))
time.sleep(1)
portal.reap()
self.assertEqual(len(portal), 0)
def test_portal_no_reaping(self):
portal = Portal(sweap=500)
portal['unix:/01'] = self.tunnel_01
portal['unix:/02'] = self.tunnel_02
portal.reap()
self.assertEqual(len(portal), 2)
for entry in portal:
self.assertIn(entry, (self.tunnel_01, self.tunnel_02))
@patch('subprocess.Popen')
@patch('os.path.exists', return_value=True)
@patch('weakref.finalize')
def test_tunnel(self, mock_finalize, mock_exists, mock_Popen):
mock_Popen.return_value.returncode = 0
context = Context(
'unix:/01',
'io.podman',
'/tmp/user/socket',
'/run/podman/socket',
'user',
'hostname',
None,
'~/.ssh/id_rsa',
)
tunnel = Tunnel(context).bore()
cmd = ['ssh', '-fNT']
if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
cmd.append('-v')
else:
cmd.append('-q')
cmd.extend((
'-L',
'{}:{}'.format(context.local_socket, context.remote_socket),
'-i',
context.identity_file,
'{}@{}'.format(context.username, context.hostname),
))
mock_finalize.assert_called_once_with(tunnel, tunnel.close)
mock_exists.assert_called_once_with(context.local_socket)
mock_Popen.assert_called_once_with(cmd, close_fds=True)

View File

@ -1,8 +0,0 @@
[tox]
envlist = py34,py35,py36
skipdist = True
[testenv]
deps=-rrequirements.txt
whitelist_externals = bash
commands=bash test/test_runner.sh

View File

@ -1,564 +0,0 @@
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=0
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# Specify a configuration file.
#rcfile=
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=print-statement,
parameter-unpacking,
unpacking-in-except,
old-raise-syntax,
backtick,
long-suffix,
old-ne-operator,
old-octal-literal,
import-star-module-level,
non-ascii-bytes-literal,
raw-checker-failed,
bad-inline-option,
locally-disabled,
locally-enabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
apply-builtin,
basestring-builtin,
buffer-builtin,
cmp-builtin,
coerce-builtin,
execfile-builtin,
file-builtin,
long-builtin,
raw_input-builtin,
reduce-builtin,
standarderror-builtin,
unicode-builtin,
xrange-builtin,
coerce-method,
delslice-method,
getslice-method,
setslice-method,
no-absolute-import,
old-division,
dict-iter-method,
dict-view-method,
next-method-called,
metaclass-assignment,
indexing-exception,
raising-string,
reload-builtin,
oct-method,
hex-method,
nonzero-method,
cmp-method,
input-builtin,
round-builtin,
intern-builtin,
unichr-builtin,
map-builtin-not-iterating,
zip-builtin-not-iterating,
range-builtin-not-iterating,
filter-builtin-not-iterating,
using-cmp-argument,
eq-without-hash,
div-method,
idiv-method,
rdiv-method,
exception-message-attribute,
invalid-str-codec,
sys-max-int,
bad-python3-import,
deprecated-string-function,
deprecated-str-translate-call,
deprecated-itertools-function,
deprecated-types-field,
next-method-defined,
dict-items-not-iterating,
dict-keys-not-iterating,
dict-values-not-iterating,
deprecated-operator-function,
deprecated-urllib-function,
xreadlines-attribute,
deprecated-sys-function,
exception-escape,
comprehension-escape
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[REPORTS]
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
#msg-template=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package..
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,
dict-separator
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[BASIC]
# Naming style matching correct argument names.
#argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style.
argument-rgx=[a-z_][a-z0-9_]{1,30}$
argument-name-hint=[a-z_][a-z0-9_]{1,30}$
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=c,
e,
i,
j,
k,
r,
v,
ex,
Run,
_
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Naming style matching correct variable names.
#variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style.
variable-rgx=[a-z_][a-z0-9_]{2,30}$
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
[SIMILARITIES]
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
# Minimum lines number of a similarity.
min-similarity-lines=4
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[IMPORTS]
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled).
ext-import-graph=
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled).
import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
[DESIGN]
# Support argparse.Action constructor API
# Maximum number of arguments for function / method.
max-args=12
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement.
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=10
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception".
overgeneral-exceptions=Exception

View File

@ -1,2 +0,0 @@
prune test/
include README.md

View File

@ -1,39 +0,0 @@
PYTHON ?= $(shell command -v python3 2>/dev/null || command -v python)
DESTDIR := /
PODMAN_VERSION ?= '0.11.1.1'
.PHONY: python-pypodman
python-pypodman:
PODMAN_VERSION=$(PODMAN_VERSION) \
$(PYTHON) setup.py sdist bdist
.PHONY: lint
lint:
$(PYTHON) -m pylint pypodman
.PHONY: integration
integration:
true
.PHONY: install
install:
PODMAN_VERSION=$(PODMAN_VERSION) \
$(PYTHON) setup.py install --root ${DESTDIR}
.PHONY: upload
upload:
PODMAN_VERSION=$(PODMAN_VERSION) $(PYTHON) setup.py sdist bdist_wheel
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
.PHONY: clobber
clobber: uninstall clean
.PHONY: uninstall
$(PYTHON) -m pip uninstall --yes pypodman ||:
.PHONY: clean
clean:
rm -rf pypodman.egg-info dist
find . -depth -name __pycache__ -exec rm -rf {} \;
find . -depth -name \*.pyc -exec rm -f {} \;
$(PYTHON) ./setup.py clean --all

View File

@ -1,34 +0,0 @@
# pypodman - CLI for podman written in python
## Status: Active Development
See [libpod](https://github.com/containers/libpod/contrib/python/pypodman)
## Releases
To build the pypodman egg and install as user:
```sh
cd ~/libpod/contrib/python/pypodman
python3 setup.py clean -a && python3 setup.py sdist bdist
python3 setup.py install --user
```
Add `~/.local/bin` to your `PATH` to run pypodman command.
## Running command:
### Against local podman service
```sh
$ pypodman images
```
### Against remote podman service
```sh
$ pypodman --host node001.example.org images
```
### Full help system available
```sh
$ pypodman -h
```
```sh
$ pypodman images -h
```

View File

@ -1,101 +0,0 @@
.TH pypodman 1 2018-07-20 0.7.3
.SH NAME
pypodman \- CLI management tool for containers and images
.SH SYNOPSIS
\f[B]pypodman\f[] [\f[I]global options\f[]] \f[I]command\f[] [\f[I]options\f[]]
.SH DESCRIPTION
pypodman 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.
.P
pypodman uses a VarLink API to commicate with a podman service running on either
the local or remote machine. pypodman uses ssh to create secure tunnels when
communicating with a remote service.
.SH GLOBAL OPTIONS
.PP
\f[B]\[en]help, \-h\f[]
.PP
Print usage statement.
.PP
\f[B]\[en]version\f[]
.PP
Print program version number and exit.
.PP
\f[B]\[en]config\-home\f[]
.PP
Directory that will be namespaced with \f[C]pypodman\f[] to hold
\f[C]pypodman.conf\f[].
See FILES below for more details.
.PP
\f[B]\[en]log\-level\f[]
.PP
Log events above specified level: DEBUG, INFO, WARNING (default), ERROR,
or CRITICAL.
.PP
\f[B]\[en]run\-dir\f[]
.PP
Directory that will be namespaced with \f[C]pypodman\f[] to hold local socket
bindings. The default is `\f[C]$XDG_RUNTIME_DIR\\\f[].
.PP
\f[B]\[en]user\f[]
.PP
Authenicating user on remote host. \f[C]pypodman\f[] defaults to the logged in
user.
.PP
\f[B]\[en]host\f[]
.PP
Name of remote host. There is no default, if not given \f[C]pypodman\f[]
attempts to connect to \f[C]\-\-remote\-socket\-path\f[] on local host.
.PP
\f[B]\[en]port\f[]
.PP
The optional port for \f[C]ssh\f[] to connect tunnel to on remote host.
Default is None and will allow \f[C]ssh\f[] to follow it's default configuration.
.PP
\f[B]\[en]remote\-socket\-path\f[]
.PP
Path on remote host for podman service's \f[C]AF_UNIX\f[] socket. The default is
\f[C]/run/podman/io.podman\f[].
.PP
\f[B]\[en]identity\-file\f[]
.PP
The optional \f[C]ssh\f[] identity file to authenicate when tunnelling to remote
host. Default is None and will allow \f[C]ssh\f[] to follow it's default methods
for resolving the identity and private key using the logged in user.
.SH COMMANDS
.PP
See podman(1) (podman.1.md)
.SH FILES
.PP
\f[B]pypodman/pypodman.conf\f[]
(\f[C]Any\ element\ of\ XDG_CONFIG_DIRS\f[] and/or
\f[C]XDG_CONFIG_HOME\f[] and/or \f[B]\[en]config\-home\f[])
.PP
pypodman.conf is one or more configuration files for running the pypodman
command. pypodman.conf is a TOML file with the stanza \f[C][default]\f[], with a
map of \f[C]option: value\f[].
.PP
pypodman follows the XDG (freedesktop.org) conventions for resolving it's
configuration. The list below are read from top to bottom with later items
overwriting earlier. Any missing items are ignored.
.IP \[bu] 2
\f[C]pypodman/pypodman.conf\f[] from any path element in
\f[C]XDG_CONFIG_DIRS\f[] or \f[C]\\etc\\xdg\f[]
.IP \[bu] 2
\f[C]XDG_CONFIG_HOME\f[] or $HOME/.config + \f[C]pypodman/pypodman.conf\f[]
.IP \[bu] 2
From \f[C]\-\-config\-home\f[] command line option + \f[C]pypodman/pypodman.conf\f[]
.IP \[bu] 2
From environment variable prefixed with PODMAN_, for example: PODMAN_RUN_DIR
.IP \[bu] 2
From command line option, for example: \[en]run\-dir
.PP
This should provide Operators the ability to setup basic configurations
and allow users to customize them.
.PP
\f[B]XDG_RUNTIME_DIR\f[] (\f[C]XDG_RUNTIME_DIR/io.podman\f[])
.PP
Directory where pypodman stores non\-essential runtime files and other file
objects (such as sockets, named pipes, \&...).
.SH SEE ALSO
.PP
\f[C]podman(1)\f[], \f[C]libpod(8)\f[]

View File

@ -1,43 +0,0 @@
"""Remote podman client support library."""
import sys
import podman
from pypodman.lib.action_base import AbstractActionBase
from pypodman.lib.parser_actions import (ChangeAction, PathAction,
PositiveIntAction, SignalAction,
UnitAction)
from pypodman.lib.podman_parser import PodmanArgumentParser
from pypodman.lib.report import Report, ReportColumn
# Silence pylint overlording...
assert ChangeAction
assert PathAction
assert PositiveIntAction
assert SignalAction
assert UnitAction
__all__ = [
'AbstractActionBase',
'PodmanArgumentParser',
'Report',
'ReportColumn',
]
def query_model(model, identifiers=None):
"""Retrieve all (default) or given model(s)."""
objs = []
if identifiers is None:
objs.extend(model.list())
else:
try:
for ident in identifiers:
objs.append(model.get(ident))
except (
podman.PodNotFound,
podman.ImageNotFound,
podman.ContainerNotFound,
) as ex:
print(
'"{}" not found'.format(ex.name), file=sys.stderr, flush=True)
return objs

View File

@ -1,79 +0,0 @@
"""Base class for all actions of remote client."""
import abc
from functools import lru_cache
import podman
class AbstractActionBase(abc.ABC):
"""Base class for all actions of remote client."""
@classmethod
@abc.abstractmethod
def subparser(cls, parent):
"""Define parser for this action. Subclasses must implement.
API:
Use set_defaults() to set attributes "class_" and "method". These will
be invoked as class_(parsed_args).method()
"""
parent.add_flag(
'--all',
help='list all items.')
parent.add_flag(
'--truncate',
'--trunc',
default=True,
help="Truncate id's and other long fields.")
parent.add_flag(
'--heading',
default=True,
help='Include table headings in the output.')
parent.add_flag(
'--quiet',
help='List only the IDs.')
def __init__(self, args):
"""Construct class."""
# Dump all unset arguments before transmitting to service
self._args = args
self.opts = {
k: v
for k, v in vars(self._args).items() if v is not None
}
@property
def remote_uri(self):
"""URI for remote side of connection."""
return self._args.remote_uri
@property
def local_uri(self):
"""URI for local side of connection."""
return self._args.local_uri
@property
def identity_file(self):
"""Key for authenication."""
return self._args.identity_file
@property
@lru_cache(maxsize=1)
def client(self):
"""Podman remote client for communicating."""
if self._args.host is None:
return podman.Client(uri=self.local_uri)
return podman.Client(
uri=self.local_uri,
remote_uri=self.remote_uri,
identity_file=self.identity_file)
def __repr__(self):
"""Compute the “official” string representation of object."""
return ("{}(local_uri='{}', remote_uri='{}',"
" identity_file='{}')").format(
self.__class__,
self.local_uri,
self.remote_uri,
self.identity_file,
)

View File

@ -1,54 +0,0 @@
"""Module to export all the podman subcommands."""
from pypodman.lib.actions.attach_action import Attach
from pypodman.lib.actions.commit_action import Commit
from pypodman.lib.actions.create_action import Create
from pypodman.lib.actions.export_action import Export
from pypodman.lib.actions.history_action import History
from pypodman.lib.actions.images_action import Images
from pypodman.lib.actions.import_action import Import
from pypodman.lib.actions.info_action import Info
from pypodman.lib.actions.inspect_action import Inspect
from pypodman.lib.actions.kill_action import Kill
from pypodman.lib.actions.logs_action import Logs
from pypodman.lib.actions.mount_action import Mount
from pypodman.lib.actions.pause_action import Pause
from pypodman.lib.actions.pod_action import Pod
from pypodman.lib.actions.port_action import Port
from pypodman.lib.actions.ps_action import Ps
from pypodman.lib.actions.pull_action import Pull
from pypodman.lib.actions.push_action import Push
from pypodman.lib.actions.restart_action import Restart
from pypodman.lib.actions.rm_action import Rm
from pypodman.lib.actions.rmi_action import Rmi
from pypodman.lib.actions.run_action import Run
from pypodman.lib.actions.search_action import Search
from pypodman.lib.actions.start_action import Start
from pypodman.lib.actions.version_action import Version
__all__ = [
'Attach',
'Commit',
'Create',
'Export',
'History',
'Images',
'Import',
'Info',
'Inspect',
'Kill',
'Logs',
'Mount',
'Pause',
'Pod',
'Port',
'Ps',
'Pull',
'Push',
'Restart',
'Rm',
'Rmi',
'Run',
'Search',
'Start',
'Version',
]

View File

@ -1,401 +0,0 @@
"""Implement common create container arguments together."""
from pypodman.lib import SignalAction, UnitAction
class CreateArguments():
"""Helper to add all the create flags to a command."""
@classmethod
def add_arguments(cls, parser):
"""Add CreateArguments to parser."""
parser.add_argument(
'--add-host',
action='append',
metavar='HOST',
help='Add a line to /etc/hosts.'
' The option can be set multiple times.'
' (format: hostname:ip)')
parser.add_argument(
'--annotation',
action='append',
help='Add an annotation to the container.'
'The option can be set multiple times.'
'(format: key=value)')
parser.add_argument(
'--attach',
'-a',
action='append',
metavar='FD',
help=('Attach to STDIN, STDOUT or STDERR. The option can be set'
' for each of stdin, stdout, and stderr.'))
parser.add_argument(
'--blkio-weight',
choices=range(10, 1000),
metavar='[10-1000]',
help=('Block IO weight (relative weight) accepts a'
' weight value between 10 and 1000.'))
parser.add_argument(
'--blkio-weight-device',
action='append',
metavar='WEIGHT',
help='Block IO weight, relative device weight.'
' (format: DEVICE_NAME:WEIGHT)')
parser.add_argument(
'--cap-add',
action='append',
metavar='CAP',
help=('Add Linux capabilities'
'The option can be set multiple times.'))
parser.add_argument(
'--cap-drop',
action='append',
metavar='CAP',
help=('Drop Linux capabilities'
'The option can be set multiple times.'))
parser.add_argument(
'--cgroup-parent',
metavar='PATH',
help='Path to cgroups under which the cgroup for the'
' container will be created. If the path is not'
' absolute, the path is considered to be relative'
' to the cgroups path of the init process. Cgroups'
' will be created if they do not already exist.')
parser.add_argument(
'--cidfile',
metavar='PATH',
help='Write the container ID to the file, on the remote host.')
parser.add_argument(
'--conmon-pidfile',
metavar='PATH',
help=('Write the pid of the conmon process to a file,'
' on the remote host.'))
parser.add_argument(
'--cpu-period',
type=int,
metavar='PERIOD',
help=('Limit the CPU CFS (Completely Fair Scheduler) period.'))
parser.add_argument(
'--cpu-quota',
type=int,
metavar='QUOTA',
help=('Limit the CPU CFS (Completely Fair Scheduler) quota.'))
parser.add_argument(
'--cpu-rt-period',
type=int,
metavar='PERIOD',
help=('Limit the CPU real-time period in microseconds.'))
parser.add_argument(
'--cpu-rt-runtime',
type=int,
metavar='LIMIT',
help=('Limit the CPU real-time runtime in microseconds.'))
parser.add_argument(
'--cpu-shares',
type=int,
metavar='SHARES',
help=('CPU shares (relative weight)'))
parser.add_argument(
'--cpus',
type=float,
help=('Number of CPUs. The default is 0.0 which means no limit'))
parser.add_argument(
'--cpuset-cpus',
metavar='LIST',
help=('CPUs in which to allow execution (0-3, 0,1)'))
parser.add_argument(
'--cpuset-mems',
metavar='NODES',
help=('Memory nodes (MEMs) in which to allow execution (0-3, 0,1).'
' Only effective on NUMA systems'))
parser.add_flag(
'--detach',
'-d',
help='Detached mode: run the container in the background and'
' print the new container ID. (default: False)')
parser.add_argument(
'--detach-keys',
metavar='KEY(s)',
default=4,
help='Override the key sequence for detaching a container.'
' (format: a single character [a-Z] or ctrl-<value> where'
' <value> is one of: a-z, @, ^, [, , or _)')
parser.add_argument(
'--device',
action='append',
help=('Add a host device to the container'
'The option can be set multiple times.'),
)
parser.add_argument(
'--device-read-bps',
action='append',
metavar='LIMIT',
help=('Limit read rate (bytes per second) from a device'
' (e.g. --device-read-bps=/dev/sda:1mb)'
'The option can be set multiple times.'),
)
parser.add_argument(
'--device-read-iops',
action='append',
metavar='LIMIT',
help=('Limit read rate (IO per second) from a device'
' (e.g. --device-read-iops=/dev/sda:1000)'
'The option can be set multiple times.'),
)
parser.add_argument(
'--device-write-bps',
action='append',
metavar='LIMIT',
help=('Limit write rate (bytes per second) to a device'
' (e.g. --device-write-bps=/dev/sda:1mb)'
'The option can be set multiple times.'),
)
parser.add_argument(
'--device-write-iops',
action='append',
metavar='LIMIT',
help=('Limit write rate (IO per second) to a device'
' (e.g. --device-write-iops=/dev/sda:1000)'
'The option can be set multiple times.'),
)
parser.add_argument(
'--dns',
action='append',
metavar='SERVER',
help=('Set custom DNS servers.'
'The option can be set multiple times.'),
)
parser.add_argument(
'--dns-option',
action='append',
metavar='OPT',
help=('Set custom DNS options.'
'The option can be set multiple times.'),
)
parser.add_argument(
'--dns-search',
action='append',
metavar='DOMAIN',
help=('Set custom DNS search domains.'
'The option can be set multiple times.'),
)
parser.add_argument(
'--entrypoint',
help=('Overwrite the default ENTRYPOINT of the image.'),
)
parser.add_argument(
'--env',
'-e',
action='append',
help=('Set environment variables.'),
)
parser.add_argument(
'--env-file',
help=('Read in a line delimited file of environment variables,'
' on the remote host.'),
)
parser.add_argument(
'--expose',
action='append',
metavar='RANGE',
help=('Expose a port, or a range of ports'
' (e.g. --expose=3300-3310) to set up port redirection.'),
)
parser.add_argument(
'--gidmap',
metavar='MAP',
action='append',
help=('GID map for the user namespace'),
)
parser.add_argument(
'--group-add',
action='append',
metavar='GROUP',
help=('Add additional groups to run as'))
parser.add_argument('--hostname', help='Container host name')
# only way for argparse to handle these options.
vol_args = {
'choices': ('bind', 'tmpfs', 'ignore'),
'metavar': 'MODE',
'type': str.lower,
'help': 'Tells podman how to handle the builtin image volumes',
}
volume_group = parser.add_mutually_exclusive_group()
volume_group.add_argument('--image-volume', **vol_args)
volume_group.add_argument('--builtin-volume', **vol_args)
parser.add_flag(
'--interactive',
'-i',
help='Keep STDIN open even if not attached.')
parser.add_argument('--ipc', help='Create namespace')
parser.add_argument(
'--kernel-memory', action=UnitAction, help='Kernel memory limit')
parser.add_argument(
'--label',
'-l',
action='append',
help=('Add metadata to a container'
' (e.g., --label com.example.key=value)'))
parser.add_argument(
'--label-file', help='Read in a line delimited file of labels')
parser.add_argument(
'--log-driver',
choices='json-file',
metavar='json-file',
default='json-file',
help='Logging driver for the container. (default: %(default)s)')
parser.add_argument(
'--log-opt',
action='append',
help='Logging driver specific options')
parser.add_argument(
'--memory', '-m', action=UnitAction, help='Memory limit')
parser.add_argument(
'--memory-reservation',
action=UnitAction,
help='Memory soft limit')
parser.add_argument(
'--memory-swap',
action=UnitAction,
help=('A limit value equal to memory plus swap.'
'Must be used with the --memory flag'))
parser.add_argument(
'--memory-swappiness',
choices=range(0, 100),
metavar='[0-100]',
help="Tune a container's memory swappiness behavior")
parser.add_argument('--name', help='Assign a name to the container')
parser.add_argument(
'--network',
'--net',
metavar='BRIDGE',
help='Set the Network mode for the container.'
' (format: bridge, host, container:UUID, ns:PATH, none)')
parser.add_flag(
'--oom-kill-disable',
help='Whether to disable OOM Killer for the container or not.')
parser.add_argument(
'--oom-score-adj',
choices=range(-1000, 1000),
metavar='[-1000-1000]',
help="Tune the host's OOM preferences for containers")
parser.add_argument(
'--pid',
help='Set the PID Namespace mode for the container.'
'(format: host, container:UUID, ns:PATH)')
parser.add_argument(
'--pids-limit',
type=int,
metavar='LIMIT',
help=("Tune the container's pids limit."
" Set -1 to have unlimited pids for the container."))
parser.add_argument('--pod', help='Run container in an existing pod')
parser.add_flag(
'--privileged',
help='Give extended privileges to this container.')
parser.add_argument(
'--publish',
'-p',
metavar='RANGE',
help="Publish a container's port, or range of ports, to the host")
parser.add_flag(
'--publish-all',
'-P',
help='Publish all exposed ports to random'
' ports on the host interfaces.')
parser.add_flag(
'--quiet',
'-q',
help='Suppress output information when pulling images')
parser.add_flag(
'--read-only',
help="Mount the container's root filesystem as read only.")
parser.add_flag(
'--rm',
help='Automatically remove the container when it exits.')
parser.add_argument(
'--rootfs',
help='If specified, the first argument refers to an'
' exploded container on the file system of remote host.')
parser.add_argument(
'--security-opt',
action='append',
metavar='OPT',
help='Set security options.')
parser.add_argument(
'--shm-size', action=UnitAction, help='Size of /dev/shm')
parser.add_flag(
'--sig-proxy',
help='Proxy signals sent to the podman run'
' command to the container process')
parser.add_argument(
'--stop-signal',
action=SignalAction,
default='TERM',
help='Signal to stop a container')
parser.add_argument(
'--stop-timeout',
metavar='TIMEOUT',
type=int,
default=10,
help='Seconds to wait on stopping container.')
parser.add_argument(
'--subgidname',
metavar='MAP',
help='Name for GID map from the /etc/subgid file')
parser.add_argument(
'--subuidname',
metavar='MAP',
help='Name for UID map from the /etc/subuid file')
parser.add_argument(
'--sysctl',
action='append',
help='Configure namespaced kernel parameters at runtime')
parser.add_argument(
'--tmpfs',
action='append',
metavar='MOUNT',
help='Create a tmpfs mount.'
' (default: rw,noexec,nosuid,nodev,size=65536k.)')
parser.add_flag(
'--tty',
'-t',
help='Allocate a pseudo-TTY for standard input of container.')
parser.add_argument(
'--uidmap',
action='append',
metavar='MAP',
help='UID map for the user namespace')
parser.add_argument(
'--ulimit',
action='append',
metavar='OPT',
help='Ulimit options',
)
parser.add_argument(
'--user',
'-u',
help='Sets the username or UID used and optionally'
' the groupname or GID for the specified command.')
parser.add_argument(
'--userns',
metavar='NAMESPACE',
help='Set the user namespace mode for the container')
parser.add_argument(
'--uts',
choices=('host', 'ns'),
type=str.lower,
help='Set the UTS mode for the container')
parser.add_argument('--volume', '-v', help='Create a bind mount.')
parser.add_argument(
'--volumes-from',
action='append',
help='Mount volumes from the specified container(s).')
parser.add_argument(
'--workdir',
'-w',
metavar='PATH',
help='Working directory inside the container')

View File

@ -1,68 +0,0 @@
"""Remote client command for attaching to a container."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Attach(AbstractActionBase):
"""Class for attaching to a running container."""
@classmethod
def subparser(cls, parent):
"""Add Attach command to parent parser."""
parser = parent.add_parser('attach', help='attach to container')
parser.add_argument(
'--image',
help='image to instantiate and attach to',
)
parser.add_argument(
'command',
nargs='*',
help='image to instantiate and attach to',
)
parser.set_defaults(class_=cls, method='attach')
def __init__(self, args):
"""Construct Attach class."""
super().__init__(args)
if not args.image:
raise ValueError('You must supply one image id'
' or name to be attached.')
def attach(self):
"""Attach to instantiated image."""
args = {
'detach': True,
'tty': True,
}
if self._args.command:
args['command'] = self._args.command
try:
try:
ident = self.client.images.pull(self._args.image)
img = self.client.images.get(ident)
except podman.ImageNotFound as e:
sys.stdout.flush()
print(
'Image {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
return 1
ctnr = img.create(**args)
ctnr.attach(eot=4)
try:
ctnr.start()
print()
except (BrokenPipeError, KeyboardInterrupt):
print('\nContainer disconnected.')
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1

View File

@ -1,99 +0,0 @@
"""Remote client command for creating image from container."""
import sys
import podman
from pypodman.lib import AbstractActionBase, ChangeAction
class Commit(AbstractActionBase):
"""Class for creating image from container."""
@classmethod
def subparser(cls, parent):
"""Add Commit command to parent parser."""
parser = parent.add_parser(
'commit',
help='create image from container',
)
parser.add_argument(
'--author',
help='Set the author for the committed image',
)
parser.add_argument(
'--change',
'-c',
action=ChangeAction,
)
parser.add_argument(
'--format',
'-f',
choices=('oci', 'docker'),
default='oci',
type=str.lower,
help='Set the format of the image manifest and metadata.'
' (Ignored.)',
)
parser.add_argument(
'--iidfile',
metavar='PATH',
help='Write the image ID to the file',
)
parser.add_argument(
'--message',
'-m',
help='Set commit message for committed image'
' (Only on docker images.)',
)
parser.add_flag(
'--pause',
'-p',
help='Pause the container when creating an image',
)
parser.add_flag(
'--quiet',
'-q',
help='Suppress output',
)
parser.add_argument(
'container',
nargs=1,
help='container to use as source',
)
parser.add_argument(
'image',
nargs=1,
help='image name to create',
)
parser.set_defaults(class_=cls, method='commit')
def commit(self):
"""Create image from container."""
try:
try:
ctnr = self.client.containers.get(self._args.container[0])
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
return 1
else:
ident = ctnr.commit(
self.opts['image'][0],
change=self.opts.get('change', None),
message=self.opts.get('message', None),
pause=self.opts['pause'],
author=self.opts.get('author', None),
)
if not self.opts['quiet']:
print(ident)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,55 +0,0 @@
"""Remote client command for creating container from image."""
import sys
import podman
from pypodman.lib import AbstractActionBase
from ._create_args import CreateArguments
class Create(AbstractActionBase):
"""Class for creating container from image."""
@classmethod
def subparser(cls, parent):
"""Add Create command to parent parser."""
parser = parent.add_parser(
'create', help='create container from image')
CreateArguments.add_arguments(parser)
parser.add_argument('image', nargs=1, help='source image id')
parser.add_argument(
'command',
nargs=parent.REMAINDER,
help='command and args to run.',
)
parser.set_defaults(class_=cls, method='create')
def __init__(self, args):
"""Construct Create class."""
super().__init__(args)
# image id used only on client
del self.opts['image']
def create(self):
"""Create container."""
try:
for ident in self._args.image:
try:
img = self.client.images.get(ident)
img.container(**self.opts)
print(ident)
except podman.ImageNotFound as e:
sys.stdout.flush()
print(
'Image {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)

View File

@ -1,54 +0,0 @@
"""Remote client command for export container filesystem to tarball."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Export(AbstractActionBase):
"""Class for exporting container filesystem to tarball."""
@classmethod
def subparser(cls, parent):
"""Add Export command to parent parser."""
parser = parent.add_parser(
'export',
help='export container to tarball',
)
parser.add_argument(
'--output',
'-o',
metavar='PATH',
nargs=1,
required=True,
help='Write to this file on host',
)
parser.add_argument(
'container',
nargs=1,
help='container to use as source',
)
parser.set_defaults(class_=cls, method='export')
def export(self):
"""Create tarball from container filesystem."""
try:
try:
ctnr = self.client.containers.get(self._args.container[0])
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
return 1
else:
ctnr.export(self._args.output[0])
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,79 +0,0 @@
"""Remote client for reporting image history."""
import json
from collections import OrderedDict
import humanize
import podman
from pypodman.lib import AbstractActionBase, Report, ReportColumn
class History(AbstractActionBase):
"""Class for reporting Image History."""
@classmethod
def subparser(cls, parent):
"""Add History command to parent parser."""
parser = parent.add_parser('history', help='report image history')
super().subparser(parser)
parser.add_flag(
'--human',
'-H',
help='Display sizes and dates in human readable format.')
parser.add_argument(
'--format',
choices=('json', 'table'),
help="Alter the output for a format like 'json' or 'table'."
" (default: table)")
parser.add_argument(
'image', nargs='+', help='image for history report')
parser.set_defaults(class_=cls, method='history')
def __init__(self, args):
"""Construct History class."""
super().__init__(args)
self.columns = OrderedDict({
'id':
ReportColumn('id', 'ID', 12),
'created':
ReportColumn('created', 'CREATED', 11),
'createdBy':
ReportColumn('createdBy', 'CREATED BY', 45),
'size':
ReportColumn('size', 'SIZE', 8),
'comment':
ReportColumn('comment', 'COMMENT', 0)
})
def history(self):
"""Report image history."""
rows = list()
for ident in self._args.image:
for details in self.client.images.get(ident).history():
fields = dict(details._asdict())
if self._args.human:
fields.update({
'size':
humanize.naturalsize(details.size),
'created':
humanize.naturaldate(
podman.datetime_parse(details.created)),
})
del fields['tags']
rows.append(fields)
if self._args.quiet:
for row in rows:
ident = row['id'][:12] if self._args.truncate else row['id']
print(ident)
elif self._args.format == 'json':
print(json.dumps(rows, indent=2), flush=True)
else:
with Report(self.columns, heading=self._args.heading) as report:
report.layout(
rows, self.columns.keys(), truncate=self._args.truncate)
for row in rows:
report.row(**row)

View File

@ -1,86 +0,0 @@
"""Remote client commands dealing with images."""
import operator
from collections import OrderedDict
import humanize
import podman
from pypodman.lib import AbstractActionBase, Report, ReportColumn
class Images(AbstractActionBase):
"""Class for Image manipulation."""
@classmethod
def subparser(cls, parent):
"""Add Images commands to parent parser."""
parser = parent.add_parser('images', help='list images')
super().subparser(parser)
parser.add_argument(
'--sort',
choices=['created', 'id', 'repository', 'size', 'tag'],
default='created',
type=str.lower,
help=('Change sort ordered of displayed images.'
' (default: %(default)s)'))
parser.add_flag(
'--digests',
help='Include digests with images.')
parser.set_defaults(class_=cls, method='list')
def __init__(self, args):
"""Construct Images class."""
super().__init__(args)
self.columns = OrderedDict({
'name':
ReportColumn('name', 'REPOSITORY', 0),
'tag':
ReportColumn('tag', 'TAG', 10),
'id':
ReportColumn('id', 'IMAGE ID', 12),
'created':
ReportColumn('created', 'CREATED', 12),
'size':
ReportColumn('size', 'SIZE', 8),
'repoDigests':
ReportColumn('repoDigests', 'DIGESTS', 35),
})
def list(self):
"""List images."""
images = sorted(
self.client.images.list(),
key=operator.attrgetter(self._args.sort))
if not images:
return
rows = list()
for image in images:
fields = dict(image)
fields.update({
'created':
humanize.naturaldate(podman.datetime_parse(image.created)),
'size':
humanize.naturalsize(int(image.size)),
'repoDigests':
' '.join(image.repoDigests),
})
for r in image.repoTags:
name, tag = r.rsplit(':', 1)
fields.update({
'name': name,
'tag': tag,
})
rows.append(fields)
if not self._args.digests:
del self.columns['repoDigests']
with Report(self.columns, heading=self._args.heading) as report:
report.layout(
rows, self.columns.keys(), truncate=self._args.truncate)
for row in rows:
report.row(**row)

View File

@ -1,69 +0,0 @@
"""Remote client command to import tarball as image filesystem."""
import sys
import podman
from pypodman.lib import AbstractActionBase, ChangeAction
class Import(AbstractActionBase):
"""Class for importing tarball as image filesystem."""
@classmethod
def subparser(cls, parent):
"""Add Import command to parent parser."""
parser = parent.add_parser(
'import',
help='import tarball as image filesystem',
)
parser.add_argument(
'--change',
'-c',
action=ChangeAction,
)
parser.add_argument(
'--message',
'-m',
help='Set commit message for imported image.',
)
parser.add_argument(
'source',
metavar='PATH',
nargs=1,
help='tarball to use as source on remote system',
)
parser.add_argument(
'reference',
metavar='TAG',
nargs='*',
help='Optional tag for image. (default: None)',
)
parser.set_defaults(class_=cls, method='import_')
def import_(self):
"""Import tarball as image filesystem."""
# ImportImage() validates it's parameters therefore we need to create
# pristine dict() for keywords
options = {}
if 'message' in self.opts:
options['message'] = self.opts['message']
if 'change' in self.opts and self.opts['change']:
options['changes'] = self.opts['change']
reference = self.opts['reference'][0] if 'reference' in self.opts\
else None
try:
ident = self.client.images.import_image(
self.opts['source'][0],
reference,
**options,
)
print(ident)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,45 +0,0 @@
"""Remote client command for reporting on Podman service."""
import json
import sys
import podman
import yaml
from pypodman.lib import AbstractActionBase
class Info(AbstractActionBase):
"""Class for reporting on Podman Service."""
@classmethod
def subparser(cls, parent):
"""Add Info command to parent parser."""
parser = parent.add_parser(
'info', help='report info on podman service')
parser.add_argument(
'--format',
choices=('json', 'yaml'),
help="Alter the output for a format like 'json' or 'yaml'."
" (default: yaml)")
parser.set_defaults(class_=cls, method='info')
def info(self):
"""Report on Podman Service."""
try:
info = self.client.system.info()
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
else:
if self._args.format == 'json':
print(json.dumps(info._asdict(), indent=2), flush=True)
else:
print(
yaml.dump(
dict(info._asdict()),
canonical=False,
default_flow_style=False),
flush=True)

View File

@ -1,89 +0,0 @@
"""Remote client command for inspecting podman objects."""
import json
import logging
import sys
import podman
from pypodman.lib import AbstractActionBase
class Inspect(AbstractActionBase):
"""Class for inspecting podman objects."""
@classmethod
def subparser(cls, parent):
"""Add Inspect command to parent parser."""
parser = parent.add_parser('inspect', help='inspect objects')
parser.add_argument(
'--type',
'-t',
choices=('all', 'container', 'image'),
default='all',
type=str.lower,
help='Type of object to inspect',
)
parser.add_flag(
'--size',
help='Display the total file size if the type is a container.')
parser.add_argument(
'objects',
nargs='+',
help='objects to inspect',
)
parser.set_defaults(class_=cls, method='inspect')
def _get_container(self, ident):
try:
logging.debug("Getting container %s", ident)
ctnr = self.client.containers.get(ident)
except podman.ContainerNotFound:
pass
else:
return ctnr.inspect()
def _get_image(self, ident):
try:
logging.debug("Getting image %s", ident)
img = self.client.images.get(ident)
except podman.ImageNotFound:
pass
else:
return img.inspect()
def inspect(self):
"""Inspect provided podman objects."""
output = []
try:
for ident in self._args.objects:
obj = None
if self._args.type in ('all', 'container'):
obj = self._get_container(ident)
if obj is None and self._args.type in ('all', 'image'):
obj = self._get_image(ident)
if obj is None:
if self._args.type == 'container':
msg = 'Container "{}" not found'.format(ident)
elif self._args.type == 'image':
msg = 'Image "{}" not found'.format(ident)
else:
msg = 'Object "{}" not found'.format(ident)
print(msg, file=sys.stderr, flush=True)
else:
fields = obj._asdict()
if not self._args.size:
try:
del fields['sizerootfs']
except KeyError:
pass
output.append(fields)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
else:
print(json.dumps(output, indent=2))

View File

@ -1,49 +0,0 @@
"""Remote client command for signaling podman containers."""
import sys
import podman
from pypodman.lib import AbstractActionBase, SignalAction
class Kill(AbstractActionBase):
"""Class for sending signal to main process in container."""
@classmethod
def subparser(cls, parent):
"""Add Kill command to parent parser."""
parser = parent.add_parser('kill', help='signal container')
parser.add_argument(
'--signal',
'-s',
action=SignalAction,
default=9,
help='Signal to send to the container. (default: %(default)s)')
parser.add_argument(
'containers',
nargs='+',
help='containers to signal',
)
parser.set_defaults(class_=cls, method='kill')
def kill(self):
"""Signal provided containers."""
try:
for ident in self._args.containers:
try:
ctnr = self.client.containers.get(ident)
ctnr.kill(self._args.signal)
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container "{}" not found'.format(e.name),
file=sys.stderr,
flush=True)
else:
print(ident)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1

View File

@ -1,61 +0,0 @@
"""Remote client command for retrieving container logs."""
import argparse
import logging
import sys
from collections import deque
import podman
from pypodman.lib import AbstractActionBase, PositiveIntAction
class Logs(AbstractActionBase):
"""Class for retrieving logs from container."""
@classmethod
def subparser(cls, parent):
"""Add Logs command to parent parser."""
parser = parent.add_parser('logs', help='retrieve logs from container')
parser.add_argument(
'--tail',
metavar='LINES',
action=PositiveIntAction,
help='Output the specified number of LINES at the end of the logs')
parser.add_argument(
'container',
nargs=1,
help='retrieve container logs',
)
parser.set_defaults(class_=cls, method='logs')
def __init__(self, args):
"""Construct Logs class."""
super().__init__(args)
def logs(self):
"""Retrieve logs from containers."""
try:
ident = self._args.container[0]
try:
logging.debug('Get container "%s" logs', ident)
ctnr = self.client.containers.get(ident)
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container "{}" not found'.format(e.name),
file=sys.stderr,
flush=True)
else:
if self._args.tail:
logs = iter(deque(ctnr.logs(), maxlen=self._args.tail))
else:
logs = ctnr.logs()
for line in logs:
sys.stdout.write(line)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1

View File

@ -1,78 +0,0 @@
"""Remote client command for retrieving mounts from containers."""
import sys
from collections import OrderedDict
import podman
from pypodman.lib import AbstractActionBase, Report, ReportColumn
class Mount(AbstractActionBase):
"""Class for retrieving mounts from container."""
@classmethod
def subparser(cls, parent):
"""Add mount command to parent parser."""
parser = parent.add_parser(
'mount', help='retrieve mounts from containers.')
super().subparser(parser)
parser.add_argument(
'containers',
nargs='*',
help='containers to list ports',
)
parser.set_defaults(class_=cls, method='mount')
def __init__(self, args):
"""Construct Mount class."""
super().__init__(args)
self.columns = OrderedDict({
'id':
ReportColumn('id', 'CONTAINER ID', 14),
'destination':
ReportColumn('destination', 'DESTINATION', 0)
})
def mount(self):
"""Retrieve mounts from containers."""
try:
ctnrs = []
if not self._args.containers:
ctnrs = self.client.containers.list()
else:
for ident in self._args.containers:
try:
ctnrs.append(self.client.containers.get(ident))
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container "{}" not found'.format(e.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
if not ctnrs:
print(
'Unable to find any containers.', file=sys.stderr, flush=True)
return 1
rows = list()
for ctnr in ctnrs:
details = ctnr.inspect()
rows.append({
'id': ctnr.id,
'destination': details.graphdriver['data']['mergeddir']
})
with Report(self.columns, heading=self._args.heading) as report:
report.layout(
rows, self.columns.keys(), truncate=self._args.truncate)
for row in rows:
report.row(**row)

View File

@ -1,43 +0,0 @@
"""Remote client command for pausing processes in containers."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Pause(AbstractActionBase):
"""Class for pausing processes in container."""
@classmethod
def subparser(cls, parent):
"""Add Pause command to parent parser."""
parser = parent.add_parser('pause', help='pause container processes')
parser.add_argument(
'containers',
nargs='+',
help='containers to pause',
)
parser.set_defaults(class_=cls, method='pause')
def pause(self):
"""Pause provided containers."""
try:
for ident in self._args.containers:
try:
ctnr = self.client.containers.get(ident)
ctnr.pause()
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container "{}" not found'.format(e.name),
file=sys.stderr,
flush=True)
else:
print(ident)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1

View File

@ -1,24 +0,0 @@
"""Provide subparsers for pod commands."""
from pypodman.lib.actions.pod.create_parser import CreatePod
from pypodman.lib.actions.pod.inspect_parser import InspectPod
from pypodman.lib.actions.pod.kill_parser import KillPod
from pypodman.lib.actions.pod.pause_parser import PausePod
from pypodman.lib.actions.pod.processes_parser import ProcessesPod
from pypodman.lib.actions.pod.remove_parser import RemovePod
from pypodman.lib.actions.pod.start_parser import StartPod
from pypodman.lib.actions.pod.stop_parser import StopPod
from pypodman.lib.actions.pod.top_parser import TopPod
from pypodman.lib.actions.pod.unpause_parser import UnpausePod
__all__ = [
'CreatePod',
'InspectPod',
'KillPod',
'PausePod',
'ProcessesPod',
'RemovePod',
'StartPod',
'StopPod',
'TopPod',
'UnpausePod',
]

View File

@ -1,76 +0,0 @@
"""Remote client command for creating pod."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class CreatePod(AbstractActionBase):
"""Implement Create Pod command."""
@classmethod
def subparser(cls, parent):
"""Add Pod Create command to parent parser."""
parser = parent.add_parser('create', help='create pod')
super().subparser(parser)
parser.add_argument(
'--cgroup-parent',
dest='cgroupparent',
type=str,
help='Path to cgroups under which the'
' cgroup for the pod will be created.')
parser.add_flag(
'--infra',
help='Create an infra container and associate it with the pod.')
parser.add_argument(
'-l',
'--label',
dest='labels',
action='append',
type=str,
help='Add metadata to a pod (e.g., --label=com.example.key=value)')
parser.add_argument(
'-n',
'--name',
dest='ident',
type=str,
help='Assign name to the pod')
parser.add_argument(
'--share',
choices=('ipc', 'net', 'pid', 'user', 'uts'),
help='Comma deliminated list of kernel namespaces to share')
parser.set_defaults(class_=cls, method='create')
# TODO: Add golang CLI arguments not included in API.
# parser.add_argument(
# '--infra-command',
# default='/pause',
# help='Command to run to start the infra container.'
# '(default: %(default)s)')
# parser.add_argument(
# '--infra-image',
# default='k8s.gcr.io/pause:3.1',
# help='Image to create for the infra container.'
# '(default: %(default)s)')
# parser.add_argument(
# '--podidfile',
# help='Write the pod ID to given file name on remote host')
def create(self):
"""Create Pod from given options."""
config = {}
for key in ('ident', 'cgroupparent', 'infra', 'labels', 'share'):
config[key] = self.opts.get(key)
try:
pod = self.client.pods.create(**config)
except podman.ErrorOccurred as ex:
sys.stdout.flush()
print(
'{}'.format(ex.reason).capitalize(),
file=sys.stderr,
flush=True)
else:
print(pod.id)

View File

@ -1,43 +0,0 @@
"""Remote client command for inspecting pods."""
import json
import sys
import podman
from pypodman.lib import AbstractActionBase
class InspectPod(AbstractActionBase):
"""Class for reporting on pods and their containers."""
@classmethod
def subparser(cls, parent):
"""Add Pod Inspect command to parent parser."""
parser = parent.add_parser(
'inspect',
help='configuration and state information about a given pod')
parser.add_argument('pod', nargs='+', help='pod(s) to inspect')
parser.set_defaults(class_=cls, method='inspect')
def inspect(self):
"""Report on provided pods."""
output = {}
try:
for ident in self._args.pod:
try:
pod = self.client.pods.get(ident)
except podman.PodNotFound:
print(
'Pod "{}" not found.'.format(ident),
file=sys.stdout,
flush=True)
output.update(pod.inspect()._asdict())
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
else:
print(json.dumps(output, indent=2))
return 0

View File

@ -1,57 +0,0 @@
"""Remote client command for signaling pods and their containers."""
import signal
import sys
import podman
from pypodman.lib import AbstractActionBase, SignalAction
from pypodman.lib import query_model as query_pods
class KillPod(AbstractActionBase):
"""Class for sending signal to processes in pod."""
@classmethod
def subparser(cls, parent):
"""Add Pod Kill command to parent parser."""
parser = parent.add_parser('kill', help='signal containers in pod')
parser.add_flag(
'--all',
'-a',
help='Sends signal to all pods.')
parser.add_argument(
'-s',
'--signal',
action=SignalAction,
default=9,
help='Signal to send to the pod. (default: %(default)s)')
parser.add_argument('pod', nargs='*', help='pod(s) to signal')
parser.set_defaults(class_=cls, method='kill')
def __init__(self, args):
"""Construct Pod Kill object."""
if args.all and args.pod:
raise ValueError('You may give a pod or use --all, but not both')
super().__init__(args)
def kill(self):
"""Signal provided pods."""
idents = None if self._args.all else self._args.pod
pods = query_pods(self.client.pods, idents)
for pod in pods:
try:
pod.kill(self._args.signal)
print(pod.id)
except podman.PodNotFound as ex:
print(
'Pod "{}" not found.'.format(ex.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as e:
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,49 +0,0 @@
"""Remote client command for pausing processes in pod."""
import sys
import podman
from pypodman.lib import AbstractActionBase
from pypodman.lib import query_model as query_pods
class PausePod(AbstractActionBase):
"""Class for pausing containers in pod."""
@classmethod
def subparser(cls, parent):
"""Add Pod Pause command to parent parser."""
parser = parent.add_parser('pause', help='pause containers in pod')
parser.add_flag(
'--all',
'-a',
help='Pause all pods.')
parser.add_argument('pod', nargs='*', help='pod(s) to pause.')
parser.set_defaults(class_=cls, method='pause')
def __init__(self, args):
"""Construct Pod Pause object."""
if args.all and args.pod:
raise ValueError('You may give a pod or use --all, but not both')
super().__init__(args)
def pause(self):
"""Pause containers in provided Pod."""
idents = None if self._args.all else self._args.pod
pods = query_pods(self.client.pods, idents)
for pod in pods:
try:
pod.pause()
print(pod.id)
except podman.PodNotFound as ex:
print(
'Pod "{}" not found'.format(ex.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as ex:
print(
'{}'.format(ex.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,94 +0,0 @@
"""Report on pod's containers' processes."""
import operator
from collections import OrderedDict
from pypodman.lib import AbstractActionBase, Report, ReportColumn
class ProcessesPod(AbstractActionBase):
"""Report on Pod's processes."""
@classmethod
def subparser(cls, parent):
"""Add Pod Ps command to parent parser."""
parser = parent.add_parser('ps', help='list processes of pod')
super().subparser(parser)
parser.add_flag(
'--ctr-names',
help='Include container name in the info field.')
parser.add_flag(
'--ctr-ids',
help='Include container ID in the info field.')
parser.add_flag(
'--ctr-status',
help='Include container status in the info field.')
parser.add_argument(
'--format',
choices=('json'),
help='Pretty-print containers to JSON')
parser.add_argument(
'--sort',
choices=('created', 'id', 'name', 'status', 'count'),
default='created',
type=str.lower,
help='Sort on given field. (default: %(default)s)')
parser.add_argument('--filter', help='Not Implemented')
parser.set_defaults(class_=cls, method='processes')
def __init__(self, args):
"""Construct ProcessesPod class."""
if args.sort == 'created':
args.sort = 'createdat'
elif args.sort == 'count':
args.sort = 'numberofcontainers'
super().__init__(args)
self.columns = OrderedDict({
'id':
ReportColumn('id', 'POD ID', 14),
'name':
ReportColumn('name', 'NAME', 30),
'status':
ReportColumn('status', 'STATUS', 8),
'numberofcontainers':
ReportColumn('numberofcontainers', 'NUMBER OF CONTAINERS', 0),
'info':
ReportColumn('info', 'CONTAINER INFO', 0),
})
def processes(self):
"""List pods."""
pods = sorted(
self.client.pods.list(), key=operator.attrgetter(self._args.sort))
if not pods:
return
rows = list()
for pod in pods:
fields = dict(pod)
if self._args.ctr_ids \
or self._args.ctr_names \
or self._args.ctr_status:
keys = ('id', 'name', 'status', 'info')
info = []
for ctnr in pod.containersinfo:
ctnr_info = []
if self._args.ctr_ids:
ctnr_info.append(ctnr['id'])
if self._args.ctr_names:
ctnr_info.append(ctnr['name'])
if self._args.ctr_status:
ctnr_info.append(ctnr['status'])
info.append("[ {} ]".format(" ".join(ctnr_info)))
fields.update({'info': " ".join(info)})
else:
keys = ('id', 'name', 'status', 'numberofcontainers')
rows.append(fields)
with Report(self.columns, heading=self._args.heading) as report:
report.layout(rows, keys, truncate=self._args.truncate)
for row in rows:
report.row(**row)

View File

@ -1,54 +0,0 @@
"""Remote client command for deleting pod and containers."""
import sys
import podman
from pypodman.lib import AbstractActionBase
from pypodman.lib import query_model as query_pods
class RemovePod(AbstractActionBase):
"""Class for removing pod and containers from storage."""
@classmethod
def subparser(cls, parent):
"""Add Pod Rm command to parent parser."""
parser = parent.add_parser('rm', help='Delete pod and container(s)')
parser.add_flag(
'--all',
'-a',
help='Remove all pods.')
parser.add_flag(
'--force',
'-f',
help='Stop and remove container(s) then delete pod.')
parser.add_argument(
'pod', nargs='*', help='Pod to remove. Or, use --all')
parser.set_defaults(class_=cls, method='remove')
def __init__(self, args):
"""Construct RemovePod object."""
if args.all and args.pod:
raise ValueError('You may give a pod or use --all, but not both')
super().__init__(args)
def remove(self):
"""Remove pod and container(s)."""
idents = None if self._args.all else self._args.pod
pods = query_pods(self.client.pods, idents)
for pod in pods:
try:
pod.remove(self._args.force)
print(pod.id)
except podman.PodNotFound as ex:
print(
'Pod "{}" not found.'.format(ex.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as ex:
print(
'{}'.format(ex.reason).capitalize,
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,50 +0,0 @@
"""Remote client command for restarting pod and container(s)."""
import sys
import podman
from pypodman.lib import AbstractActionBase
from pypodman.lib import query_model as query_pods
class RestartPod(AbstractActionBase):
"""Class for restarting containers in Pod."""
@classmethod
def subparser(cls, parent):
"""Add Pod Restart command to parent parser."""
parser = parent.add_parser('restart', help='restart containers in pod')
parser.add_flag(
'--all',
'-a',
help='Restart all pods.')
parser.add_argument(
'pod', nargs='*', help='Pod to restart. Or, use --all')
parser.set_defaults(class_=cls, method='restart')
def __init__(self, args):
"""Construct RestartPod object."""
if args.all and args.pod:
raise ValueError('You may give a pod or use --all, not both')
super().__init__(args)
def restart(self):
"""Restart pod and container(s)."""
idents = None if self._args.all else self._args.pod
pods = query_pods(self.client.pods, idents)
for pod in pods:
try:
pod.restart()
print(pod.id)
except podman.PodNotFound as ex:
print(
'Pod "{}" not found.'.format(ex.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as ex:
print(
'{}'.format(ex.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,45 +0,0 @@
"""Remote client command for starting pod and container(s)."""
import sys
import podman
from pypodman.lib import AbstractActionBase
from pypodman.lib import query_model as query_pods
class StartPod(AbstractActionBase):
"""Class for starting pod and container(s)."""
@classmethod
def subparser(cls, parent):
"""Add Pod Start command to parent parser."""
parser = parent.add_parser('start', help='start pod')
parser.add_flag(
'--all',
'-a',
help='Start all pods.')
parser.add_argument(
'pod', nargs='*', help='Pod to start. Or, use --all')
parser.set_defaults(class_=cls, method='start')
def __init__(self, args):
"""Construct StartPod object."""
if args.all and args.pod:
raise ValueError('You may give a pod or use --all, but not both')
super().__init__(args)
def start(self):
"""Start pod and container(s)."""
idents = None if self._args.all else self._args.pod
pods = query_pods(self.client.pods, idents)
for pod in pods:
try:
pod.start()
except podman.ErrorOccurred as ex:
print(
'{}'.format(ex.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,44 +0,0 @@
"""Remote client command for stopping pod and container(s)."""
import sys
import podman
from pypodman.lib import AbstractActionBase
from pypodman.lib import query_model as query_pods
class StopPod(AbstractActionBase):
"""Class for stopping pod and container(s)."""
@classmethod
def subparser(cls, parent):
"""Add Pod Stop command to parent parser."""
parser = parent.add_parser('stop', help='stop pod')
parser.add_flag(
'--all',
'-a',
help='Stop all pods.')
parser.add_argument(
'pod', nargs='*', help='Pod to stop. Or, use --all')
parser.set_defaults(class_=cls, method='stop')
def __init__(self, args):
"""Contruct StopPod object."""
if args.all and args.pod:
raise ValueError('You may give a pod or use --all, not both')
super().__init__(args)
def stop(self):
"""Stop pod and container(s)."""
idents = None if self._args.all else self._args.pod
pods = query_pods(self.client.pods, idents)
for pod in pods:
try:
pod.stop()
except podman.ErrorOccurred as ex:
print(
'{}'.format(ex.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,35 +0,0 @@
"""Remote client command for reporting on pod and container(s)."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class TopPod(AbstractActionBase):
"""Report on containers in Pod."""
@classmethod
def subparser(cls, parent):
"""Add Pod Top command to parent parser."""
parser = parent.add_parser('top', help='report on containers in pod')
parser.add_argument('pod', nargs=1, help='Pod to report on.')
parser.set_defaults(class_=cls, method='top')
def top(self):
"""Report on pod and container(s)."""
try:
for ident in self._args.pod:
pod = self.client.pods.get(ident)
print(pod.top())
except podman.PodNotFound as ex:
print(
'Pod "{}" not found.'.format(ex.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as ex:
print(
'{}'.format(ex.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,50 +0,0 @@
"""Remote client command for unpausing processes in pod."""
import sys
import podman
from pypodman.lib import AbstractActionBase
from pypodman.lib import query_model as query_pods
class UnpausePod(AbstractActionBase):
"""Class for unpausing containers in pod."""
@classmethod
def subparser(cls, parent):
"""Add Pod Unpause command to parent parser."""
parser = parent.add_parser('unpause', help='unpause pod')
parser.add_flag(
'--all',
'-a',
help='Unpause all pods.')
parser.add_argument(
'pod', nargs='*', help='Pod to unpause. Or, use --all')
parser.set_defaults(class_=cls, method='unpause')
def __init__(self, args):
"""Construct Pod Unpause class."""
if args.all and args.pod:
raise ValueError('You may give a pod or use --all, but not both')
super().__init__(args)
def unpause(self):
"""Unpause containers in provided Pod."""
idents = None if self._args.all else self._args.pod
pods = query_pods(self.client.pods, idents)
for pod in pods:
try:
pod.unpause()
print(pod.id)
except podman.PodNotFound as ex:
print(
'Pod "{}" not found'.format(ex.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as ex:
print(
'{}'.format(ex.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,36 +0,0 @@
"""Remote client command for pod subcommands."""
import inspect
import logging
import sys
from pypodman.lib import AbstractActionBase
# pylint: disable=wildcard-import
# pylint: disable=unused-wildcard-import
from .pod import *
class Pod(AbstractActionBase):
"""Class for creating a pod."""
@classmethod
def subparser(cls, parent):
"""Add Pod Create command to parent parser."""
pod_parser = parent.add_parser(
'pod',
help='pod commands.'
' For subcommands, see: %(prog)s pod --help')
subparser = pod_parser.add_subparsers()
# pull in plugin(s) code for each subcommand
for name, obj in inspect.getmembers(
sys.modules['pypodman.lib.actions.pod'],
predicate=inspect.isclass):
if hasattr(obj, 'subparser'):
try:
obj.subparser(subparser)
except NameError as e:
logging.critical(e)
logging.warning(
'See subparser configuration for Class "%s"', name)
sys.exit(3)

View File

@ -1,61 +0,0 @@
"""Remote client command for retrieving ports from containers."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Port(AbstractActionBase):
"""Class for retrieving ports from container."""
@classmethod
def subparser(cls, parent):
"""Add Port command to parent parser."""
parser = parent.add_parser(
'port', help='retrieve ports from containers')
parser.add_flag(
'--all',
'-a',
help='List all known port mappings for running containers')
parser.add_argument(
'containers',
nargs='*',
help='containers to list ports',
)
parser.set_defaults(class_=cls, method='port')
def __init__(self, args):
"""Construct Port class."""
if not args.all and not args.containers:
raise ValueError('You must supply at least one'
' container id or name, or --all.')
super().__init__(args)
def port(self):
"""Retrieve ports from containers."""
try:
ctnrs = []
if self._args.all:
ctnrs = self.client.containers.list()
else:
for ident in self._args.containers:
try:
ctnrs.append(self.client.containers.get(ident))
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container "{}" not found'.format(e.name),
file=sys.stderr,
flush=True)
for ctnr in ctnrs:
print("{}\n{}".format(ctnr.id, ctnr.ports))
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,80 +0,0 @@
"""Remote client commands dealing with containers."""
import operator
from collections import OrderedDict
import humanize
import podman
from pypodman.lib import AbstractActionBase, Report, ReportColumn
class Ps(AbstractActionBase):
"""Class for Container manipulation."""
@classmethod
def subparser(cls, parent):
"""Add Images command to parent parser."""
parser = parent.add_parser('ps', help='list containers')
super().subparser(parser)
parser.add_argument(
'--sort',
choices=('createdat', 'id', 'image', 'names', 'runningfor', 'size',
'status'),
default='createdat',
type=str.lower,
help=('Change sort ordered of displayed containers.'
' (default: %(default)s)'))
parser.set_defaults(class_=cls, method='list')
def __init__(self, args):
"""Construct Ps class."""
super().__init__(args)
self.columns = OrderedDict({
'id':
ReportColumn('id', 'CONTAINER ID', 12),
'image':
ReportColumn('image', 'IMAGE', 31),
'command':
ReportColumn('column', 'COMMAND', 20),
'createdat':
ReportColumn('createdat', 'CREATED', 12),
'status':
ReportColumn('status', 'STATUS', 10),
'ports':
ReportColumn('ports', 'PORTS', 0),
'names':
ReportColumn('names', 'NAMES', 18)
})
def list(self):
"""List containers."""
if self._args.all:
ictnrs = self.client.containers.list()
else:
ictnrs = filter(
lambda c: podman.FoldedString(c['status']) == 'running',
self.client.containers.list())
# TODO: Verify sorting on dates and size
ctnrs = sorted(ictnrs, key=operator.attrgetter(self._args.sort))
if not ctnrs:
return
rows = list()
for ctnr in ctnrs:
fields = dict(ctnr)
fields.update({
'command':
' '.join(ctnr.command),
'createdat':
humanize.naturaldate(podman.datetime_parse(ctnr.createdat)),
})
rows.append(fields)
with Report(self.columns, heading=self._args.heading) as report:
report.layout(
rows, self.columns.keys(), truncate=self._args.truncate)
for row in rows:
report.row(**row)

View File

@ -1,46 +0,0 @@
"""Remote client command for pulling images."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Pull(AbstractActionBase):
"""Class for retrieving images from repository."""
@classmethod
def subparser(cls, parent):
"""Add Pull command to parent parser."""
parser = parent.add_parser(
'pull',
help='retrieve image from repository',
)
parser.add_argument(
'targets',
nargs='+',
help='image id(s) to retrieve.',
)
parser.set_defaults(class_=cls, method='pull')
def __init__(self, args):
"""Construct Pull class."""
super().__init__(args)
def pull(self):
"""Retrieve image."""
for ident in self._args.targets:
try:
self.client.images.pull(ident)
print(ident)
except podman.ImageNotFound as e:
sys.stdout.flush()
print(
'Image {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)

View File

@ -1,50 +0,0 @@
"""Remote client command for pushing image elsewhere."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Push(AbstractActionBase):
"""Class for pushing images to repository."""
@classmethod
def subparser(cls, parent):
"""Add Push command to parent parser."""
parser = parent.add_parser(
'push',
help='push image elsewhere',
)
parser.add_flag(
'--tlsverify',
help='Require HTTPS and verify certificates when'
' contacting registries.')
parser.add_argument(
'image', nargs=1, help='name or id of image to push')
parser.add_argument(
'tag',
nargs=1,
help='destination image id',
)
parser.set_defaults(class_=cls, method='push')
def pull(self):
"""Store image elsewhere."""
try:
try:
img = self.client.images.get(self._args.image[0])
except podman.ImageNotFound as e:
sys.stdout.flush()
print(
'Image {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
else:
img.push(self._args.tag[0], tlsverify=self._args.tlsverify)
print(self._args.image[0])
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)

View File

@ -1,46 +0,0 @@
"""Remote client command for restarting containers."""
import logging
import sys
import podman
from pypodman.lib import AbstractActionBase, PositiveIntAction
class Restart(AbstractActionBase):
"""Class for Restarting containers."""
@classmethod
def subparser(cls, parent):
"""Add Restart command to parent parser."""
parser = parent.add_parser('restart', help='restart container(s)')
parser.add_argument(
'--timeout',
action=PositiveIntAction,
default=10,
help='Timeout to wait before forcibly stopping the container'
' (default: %(default)s seconds)')
parser.add_argument(
'targets', nargs='+', help='container id(s) to restart')
parser.set_defaults(class_=cls, method='restart')
def restart(self):
"""Restart container(s)."""
try:
for ident in self._args.targets:
try:
ctnr = self.client.containers.get(ident)
logging.debug('Restarting Container %s', ctnr.id)
ctnr.restart(timeout=self._args.timeout)
print(ident)
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)

View File

@ -1,41 +0,0 @@
"""Remote client command for deleting containers."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Rm(AbstractActionBase):
"""Class for removing containers from storage."""
@classmethod
def subparser(cls, parent):
"""Add Rm command to parent parser."""
parser = parent.add_parser('rm', help='delete container(s)')
parser.add_flag(
'--force',
'-f',
help='force delete of running container(s).')
parser.add_argument(
'targets', nargs='+', help='container id(s) to delete')
parser.set_defaults(class_=cls, method='remove')
def remove(self):
"""Remove container(s)."""
for ident in self._args.targets:
try:
ctnr = self.client.containers.get(ident)
ctnr.remove(self._args.force)
print(ident)
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)

View File

@ -1,40 +0,0 @@
"""Remote client command for deleting images."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Rmi(AbstractActionBase):
"""Class for removing images from storage."""
@classmethod
def subparser(cls, parent):
"""Add Rmi command to parent parser."""
parser = parent.add_parser('rmi', help='delete image(s)')
parser.add_flag(
'--force',
'-f',
help='force delete of image(s) and associated containers.')
parser.add_argument('targets', nargs='+', help='image id(s) to delete')
parser.set_defaults(class_=cls, method='remove')
def remove(self):
"""Remove image(s)."""
for ident in self._args.targets:
try:
img = self.client.images.get(ident)
img.remove(self._args.force)
print(ident)
except podman.ImageNotFound as e:
sys.stdout.flush()
print(
'Image {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)

View File

@ -1,73 +0,0 @@
"""Remote client command for run a command in a new container."""
import logging
import sys
import podman
from pypodman.lib import AbstractActionBase
from ._create_args import CreateArguments
class Run(AbstractActionBase):
"""Class for running a command in a container."""
@classmethod
def subparser(cls, parent):
"""Add Run command to parent parser."""
parser = parent.add_parser('run', help='Run container from image')
CreateArguments.add_arguments(parser)
parser.add_argument('image', nargs=1, help='source image id.')
parser.add_argument(
'command',
nargs=parent.REMAINDER,
help='command and args to run.',
)
parser.set_defaults(class_=cls, method='run')
def __init__(self, args):
"""Construct Run class."""
super().__init__(args)
if args.detach and args.rm:
raise ValueError('Incompatible options: --detach and --rm')
# image id used only on client
del self.opts['image']
def run(self):
"""Run container."""
for ident in self._args.image:
try:
try:
img = self.client.images.get(ident)
ctnr = img.container(**self.opts)
except podman.ImageNotFound as e:
sys.stdout.flush()
print(
'Image {} not found.'.format(e.name),
file=sys.stderr,
flush=True)
continue
else:
logging.debug('New container created "{}"'.format(ctnr.id))
if self._args.detach:
ctnr.start()
print(ctnr.id)
else:
ctnr.attach(eot=4)
ctnr.start()
print(ctnr.id)
if self._args.rm:
ctnr.remove(force=True)
except (BrokenPipeError, KeyboardInterrupt):
print('\nContainer "{}" disconnected.'.format(ctnr.id))
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'Run for container "{}" failed: {} {}'.format(
ctnr.id, repr(e), e.reason.capitalize()),
file=sys.stderr,
flush=True)

View File

@ -1,160 +0,0 @@
"""Remote client command for searching registries for an image."""
import argparse
import sys
from collections import OrderedDict
import podman
from pypodman.lib import (AbstractActionBase, PositiveIntAction, Report,
ReportColumn)
class FilterAction(argparse.Action):
"""Parse filter argument components."""
def __init__(self,
option_strings,
dest,
nargs=None,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar='FILTER'):
"""Create FilterAction object."""
help = (help or '') + (' (format: stars=##'
' or is-automated=[True|False]'
' or is-official=[True|False])')
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
"""
Convert and Validate input.
Note: side effects
1) self.dest value is set to subargument dest
2) new attribute self.dest + '_value' is created with 2nd value.
"""
opt, val = values.split('=', 1)
if opt == 'stars':
msg = ('{} option "stars" requires'
' a positive integer').format(self.dest)
try:
val = int(val)
except ValueError:
parser.error(msg)
if val < 0:
parser.error(msg)
elif opt == 'is-automated':
if val.capitalize() in ('True', 'False'):
val = bool(val)
else:
msg = ('{} option "is-automated"'
' must be True or False.'.format(self.dest))
parser.error(msg)
elif opt == 'is-official':
if val.capitalize() in ('True', 'False'):
val = bool(val)
else:
msg = ('{} option "is-official"'
' must be True or False.'.format(self.dest))
parser.error(msg)
else:
msg = ('{} only supports one of the following options:\n'
' stars, is-automated, or is-official').format(self.dest)
parser.error(msg)
setattr(namespace, self.dest, opt)
setattr(namespace, self.dest + '_value', val)
class Search(AbstractActionBase):
"""Class for searching registries for an image."""
@classmethod
def subparser(cls, parent):
"""Add Search command to parent parser."""
parser = parent.add_parser('search', help='search for images')
super().subparser(parser)
parser.add_argument(
'--filter',
'-f',
action=FilterAction,
help='Filter output based on conditions provided.')
parser.add_argument(
'--limit',
action=PositiveIntAction,
default=25,
help='Limit the number of results.'
' (default: %(default)s)')
parser.add_argument('term', nargs=1, help='search term for image')
parser.set_defaults(class_=cls, method='search')
def __init__(self, args):
"""Construct Search class."""
super().__init__(args)
self.columns = OrderedDict({
'name':
ReportColumn('name', 'NAME', 44),
'description':
ReportColumn('description', 'DESCRIPTION', 44),
'star_count':
ReportColumn('star_count', 'STARS', 5),
'is_official':
ReportColumn('is_official', 'OFFICIAL', 8),
'is_automated':
ReportColumn('is_automated', 'AUTOMATED', 9),
})
def search(self):
"""Search registries for image."""
try:
rows = list()
for entry in self.client.images.search(
self._args.term[0], limit=self._args.limit):
if self._args.filter == 'is-official':
if self._args.filter_value != entry.is_official:
continue
elif self._args.filter == 'is-automated':
if self._args.filter_value != entry.is_automated:
continue
elif self._args.filter == 'stars':
if self._args.filter_value > entry.star_count:
continue
fields = dict(entry._asdict())
status = '[OK]' if entry.is_official else ''
fields['is_official'] = status
status = '[OK]' if entry.is_automated else ''
fields['is_automated'] = status
if self._args.truncate:
fields.update({'name': entry.name[-44:]})
rows.append(fields)
with Report(self.columns, heading=self._args.heading) as report:
report.layout(
rows, self.columns.keys(), truncate=self._args.truncate)
for row in rows:
report.row(**row)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)

View File

@ -1,71 +0,0 @@
"""Remote client command for starting containers."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Start(AbstractActionBase):
"""Class for starting container."""
@classmethod
def subparser(cls, parent):
"""Add Start command to parent parser."""
parser = parent.add_parser('start', help='start container')
parser.add_flag(
'--attach',
'-a',
help="Attach container's STDOUT and STDERR.")
parser.add_argument(
'--detach-keys',
metavar='KEY(s)',
default=4,
help='Override the key sequence for detaching a container.'
' (format: a single character [a-Z] or ctrl-<value> where'
' <value> is one of: a-z, @, ^, [, , or _) (default: ^D)')
parser.add_flag(
'--interactive',
'-i',
help="Attach container's STDIN.")
# TODO: Implement sig-proxy
parser.add_flag(
'--sig-proxy',
help="Proxy received signals to the process."
)
parser.add_argument(
'containers',
nargs='+',
help='containers to start',
)
parser.set_defaults(class_=cls, method='start')
def start(self):
"""Start provided containers."""
stdin = sys.stdin if self.opts['interactive'] else None
stdout = sys.stdout if self.opts['attach'] else None
try:
for ident in self._args.containers:
try:
ctnr = self.client.containers.get(ident)
ctnr.attach(
eot=self.opts['detach_keys'],
stdin=stdin,
stdout=stdout)
ctnr.start()
except podman.ContainerNotFound as e:
sys.stdout.flush()
print(
'Container "{}" not found'.format(e.name),
file=sys.stderr,
flush=True)
else:
print(ident)
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
return 0

View File

@ -1,35 +0,0 @@
"""Remote client command for reporting on Podman service."""
import sys
import podman
from pypodman.lib import AbstractActionBase
class Version(AbstractActionBase):
"""Class for reporting on Podman Service."""
@classmethod
def subparser(cls, parent):
"""Add Version command to parent parser."""
parser = parent.add_parser(
'version', help='report version on podman service')
parser.set_defaults(class_=cls, method='version')
def version(self):
"""Report on Podman Service."""
try:
info = self.client.system.info()
except podman.ErrorOccurred as e:
sys.stdout.flush()
print(
'{}'.format(e.reason).capitalize(),
file=sys.stderr,
flush=True)
return 1
else:
version = info._asdict()['podman']
host = info._asdict()['host']
print("Version {}".format(version['podman_version']))
print("Go Version {}".format(version['go_version']))
print("Git Commit {}".format(version['git_commit']))
print("OS/Arch {}/{}".format(host["os"], host["arch"]))

View File

@ -1,247 +0,0 @@
"""
Supplimental argparse.Action converters and validaters.
The constructors are very verbose but remain for IDE support.
"""
import argparse
import copy
import os
import signal
# API defined by argparse.Action therefore shut up pylint
# pragma pylint: disable=redefined-builtin
# pragma pylint: disable=too-few-public-methods
# pragma pylint: disable=too-many-arguments
class ChangeAction(argparse.Action):
"""Convert and validate change argument."""
def __init__(self,
option_strings,
dest,
nargs=None,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar='OPT=VALUE'):
"""Create ChangeAction object."""
help = (help or '') + ('Apply change(s) to the new image.'
' May be given multiple times.')
if default is None:
default = []
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
"""Convert and Validate input."""
items = getattr(namespace, self.dest, None) or []
items = copy.copy(items)
choices = ('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD',
'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR')
opt, _ = values.split('=', 1)
if opt not in choices:
parser.error('Option "{}" is not supported by argument "{}",'
' valid options are: {}'.format(
opt, option_string, ', '.join(choices)))
items.append(values)
setattr(namespace, self.dest, items)
class SignalAction(argparse.Action):
"""Validate input as a signal."""
def __init__(self,
option_strings,
dest,
nargs=None,
const=None,
default=None,
type=str,
choices=None,
required=False,
help='The signal to send.'
' It may be given as a name or a number.',
metavar='SIGNAL'):
"""Create SignalAction object."""
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)
if hasattr(signal, "Signals"):
def _signal_number(signame):
cooked = 'SIG{}'.format(signame)
try:
return signal.Signals[cooked].value
except ValueError:
pass
else:
def _signal_number(signame):
cooked = 'SIG{}'.format(signame)
for n, v in sorted(signal.__dict__.items()):
if n != cooked:
continue
if n.startswith("SIG") and not n.startswith("SIG_"):
return v
self._signal_number = _signal_number
def __call__(self, parser, namespace, values, option_string=None):
"""Validate input is a signal for platform."""
if values.isdigit():
signum = int(values)
if signal.SIGRTMIN <= signum >= signal.SIGRTMAX:
raise ValueError('"{}" is not a valid signal. {}-{}'.format(
values, signal.SIGRTMIN, signal.SIGRTMAX))
else:
signum = self._signal_number(values)
if signum is None:
parser.error(
'"{}" is not a valid signal,'
' see your platform documentation.'.format(values))
setattr(namespace, self.dest, signum)
class UnitAction(argparse.Action):
"""Validate number given is positive integer, with optional suffix."""
def __init__(self,
option_strings,
dest,
nargs=None,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar='UNIT'):
"""Create UnitAction object."""
help = (help or metavar or dest)\
+ ' (format: <number>[<unit>], where unit = b, k, m or g)'
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
"""Validate input as a UNIT."""
try:
val = int(values)
except ValueError:
if not values[:-1].isdigit():
msg = ('{} must be a positive integer,'
' with optional suffix').format(option_string)
parser.error(msg)
if not values[-1] in ('b', 'k', 'm', 'g'):
msg = '{} only supports suffices of: b, k, m, g'.format(
option_string)
parser.error(msg)
else:
if val <= 0:
msg = '{} must be a positive integer'.format(option_string)
parser.error(msg)
setattr(namespace, self.dest, values)
class PositiveIntAction(argparse.Action):
"""Validate number given is positive integer."""
def __init__(self,
option_strings,
dest,
nargs=None,
const=None,
default=None,
type=int,
choices=None,
required=False,
help='Must be a positive integer.',
metavar=None):
"""Create PositiveIntAction object."""
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
"""Validate input."""
if values > 0:
setattr(namespace, self.dest, values)
return
msg = '{} must be a positive integer'.format(option_string)
parser.error(msg)
class PathAction(argparse.Action):
"""Expand user- and relative-paths."""
def __init__(self,
option_strings,
dest,
nargs=None,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar='PATH'):
"""Create PathAction object."""
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar)
def __call__(self, parser, namespace, values, option_string=None):
"""Resolve full path value on local filesystem."""
setattr(namespace, self.dest,
os.path.abspath(os.path.expanduser(values)))

View File

@ -1,255 +0,0 @@
"""Parse configuration while building subcommands."""
import argparse
import getpass
import inspect
import logging
import os
import shutil
import sys
from contextlib import suppress
from pathlib import Path
import pkg_resources
import pytoml
from .parser_actions import PathAction, PositiveIntAction
# TODO: setup.py and obtain __version__ from rpm.spec
try:
__version__ = pkg_resources.get_distribution('pypodman').version
except Exception: # pylint: disable=broad-except
__version__ = '0.0.0'
class HelpFormatter(argparse.RawDescriptionHelpFormatter):
"""Set help width to screen size."""
def __init__(self, *args, **kwargs):
"""Construct HelpFormatter using screen width."""
if 'width' not in kwargs:
try:
size = shutil.get_terminal_size()
kwargs['width'] = size.columns
except Exception: # pylint: disable=broad-except
kwargs['width'] = 80
super().__init__(*args, **kwargs)
class PodmanArgumentParser(argparse.ArgumentParser):
"""Default remote podman configuration."""
def __init__(self, **kwargs):
"""Construct the parser."""
kwargs['add_help'] = True
kwargs['description'] = ('Portable and simple management'
' tool for containers and images')
kwargs['formatter_class'] = HelpFormatter
super().__init__(**kwargs)
def add_flag(self, *args, **kwargs):
"""Add flag to parser."""
flags = [a for a in args if a[0] in self.prefix_chars]
dest = flags[0].lstrip(self.prefix_chars)
no_flag = '{0}{0}no-{1}'.format(self.prefix_chars, dest)
group = self.add_mutually_exclusive_group(required=False)
group.add_argument(*flags, action='store_true', dest=dest, **kwargs)
group.add_argument(no_flag, action='store_false', dest=dest, **kwargs)
default = kwargs.get('default', False)
self.set_defaults(**{dest: default})
def initialize_parser(self):
"""Initialize parser without causing recursion meltdown."""
self.add_argument(
'--version',
action='version',
version='%(prog)s v. ' + __version__)
self.add_argument(
'--log-level',
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
default='WARNING',
type=str.upper,
help='set logging level for events. (default: %(default)s)',
)
self.add_argument(
'--run-dir',
metavar='DIRECTORY',
help=('directory to place local socket bindings.'
' (default: XDG_RUNTIME_DIR/pypodman)'))
self.add_argument(
'--username',
'-l',
help='Authenicating user on remote host. (default: {})'.format(
getpass.getuser()))
self.add_argument(
'--host', help='name of remote host. (default: None)')
self.add_argument(
'--port',
'-p',
action=PositiveIntAction,
help='port for ssh tunnel to remote host. (default: 22)')
self.add_argument(
'--remote-socket-path',
metavar='PATH',
help=('path of podman socket on remote host'
' (default: /run/podman/io.podman)'))
self.add_argument(
'--identity-file',
'-i',
action=PathAction,
help='path to ssh identity file. (default: ~user/.ssh/id_dsa)')
self.add_argument(
'--config-home',
metavar='DIRECTORY',
action=PathAction,
help=('home of configuration "pypodman.conf".'
' (default: XDG_CONFIG_HOME/pypodman)'))
actions_parser = self.add_subparsers(
dest='subparser_name', help='commands')
# For create/exec/run: don't process options intended for subcommand
actions_parser.REMAINDER = argparse.REMAINDER
# import buried here to prevent import loops
import pypodman.lib.actions # pylint: disable=cyclic-import
assert pypodman.lib.actions
# pull in plugin(s) code for each subcommand
for name, obj in inspect.getmembers(
sys.modules['pypodman.lib.actions'],
predicate=inspect.isclass):
if hasattr(obj, 'subparser'):
try:
obj.subparser(actions_parser)
except NameError as e:
logging.critical(e)
logging.warning(
'See subparser configuration for Class "%s"', name)
sys.exit(3)
def parse_args(self, args=None, namespace=None):
"""Parse command line arguments, backed by env var and config_file."""
self.initialize_parser()
cooked = super().parse_args(args, namespace)
return self.resolve_configuration(cooked)
def resolve_configuration(self, args):
"""Find and fill in any arguments not passed on command line."""
args.xdg_runtime_dir = os.environ.get('XDG_RUNTIME_DIR', '/tmp')
args.xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
os.path.expanduser('~/.config'))
args.xdg_config_dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg')
# Configuration file(s) are optional,
# required arguments may be provided elsewhere
config = {'default': {}}
dirs = args.xdg_config_dirs.split(':')
dirs.extend((args.xdg_config_home, args.config_home))
for dir_ in dirs:
if dir_ is None:
continue
with suppress(OSError):
cnf = Path(dir_, 'pypodman', 'pypodman.conf')
with cnf.open() as stream:
config.update(pytoml.load(stream))
def reqattr(name, value):
"""Raise an error if value is unset."""
if value:
setattr(args, name, value)
return value
return self.error(
'Required argument "{}" is not configured.'.format(name))
reqattr(
'run_dir',
getattr(args, 'run_dir')
or os.environ.get('PODMAN_RUN_DIR')
or config['default'].get('run_dir')
or str(Path(args.xdg_runtime_dir, 'pypodman'))
) # yapf: disable
setattr(
args,
'host',
getattr(args, 'host')
or os.environ.get('PODMAN_HOST')
or config['default'].get('host')
) # yapf:disable
reqattr(
'username',
getattr(args, 'username')
or os.environ.get('PODMAN_USER')
or config['default'].get('username')
or os.environ.get('USER')
or os.environ.get('LOGNAME')
or getpass.getuser()
) # yapf:disable
reqattr(
'port',
getattr(args, 'port')
or os.environ.get('PODMAN_PORT')
or config['default'].get('port', None)
or 22
) # yapf:disable
reqattr(
'remote_socket_path',
getattr(args, 'remote_socket_path')
or os.environ.get('PODMAN_REMOTE_SOCKET_PATH')
or config['default'].get('remote_socket_path')
or '/run/podman/io.podman'
) # yapf:disable
reqattr(
'log_level',
getattr(args, 'log_level')
or os.environ.get('PODMAN_LOG_LEVEL')
or config['default'].get('log_level')
or logging.WARNING
) # yapf:disable
setattr(
args,
'identity_file',
getattr(args, 'identity_file')
or os.environ.get('PODMAN_IDENTITY_FILE')
or config['default'].get('identity_file')
or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.username))
) # yapf:disable
if not os.path.isfile(args.identity_file):
args.identity_file = None
if args.host:
args.local_socket_path = str(Path(args.run_dir, 'podman.socket'))
else:
args.local_socket_path = args.remote_socket_path
args.local_uri = 'unix:{}'.format(args.local_socket_path)
if args.host:
components = ['ssh://', args.username, '@', args.host]
if args.port:
components.extend((':', str(args.port)))
components.append(args.remote_socket_path)
args.remote_uri = ''.join(components)
return args
def exit(self, status=0, message=None):
"""Capture message and route to logger."""
if message:
log = logging.info if status == 0 else logging.error
log(message)
super().exit(status)
def error(self, message):
"""Capture message and route to logger."""
logging.error('%s: %s', self.prog, message)
logging.error("Try '%s --help' for more information.", self.prog)
super().exit(2)

View File

@ -1,87 +0,0 @@
"""Report Manager."""
import string
import sys
from collections import namedtuple
class ReportFormatter(string.Formatter):
"""Custom formatter to default missing keys to '<none>'."""
def get_value(self, key, args, kwargs):
"""Map missing key to value '<none>'."""
try:
if isinstance(key, int):
return args[key]
else:
return kwargs[key]
except KeyError:
return '<none>'
class ReportColumn(namedtuple('ReportColumn', 'key display width default')):
"""Hold attributes of output column."""
__slots__ = ()
def __new__(cls, key, display, width, default=None):
"""Add defaults for attributes."""
return super(ReportColumn, cls).__new__(cls, key, display, width,
default)
class Report():
"""Report Manager."""
def __init__(self, columns, heading=True, epilog=None, file=sys.stdout):
"""Construct Report.
columns is a mapping for named fields to column headings.
headers True prints headers on table.
epilog will be printed when the report context is closed.
"""
self._columns = columns
self._file = file
self._format_string = None
self._formatter = ReportFormatter()
self._heading = heading
self.epilog = epilog
def row(self, **fields):
"""Print row for report."""
if self._heading:
hdrs = {k: v.display for (k, v) in self._columns.items()}
print(
self._formatter.format(self._format_string, **hdrs),
flush=True,
file=self._file,
)
self._heading = False
fields = {k: str(v) for k, v in fields.items()}
print(self._formatter.format(self._format_string, **fields))
def __enter__(self):
"""Return `self` upon entering the runtime context."""
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Leave Report context and print epilog if provided."""
if self.epilog:
print(self.epilog, flush=True, file=self._file)
def layout(self, iterable, keys, truncate=True):
"""Use data and headings build format for table to fit."""
fmt = []
for key in keys:
slice_ = [str(i.get(key, '')) for i in iterable]
data_len = len(max(slice_, key=len))
info = self._columns.get(key,
ReportColumn(key, key.upper(), data_len))
display_len = max(data_len, len(info.display))
if truncate and info.width != 0:
display_len = info.width
fmt.append('{{{0}:{1}.{1}}}'.format(key, display_len))
self._format_string = ' '.join(fmt)

View File

@ -1,81 +0,0 @@
"""Remote podman client."""
import logging
import os
import sys
from subprocess import CalledProcessError
from pypodman.lib import PodmanArgumentParser
def main():
"""Entry point."""
# Setup logging so we use stderr and can change logging level later
# Do it now before there is any chance of a default setup hardcoding crap.
log = logging.getLogger()
fmt = logging.Formatter('%(asctime)s | %(levelname)-8s | %(message)s',
'%Y-%m-%d %H:%M:%S %Z')
stderr = logging.StreamHandler(stream=sys.stderr)
stderr.setFormatter(fmt)
log.addHandler(stderr)
log.setLevel(logging.WARNING)
parser = PodmanArgumentParser()
args = parser.parse_args()
log.setLevel(args.log_level)
logging.debug(
'Logging initialized at level %s',
logging.getLevelName(logging.getLogger().getEffectiveLevel()))
def want_tb():
"""Add traceback when logging events."""
return log.getEffectiveLevel() == logging.DEBUG
try:
if not os.path.exists(args.run_dir):
os.makedirs(args.run_dir)
except PermissionError as e:
logging.critical(e, exc_info=want_tb())
sys.exit(6)
# class_(args).method() are set by the sub-command's parser
returncode = None
try:
obj = args.class_(args)
except AttributeError:
parser.print_help(sys.stderr)
sys.exit(1)
except ValueError as e:
print(e, file=sys.stderr, flush=True)
sys.exit(1)
except Exception as e: # pylint: disable=broad-except
logging.critical(repr(e), exc_info=want_tb())
logging.warning('See subparser "%s" configuration.',
args.subparser_name)
sys.exit(5)
try:
returncode = getattr(obj, args.method)()
except KeyboardInterrupt:
pass
except AttributeError as e:
logging.critical(e, exc_info=want_tb())
logging.warning('See subparser "%s" configuration.',
args.subparser_name)
returncode = 3
except (
CalledProcessError,
ConnectionError,
ConnectionRefusedError,
ConnectionResetError,
TimeoutError,
) as e:
logging.critical(e, exc_info=want_tb())
logging.info('Review connection arguments for correctness.')
returncode = 4
return 0 if returncode is None else returncode
if __name__ == '__main__':
sys.exit(main())

View File

@ -1,23 +0,0 @@
from __future__ import absolute_import
import unittest
from report import Report, ReportColumn
class TestReport(unittest.TestCase):
def setUp(self):
pass
def test_report_column(self):
rc = ReportColumn('k', 'v', 3)
self.assertEqual(rc.key, 'k')
self.assertEqual(rc.display, 'v')
self.assertEqual(rc.width, 3)
self.assertIsNone(rc.default)
rc = ReportColumn('k', 'v', 3, 'd')
self.assertEqual(rc.key, 'k')
self.assertEqual(rc.display, 'v')
self.assertEqual(rc.width, 3)
self.assertEqual(rc.default, 'd')

View File

@ -1,5 +0,0 @@
humanize
podman
pytoml
PyYAML
setuptools>=39

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python
import os
from setuptools import find_packages, setup
root = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(root, 'README.md')) as me:
readme = me.read()
with open(os.path.join(root, 'requirements.txt')) as r:
requirements = r.read().splitlines()
setup(
name='pypodman',
version=os.environ.get('PODMAN_VERSION', '0.0.0'),
description='A client for communicating with a Podman server',
author_email='jhonce@redhat.com',
author='Jhon Honce',
license='Apache Software License',
long_description=readme,
entry_points={'console_scripts': [
'pypodman = pypodman.main:main',
]},
include_package_data=True,
install_requires=requirements,
packages=find_packages(exclude=['test']),
python_requires='>=3',
zip_safe=True,
url='http://github.com/containers/libpod',
keywords='varlink libpod podman pypodman',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: Apache Software License',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Programming Language :: Python :: 3.4',
'Topic :: System :: Systems Administration',
'Topic :: Utilities',
])

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