Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
|
b500bcc7c3 | |
|
aaedfb82df | |
|
ff0a8a9711 |
127
.cirrus.yml
127
.cirrus.yml
|
@ -1,127 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
env:
|
|
||||||
DEST_BRANCH: "main"
|
|
||||||
GOPATH: "/var/tmp/go"
|
|
||||||
GOBIN: "${GOPATH}/bin"
|
|
||||||
GOCACHE: "${GOPATH}/cache"
|
|
||||||
GOSRC: "${GOPATH}/src/github.com/containers/podman"
|
|
||||||
CIRRUS_WORKING_DIR: "${GOPATH}/src/github.com/containers/podman-py"
|
|
||||||
SCRIPT_BASE: "./contrib/cirrus"
|
|
||||||
CIRRUS_SHELL: "/bin/bash"
|
|
||||||
HOME: "/root" # not set by default
|
|
||||||
|
|
||||||
####
|
|
||||||
#### Cache-image names to test with (double-quotes around names are critical)
|
|
||||||
####
|
|
||||||
FEDORA_NAME: "fedora-35"
|
|
||||||
PRIOR_FEDORA_NAME: "fedora-34"
|
|
||||||
UBUNTU_NAME: "ubuntu-2110"
|
|
||||||
|
|
||||||
# Google-cloud VM Images
|
|
||||||
IMAGE_SUFFIX: "c4764556961513472"
|
|
||||||
FEDORA_CACHE_IMAGE_NAME: "fedora-podman-py-${IMAGE_SUFFIX}"
|
|
||||||
|
|
||||||
|
|
||||||
gcp_credentials: ENCRYPTED[0c639039cdd3a9a93fac7746ea1bf366d432e5ff3303bf293e64a7ff38dee85fd445f71625fa5626dc438be2b8efe939]
|
|
||||||
|
|
||||||
|
|
||||||
# Default VM to use unless set or modified by task
|
|
||||||
gce_instance:
|
|
||||||
image_project: "libpod-218412"
|
|
||||||
zone: "us-central1-c" # Required by Cirrus for the time being
|
|
||||||
cpu: 2
|
|
||||||
memory: "4Gb"
|
|
||||||
disk: 200 # Required for performance reasons
|
|
||||||
image_name: "${FEDORA_CACHE_IMAGE_NAME}"
|
|
||||||
|
|
||||||
gating_task:
|
|
||||||
name: "Gating test"
|
|
||||||
alias: gating
|
|
||||||
|
|
||||||
# Only run this on PRs, never during post-merge testing. This is also required
|
|
||||||
# for proper setting of EPOCH_TEST_COMMIT value, required by validation tools.
|
|
||||||
only_if: $CIRRUS_PR != ""
|
|
||||||
|
|
||||||
timeout_in: 20m
|
|
||||||
|
|
||||||
env:
|
|
||||||
PATH: ${PATH}:${GOPATH}/bin
|
|
||||||
|
|
||||||
script:
|
|
||||||
- make
|
|
||||||
- make lint
|
|
||||||
|
|
||||||
test_task:
|
|
||||||
name: "Test on $FEDORA_NAME"
|
|
||||||
alias: test
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- gating
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ${SCRIPT_BASE}/enable_ssh.sh
|
|
||||||
- ${SCRIPT_BASE}/enable_podman.sh
|
|
||||||
- ${SCRIPT_BASE}/test.sh
|
|
||||||
|
|
||||||
latest_task:
|
|
||||||
name: "Test Podman main on $FEDORA_NAME"
|
|
||||||
alias: latest
|
|
||||||
allow_failures: true
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- gating
|
|
||||||
|
|
||||||
env:
|
|
||||||
PATH: ${PATH}:${GOPATH}/bin
|
|
||||||
|
|
||||||
script:
|
|
||||||
- ${SCRIPT_BASE}/enable_ssh.sh
|
|
||||||
- ${SCRIPT_BASE}/build_podman.sh
|
|
||||||
- ${SCRIPT_BASE}/enable_podman.sh
|
|
||||||
- ${SCRIPT_BASE}/test.sh
|
|
||||||
|
|
||||||
# This task is critical. It updates the "last-used by" timestamp stored
|
|
||||||
# in metadata for all VM images. This mechanism functions in tandem with
|
|
||||||
# an out-of-band pruning operation to remove disused VM images.
|
|
||||||
meta_task:
|
|
||||||
alias: meta
|
|
||||||
name: "VM img. keepalive"
|
|
||||||
|
|
||||||
container: &smallcontainer
|
|
||||||
image: "quay.io/libpod/imgts:$IMAGE_SUFFIX"
|
|
||||||
cpu: 1
|
|
||||||
memory: 1
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMGNAMES: ${FEDORA_CACHE_IMAGE_NAME}
|
|
||||||
BUILDID: "${CIRRUS_BUILD_ID}"
|
|
||||||
REPOREF: "${CIRRUS_REPO_NAME}"
|
|
||||||
GCPJSON: ENCRYPTED[e8a53772eff6e86bf6b99107b6e6ee3216e2ca00c36252ae3bd8cb29d9b903ffb2e1a1322ea810ca251b04f833b8f8d9]
|
|
||||||
GCPNAME: ENCRYPTED[fb878daf188d35c2ed356dc777267d99b59863ff3abf0c41199d562fca50ba0668fdb0d87e109c9eaa2a635d2825feed]
|
|
||||||
GCPPROJECT: "libpod-218412"
|
|
||||||
|
|
||||||
clone_script: &noop mkdir -p $CIRRUS_WORKING_DIR
|
|
||||||
script: /usr/local/bin/entrypoint.sh
|
|
||||||
|
|
||||||
# Status aggregator for all tests. This task simply ensures a defined
|
|
||||||
# set of tasks all passed, and allows confirming that based on the status
|
|
||||||
# of this task.
|
|
||||||
success_task:
|
|
||||||
name: "Total Success"
|
|
||||||
alias: success
|
|
||||||
|
|
||||||
# N/B: ALL tasks must be listed here, minus their '_task' suffix.
|
|
||||||
depends_on:
|
|
||||||
- meta
|
|
||||||
- gating
|
|
||||||
- test
|
|
||||||
- latest
|
|
||||||
container:
|
|
||||||
image: quay.io/libpod/alpine:latest
|
|
||||||
cpu: 1
|
|
||||||
memory: 1
|
|
||||||
env:
|
|
||||||
CIRRUS_SHELL: "/bin/sh"
|
|
||||||
clone_script: *noop
|
|
||||||
script: *noop
|
|
|
@ -11,7 +11,9 @@ ignore=CVS,docs
|
||||||
|
|
||||||
# Add files or directories matching the regex patterns to the blacklist. The
|
# Add files or directories matching the regex patterns to the blacklist. The
|
||||||
# regex matches against base names, not paths.
|
# regex matches against base names, not paths.
|
||||||
ignore-patterns=test_.*
|
# ignore-patterns=test_.*
|
||||||
|
|
||||||
|
ignore-paths=^podman/tests/.*$
|
||||||
|
|
||||||
# Python code to execute, usually for sys.path manipulation such as
|
# Python code to execute, usually for sys.path manipulation such as
|
||||||
# pygtk.require().
|
# pygtk.require().
|
||||||
|
|
22
Makefile
22
Makefile
|
@ -8,25 +8,23 @@ DESTDIR ?=
|
||||||
EPOCH_TEST_COMMIT ?= $(shell git merge-base $${DEST_BRANCH:-main} HEAD)
|
EPOCH_TEST_COMMIT ?= $(shell git merge-base $${DEST_BRANCH:-main} HEAD)
|
||||||
HEAD ?= HEAD
|
HEAD ?= HEAD
|
||||||
|
|
||||||
export PODMAN_VERSION ?= "3.2.0"
|
export PODMAN_VERSION ?= "4.0.1"
|
||||||
|
|
||||||
.PHONY: podman
|
.PHONY: podman
|
||||||
podman:
|
podman:
|
||||||
rm dist/* || :
|
rm dist/* || :
|
||||||
python -m pip install --user -r requirements.txt
|
$(PYTHON) -m pip install --user -r requirements.txt
|
||||||
PODMAN_VERSION=$(PODMAN_VERSION) \
|
PODMAN_VERSION=$(PODMAN_VERSION) \
|
||||||
$(PYTHON) setup.py sdist bdist bdist_wheel
|
$(PYTHON) setup.py sdist bdist bdist_wheel
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint:
|
lint: tox
|
||||||
$(PYTHON) -m pylint podman || exit $$(($$? % 4));
|
$(PYTHON) -m tox -e black,pylint
|
||||||
|
|
||||||
.PHONY: tests
|
.PHONY: tests
|
||||||
tests:
|
tests: tox
|
||||||
python -m pip install --user -r test-requirements.txt
|
# see tox.ini for environment variable settings
|
||||||
DEBUG=1 coverage run -m unittest discover -s podman/tests
|
$(PYTHON) -m tox -e pylint,coverage,py36,py38,py39,py310
|
||||||
coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=.tox/* \
|
|
||||||
--omit=/usr/lib/* --omit=*/lib/python*
|
|
||||||
|
|
||||||
.PHONY: unittest
|
.PHONY: unittest
|
||||||
unittest:
|
unittest:
|
||||||
|
@ -38,6 +36,12 @@ integration:
|
||||||
coverage run -m unittest discover -s podman/tests/integration
|
coverage run -m unittest discover -s podman/tests/integration
|
||||||
coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=.tox/* --omit=/usr/lib/*
|
coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=.tox/* --omit=/usr/lib/*
|
||||||
|
|
||||||
|
.PHONY: tox
|
||||||
|
tox:
|
||||||
|
-dnf install -y python3 python3.6 python3.8 python3.9
|
||||||
|
# ensure tox is available. It will take care of other testing requirements
|
||||||
|
$(PYTHON) -m pip install --user tox
|
||||||
|
|
||||||
.PHONY: test-release
|
.PHONY: test-release
|
||||||
test-release: SOURCE = $(shell find dist -regex '.*/podman-[0-9][0-9\.]*.tar.gz' -print)
|
test-release: SOURCE = $(shell find dist -regex '.*/podman-[0-9][0-9\.]*.tar.gz' -print)
|
||||||
test-release:
|
test-release:
|
||||||
|
|
|
@ -2,13 +2,9 @@
|
||||||
|
|
||||||
set -xeo pipefail
|
set -xeo pipefail
|
||||||
|
|
||||||
mkdir -p "$GOPATH/src/github.com/containers/"
|
systemctl stop podman.socket || :
|
||||||
cd "$GOPATH/src/github.com/containers/"
|
|
||||||
|
|
||||||
systemctl stop podman.socket ||:
|
|
||||||
dnf erase podman -y
|
dnf erase podman -y
|
||||||
git clone https://github.com/containers/podman.git
|
dnf copr enable rhcontainerbot/podman-next -y
|
||||||
|
dnf install podman -y
|
||||||
|
|
||||||
cd podman
|
|
||||||
make binaries
|
|
||||||
make install PREFIX=/usr
|
|
||||||
|
|
|
@ -2,6 +2,4 @@
|
||||||
|
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
make tests
|
make tests
|
||||||
|
|
|
@ -84,7 +84,7 @@ def create_tar(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Workaround https://bugs.python.org/issue32713. Fixed in Python 3.7
|
# Workaround https://bugs.python.org/issue32713. Fixed in Python 3.7
|
||||||
if info.mtime < 0 or info.mtime > 8 ** 11 - 1:
|
if info.mtime < 0 or info.mtime > 8**11 - 1:
|
||||||
info.mtime = int(info.mtime)
|
info.mtime = int(info.mtime)
|
||||||
|
|
||||||
# do not leak client information to service
|
# do not leak client information to service
|
||||||
|
@ -97,9 +97,8 @@ def create_tar(
|
||||||
return info
|
return info
|
||||||
|
|
||||||
if name is None:
|
if name is None:
|
||||||
name = tempfile.NamedTemporaryFile(
|
# pylint: disable=consider-using-with
|
||||||
prefix="podman_context", suffix=".tar"
|
name = tempfile.NamedTemporaryFile(prefix="podman_context", suffix=".tar")
|
||||||
) # pylint: disable=consider-using-with
|
|
||||||
else:
|
else:
|
||||||
name = pathlib.Path(name)
|
name = pathlib.Path(name)
|
||||||
|
|
||||||
|
|
|
@ -884,7 +884,6 @@ elif _geqv_defined:
|
||||||
return collections.deque(*args, **kwds)
|
return collections.deque(*args, **kwds)
|
||||||
return _generic_new(collections.deque, cls, *args, **kwds)
|
return _generic_new(collections.deque, cls, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class Deque(
|
class Deque(
|
||||||
|
@ -912,7 +911,6 @@ elif hasattr(contextlib, 'AbstractContextManager'):
|
||||||
):
|
):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class ContextManager(typing.Generic[T_co]):
|
class ContextManager(typing.Generic[T_co]):
|
||||||
|
@ -994,7 +992,6 @@ elif _geqv_defined:
|
||||||
return collections.defaultdict(*args, **kwds)
|
return collections.defaultdict(*args, **kwds)
|
||||||
return _generic_new(collections.defaultdict, cls, *args, **kwds)
|
return _generic_new(collections.defaultdict, cls, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class DefaultDict(
|
class DefaultDict(
|
||||||
|
@ -1032,7 +1029,6 @@ elif _geqv_defined:
|
||||||
return collections.OrderedDict(*args, **kwds)
|
return collections.OrderedDict(*args, **kwds)
|
||||||
return _generic_new(collections.OrderedDict, cls, *args, **kwds)
|
return _generic_new(collections.OrderedDict, cls, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class OrderedDict(
|
class OrderedDict(
|
||||||
|
@ -1073,7 +1069,6 @@ elif (3, 5, 0) <= sys.version_info[:3] <= (3, 5, 1):
|
||||||
return collections.Counter(*args, **kwds)
|
return collections.Counter(*args, **kwds)
|
||||||
return _generic_new(collections.Counter, cls, *args, **kwds)
|
return _generic_new(collections.Counter, cls, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
elif _geqv_defined:
|
elif _geqv_defined:
|
||||||
|
|
||||||
class Counter(
|
class Counter(
|
||||||
|
@ -1090,7 +1085,6 @@ elif _geqv_defined:
|
||||||
return collections.Counter(*args, **kwds)
|
return collections.Counter(*args, **kwds)
|
||||||
return _generic_new(collections.Counter, cls, *args, **kwds)
|
return _generic_new(collections.Counter, cls, *args, **kwds)
|
||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
class Counter(
|
class Counter(
|
||||||
|
@ -1353,9 +1347,7 @@ elif HAVE_PROTOCOLS and not PEP_560:
|
||||||
bases = tuple(b for b in bases if b is not Generic)
|
bases = tuple(b for b in bases if b is not Generic)
|
||||||
namespace.update({'__origin__': origin, '__extra__': extra})
|
namespace.update({'__origin__': origin, '__extra__': extra})
|
||||||
self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, _root=True)
|
self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, _root=True)
|
||||||
super(GenericMeta, self).__setattr__(
|
super(GenericMeta, self).__setattr__('_gorg', self if not origin else _gorg(origin))
|
||||||
'_gorg', self if not origin else _gorg(origin)
|
|
||||||
)
|
|
||||||
self.__parameters__ = tvars
|
self.__parameters__ = tvars
|
||||||
self.__args__ = (
|
self.__args__ = (
|
||||||
tuple(
|
tuple(
|
||||||
|
@ -1479,9 +1471,7 @@ elif HAVE_PROTOCOLS and not PEP_560:
|
||||||
if not isinstance(params, tuple):
|
if not isinstance(params, tuple):
|
||||||
params = (params,)
|
params = (params,)
|
||||||
if not params and _gorg(self) is not Tuple:
|
if not params and _gorg(self) is not Tuple:
|
||||||
raise TypeError(
|
raise TypeError("Parameter list to %s[...] cannot be empty" % self.__qualname__)
|
||||||
"Parameter list to %s[...] cannot be empty" % self.__qualname__
|
|
||||||
)
|
|
||||||
msg = "Parameters to generic types must be types."
|
msg = "Parameters to generic types must be types."
|
||||||
params = tuple(_type_check(p, msg) for p in params)
|
params = tuple(_type_check(p, msg) for p in params)
|
||||||
if self in (Generic, Protocol):
|
if self in (Generic, Protocol):
|
||||||
|
@ -2108,7 +2098,6 @@ elif PEP_560:
|
||||||
return hint
|
return hint
|
||||||
return {k: _strip_annotations(t) for k, t in hint.items()}
|
return {k: _strip_annotations(t) for k, t in hint.items()}
|
||||||
|
|
||||||
|
|
||||||
elif HAVE_ANNOTATED:
|
elif HAVE_ANNOTATED:
|
||||||
|
|
||||||
def _is_dunder(name):
|
def _is_dunder(name):
|
||||||
|
@ -2344,7 +2333,6 @@ elif sys.version_info[:2] >= (3, 9):
|
||||||
"""
|
"""
|
||||||
raise TypeError("{} is not subscriptable".format(self))
|
raise TypeError("{} is not subscriptable".format(self))
|
||||||
|
|
||||||
|
|
||||||
elif sys.version_info[:2] >= (3, 7):
|
elif sys.version_info[:2] >= (3, 7):
|
||||||
|
|
||||||
class _TypeAliasForm(typing._SpecialForm, _root=True):
|
class _TypeAliasForm(typing._SpecialForm, _root=True):
|
||||||
|
@ -2672,7 +2660,6 @@ elif sys.version_info[:2] >= (3, 9):
|
||||||
"""
|
"""
|
||||||
return _concatenate_getitem(self, parameters)
|
return _concatenate_getitem(self, parameters)
|
||||||
|
|
||||||
|
|
||||||
elif sys.version_info[:2] >= (3, 7):
|
elif sys.version_info[:2] >= (3, 7):
|
||||||
|
|
||||||
class _ConcatenateForm(typing._SpecialForm, _root=True):
|
class _ConcatenateForm(typing._SpecialForm, _root=True):
|
||||||
|
@ -2821,7 +2808,6 @@ elif sys.version_info[:2] >= (3, 9):
|
||||||
item = typing._type_check(parameters, '{} accepts only single type.'.format(self))
|
item = typing._type_check(parameters, '{} accepts only single type.'.format(self))
|
||||||
return _GenericAlias(self, (item,))
|
return _GenericAlias(self, (item,))
|
||||||
|
|
||||||
|
|
||||||
elif sys.version_info[:2] >= (3, 7):
|
elif sys.version_info[:2] >= (3, 7):
|
||||||
|
|
||||||
class _TypeGuardForm(typing._SpecialForm, _root=True):
|
class _TypeGuardForm(typing._SpecialForm, _root=True):
|
||||||
|
|
|
@ -74,6 +74,7 @@ class CreateMixin: # pylint: disable=too-few-public-methods
|
||||||
process will run as.
|
process will run as.
|
||||||
healthcheck (Dict[str,Any]): Specify a test to perform to check that the
|
healthcheck (Dict[str,Any]): Specify a test to perform to check that the
|
||||||
container is healthy.
|
container is healthy.
|
||||||
|
health_check_on_failure_action (int): Specify an action if a healthcheck fails.
|
||||||
hostname (str): Optional hostname for the container.
|
hostname (str): Optional hostname for the container.
|
||||||
init (bool): Run an init inside the container that forwards signals and reaps processes
|
init (bool): Run an init inside the container that forwards signals and reaps processes
|
||||||
init_path (str): Path to the docker-init binary
|
init_path (str): Path to the docker-init binary
|
||||||
|
@ -310,8 +311,7 @@ class CreateMixin: # pylint: disable=too-few-public-methods
|
||||||
if search:
|
if search:
|
||||||
return int(search.group(1)) * (1024 ** mapping[search.group(2)])
|
return int(search.group(1)) * (1024 ** mapping[search.group(2)])
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
f"Passed string size {size} should be in format\\d+[bBkKmMgG] (e.g."
|
f"Passed string size {size} should be in format\\d+[bBkKmMgG] (e.g. '100m')"
|
||||||
" '100m')"
|
|
||||||
) from bad_size
|
) from bad_size
|
||||||
else:
|
else:
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
|
@ -341,6 +341,7 @@ class CreateMixin: # pylint: disable=too-few-public-methods
|
||||||
"expose": {},
|
"expose": {},
|
||||||
"groups": pop("group_add"),
|
"groups": pop("group_add"),
|
||||||
"healthconfig": pop("healthcheck"),
|
"healthconfig": pop("healthcheck"),
|
||||||
|
"health_check_on_failure_action": pop("health_check_on_failure_action"),
|
||||||
"hostadd": [],
|
"hostadd": [],
|
||||||
"hostname": pop("hostname"),
|
"hostname": pop("hostname"),
|
||||||
"httpproxy": pop("use_config_proxy"),
|
"httpproxy": pop("use_config_proxy"),
|
||||||
|
@ -415,9 +416,7 @@ class CreateMixin: # pylint: disable=too-few-public-methods
|
||||||
if "Config" in args["log_config"]:
|
if "Config" in args["log_config"]:
|
||||||
params["log_configuration"]["path"] = args["log_config"]["Config"].get("path")
|
params["log_configuration"]["path"] = args["log_config"]["Config"].get("path")
|
||||||
params["log_configuration"]["size"] = args["log_config"]["Config"].get("size")
|
params["log_configuration"]["size"] = args["log_config"]["Config"].get("size")
|
||||||
params["log_configuration"]["options"] = args["log_config"]["Config"].get(
|
params["log_configuration"]["options"] = args["log_config"]["Config"].get("options")
|
||||||
"options"
|
|
||||||
)
|
|
||||||
args.pop("log_config")
|
args.pop("log_config")
|
||||||
|
|
||||||
for item in args.pop("mounts", []):
|
for item in args.pop("mounts", []):
|
||||||
|
|
|
@ -25,10 +25,7 @@ class ContainersManager(RunMixin, CreateMixin, Manager):
|
||||||
response = self.client.get(f"/containers/{key}/exists")
|
response = self.client.get(f"/containers/{key}/exists")
|
||||||
return response.ok
|
return response.ok
|
||||||
|
|
||||||
# pylint is flagging 'container_id' here vs. 'key' parameter in super.get()
|
def get(self, key: str) -> Container:
|
||||||
def get(
|
|
||||||
self, container_id: str
|
|
||||||
) -> Container: # pylint: disable=arguments-differ,arguments-renamed
|
|
||||||
"""Get container by name or id.
|
"""Get container by name or id.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -38,7 +35,7 @@ class ContainersManager(RunMixin, CreateMixin, Manager):
|
||||||
NotFound: when Container does not exist
|
NotFound: when Container does not exist
|
||||||
APIError: when an error return by service
|
APIError: when an error return by service
|
||||||
"""
|
"""
|
||||||
container_id = urllib.parse.quote_plus(container_id)
|
container_id = urllib.parse.quote_plus(key)
|
||||||
response = self.client.get(f"/containers/{container_id}/json")
|
response = self.client.get(f"/containers/{container_id}/json")
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return self.prepare_model(attrs=response.json())
|
return self.prepare_model(attrs=response.json())
|
||||||
|
|
|
@ -27,6 +27,7 @@ class ImagesManager(BuildMixin, Manager):
|
||||||
return Image
|
return Image
|
||||||
|
|
||||||
def exists(self, key: str) -> bool:
|
def exists(self, key: str) -> bool:
|
||||||
|
"""Return true when image exists."""
|
||||||
key = urllib.parse.quote_plus(key)
|
key = urllib.parse.quote_plus(key)
|
||||||
response = self.client.get(f"/images/{key}/exists")
|
response = self.client.get(f"/images/{key}/exists")
|
||||||
return response.ok
|
return response.ok
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import logging
|
import logging
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
from podman import api
|
from podman import api
|
||||||
from podman.domain.images import Image
|
from podman.domain.images import Image
|
||||||
|
@ -17,20 +17,18 @@ class Manifest(PodmanResource):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self):
|
def id(self):
|
||||||
"""str: Returns the identifier of the manifest."""
|
"""str: Returns the identifier of the manifest list."""
|
||||||
with suppress(KeyError, TypeError, IndexError):
|
with suppress(KeyError, TypeError, IndexError):
|
||||||
return self.attrs["manifests"][0]["digest"]
|
digest = self.attrs["manifests"][0]["digest"]
|
||||||
|
if digest.startswith("sha256:"):
|
||||||
|
return digest[7:]
|
||||||
|
return digest
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""str: Returns the identifier of the manifest."""
|
"""str: Returns the human-formatted identifier of the manifest list."""
|
||||||
try:
|
return self.attrs.get("names")
|
||||||
if len(self.names[0]) == 0:
|
|
||||||
raise ValueError("Manifest attribute 'names' is empty.")
|
|
||||||
return self.names[0]
|
|
||||||
except (TypeError, IndexError) as e:
|
|
||||||
raise ValueError("Manifest attribute 'names' is missing.") from e
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def quoted_name(self):
|
def quoted_name(self):
|
||||||
|
@ -40,7 +38,7 @@ class Manifest(PodmanResource):
|
||||||
@property
|
@property
|
||||||
def names(self):
|
def names(self):
|
||||||
"""List[str]: Returns the identifier of the manifest."""
|
"""List[str]: Returns the identifier of the manifest."""
|
||||||
return self.attrs.get("names")
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_type(self):
|
def media_type(self):
|
||||||
|
@ -71,7 +69,7 @@ class Manifest(PodmanResource):
|
||||||
ImageNotFound: when Image(s) could not be found
|
ImageNotFound: when Image(s) could not be found
|
||||||
APIError: when service reports an error
|
APIError: when service reports an error
|
||||||
"""
|
"""
|
||||||
params = {
|
data = {
|
||||||
"all": kwargs.get("all"),
|
"all": kwargs.get("all"),
|
||||||
"annotation": kwargs.get("annotation"),
|
"annotation": kwargs.get("annotation"),
|
||||||
"arch": kwargs.get("arch"),
|
"arch": kwargs.get("arch"),
|
||||||
|
@ -80,14 +78,15 @@ class Manifest(PodmanResource):
|
||||||
"os": kwargs.get("os"),
|
"os": kwargs.get("os"),
|
||||||
"os_version": kwargs.get("os_version"),
|
"os_version": kwargs.get("os_version"),
|
||||||
"variant": kwargs.get("variant"),
|
"variant": kwargs.get("variant"),
|
||||||
|
"operation": "update",
|
||||||
}
|
}
|
||||||
for item in images:
|
for item in images:
|
||||||
if isinstance(item, Image):
|
if isinstance(item, Image):
|
||||||
item = item.attrs["RepoTags"][0]
|
item = item.attrs["RepoTags"][0]
|
||||||
params["images"].append(item)
|
data["images"].append(item)
|
||||||
|
|
||||||
data = api.prepare_body(params)
|
data = api.prepare_body(data)
|
||||||
response = self.client.post(f"/manifests/{self.quoted_name}/add", data=data)
|
response = self.client.put(f"/manifests/{self.quoted_name}", data=data)
|
||||||
response.raise_for_status(not_found=ImageNotFound)
|
response.raise_for_status(not_found=ImageNotFound)
|
||||||
return self.reload()
|
return self.reload()
|
||||||
|
|
||||||
|
@ -127,7 +126,10 @@ class Manifest(PodmanResource):
|
||||||
if "@" in digest:
|
if "@" in digest:
|
||||||
digest = digest.split("@", maxsplit=2)[1]
|
digest = digest.split("@", maxsplit=2)[1]
|
||||||
|
|
||||||
response = self.client.delete(f"/manifests/{self.quoted_name}", params={"digest": digest})
|
data = {"operation": "remove", "images": [digest]}
|
||||||
|
data = api.prepare_body(data)
|
||||||
|
|
||||||
|
response = self.client.put(f"/manifests/{self.quoted_name}", data=data)
|
||||||
response.raise_for_status(not_found=ImageNotFound)
|
response.raise_for_status(not_found=ImageNotFound)
|
||||||
return self.reload()
|
return self.reload()
|
||||||
|
|
||||||
|
@ -147,14 +149,14 @@ class ManifestsManager(Manager):
|
||||||
|
|
||||||
def create(
|
def create(
|
||||||
self,
|
self,
|
||||||
names: List[str],
|
name: str,
|
||||||
images: Optional[List[Union[Image, str]]] = None,
|
images: Optional[List[Union[Image, str]]] = None,
|
||||||
all: Optional[bool] = None, # pylint: disable=redefined-builtin
|
all: Optional[bool] = None, # pylint: disable=redefined-builtin
|
||||||
) -> Manifest:
|
) -> Manifest:
|
||||||
"""Create a Manifest.
|
"""Create a Manifest.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
names: Identifiers to be added to the manifest. There must be at least one.
|
name: Name of manifest list.
|
||||||
images: Images or Image identifiers to be included in the manifest.
|
images: Images or Image identifiers to be included in the manifest.
|
||||||
all: When True, add all contents from images given.
|
all: When True, add all contents from images given.
|
||||||
|
|
||||||
|
@ -162,26 +164,24 @@ class ManifestsManager(Manager):
|
||||||
ValueError: when no names are provided
|
ValueError: when no names are provided
|
||||||
NotFoundImage: when a given image does not exist
|
NotFoundImage: when a given image does not exist
|
||||||
"""
|
"""
|
||||||
if names is None or len(names) == 0:
|
params: Dict[str, Any] = {}
|
||||||
raise ValueError("At least one manifest name is required.")
|
|
||||||
|
|
||||||
params = {"name": names}
|
|
||||||
if images is not None:
|
if images is not None:
|
||||||
params["image"] = []
|
params["images"] = []
|
||||||
for item in images:
|
for item in images:
|
||||||
if isinstance(item, Image):
|
if isinstance(item, Image):
|
||||||
item = item.attrs["RepoTags"][0]
|
item = item.attrs["RepoTags"][0]
|
||||||
params["image"].append(item)
|
params["images"].append(item)
|
||||||
|
|
||||||
if all is not None:
|
if all is not None:
|
||||||
params["all"] = all
|
params["all"] = all
|
||||||
|
|
||||||
response = self.client.post("/manifests/create", params=params)
|
name_quoted = urllib.parse.quote_plus(name)
|
||||||
|
response = self.client.post(f"/manifests/{name_quoted}", params=params)
|
||||||
response.raise_for_status(not_found=ImageNotFound)
|
response.raise_for_status(not_found=ImageNotFound)
|
||||||
|
|
||||||
body = response.json()
|
body = response.json()
|
||||||
manifest = self.get(body["Id"])
|
manifest = self.get(body["Id"])
|
||||||
manifest.attrs["names"] = names
|
manifest.attrs["names"] = name
|
||||||
|
|
||||||
if manifest.attrs["manifests"] is None:
|
if manifest.attrs["manifests"] is None:
|
||||||
manifest.attrs["manifests"] = []
|
manifest.attrs["manifests"] = []
|
||||||
|
@ -198,9 +198,6 @@ class ManifestsManager(Manager):
|
||||||
To have Manifest conform with other PodmanResource's, we use the key that
|
To have Manifest conform with other PodmanResource's, we use the key that
|
||||||
retrieved the Manifest be its name.
|
retrieved the Manifest be its name.
|
||||||
|
|
||||||
See https://issues.redhat.com/browse/RUN-1217 for details on refactoring Podman service
|
|
||||||
manifests API.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: Manifest name for which to search
|
key: Manifest name for which to search
|
||||||
|
|
||||||
|
@ -213,10 +210,23 @@ class ManifestsManager(Manager):
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
body = response.json()
|
body = response.json()
|
||||||
body["names"] = [key]
|
if "names" not in body:
|
||||||
|
body["names"] = key
|
||||||
return self.prepare_model(attrs=body)
|
return self.prepare_model(attrs=body)
|
||||||
|
|
||||||
def list(self, **kwargs) -> List[Manifest]:
|
def list(self, **kwargs) -> List[Manifest]:
|
||||||
"""Not Implemented."""
|
"""Not Implemented."""
|
||||||
|
|
||||||
raise NotImplementedError("Podman service currently does not support listing manifests.")
|
raise NotImplementedError("Podman service currently does not support listing manifests.")
|
||||||
|
|
||||||
|
def remove(self, name: Union[Manifest, str]) -> Dict[str, Any]:
|
||||||
|
"""Delete the manifest list from the Podman service."""
|
||||||
|
if isinstance(name, Manifest):
|
||||||
|
name = name.name
|
||||||
|
|
||||||
|
response = self.client.delete(f"/manifests/{name}")
|
||||||
|
response.raise_for_status(not_found=ImageNotFound)
|
||||||
|
|
||||||
|
body = response.json()
|
||||||
|
body["ExitCode"] = response.status_code
|
||||||
|
return body
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
"""Model and Manager for Network resources.
|
"""Model for Network resources.
|
||||||
|
|
||||||
By default, most methods in this module uses the Podman compatible API rather than the
|
Example:
|
||||||
libpod API as the results are so different. To use the libpod API add the keyword argument
|
|
||||||
compatible=False to any method call.
|
with PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock") as client:
|
||||||
|
net = client.networks.get("db_network")
|
||||||
|
print(net.name, "\n")
|
||||||
"""
|
"""
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
@ -42,11 +44,12 @@ class Network(PodmanResource):
|
||||||
with suppress(KeyError):
|
with suppress(KeyError):
|
||||||
container_manager = ContainersManager(client=self.client)
|
container_manager = ContainersManager(client=self.client)
|
||||||
return [container_manager.get(ident) for ident in self.attrs["Containers"].keys()]
|
return [container_manager.get(ident) for ident in self.attrs["Containers"].keys()]
|
||||||
return {}
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""str: Returns the name of the network."""
|
"""str: Returns the name of the network."""
|
||||||
|
|
||||||
if "Name" in self.attrs:
|
if "Name" in self.attrs:
|
||||||
return self.attrs["Name"]
|
return self.attrs["Name"]
|
||||||
|
|
||||||
|
@ -68,7 +71,6 @@ class Network(PodmanResource):
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
aliases (List[str]): Aliases to add for this endpoint
|
aliases (List[str]): Aliases to add for this endpoint
|
||||||
compatible (bool): Should compatible API be used. Default: True
|
|
||||||
driver_opt (Dict[str, Any]): Options to provide to network driver
|
driver_opt (Dict[str, Any]): Options to provide to network driver
|
||||||
ipv4_address (str): IPv4 address for given Container on this network
|
ipv4_address (str): IPv4 address for given Container on this network
|
||||||
ipv6_address (str): IPv6 address for given Container on this network
|
ipv6_address (str): IPv6 address for given Container on this network
|
||||||
|
@ -78,8 +80,6 @@ class Network(PodmanResource):
|
||||||
Raises:
|
Raises:
|
||||||
APIError: when Podman service reports an error
|
APIError: when Podman service reports an error
|
||||||
"""
|
"""
|
||||||
compatible = kwargs.get("compatible", True)
|
|
||||||
|
|
||||||
if isinstance(container, Container):
|
if isinstance(container, Container):
|
||||||
container = container.id
|
container = container.id
|
||||||
|
|
||||||
|
@ -110,7 +110,6 @@ class Network(PodmanResource):
|
||||||
f"/networks/{self.name}/connect",
|
f"/networks/{self.name}/connect",
|
||||||
data=json.dumps(data),
|
data=json.dumps(data),
|
||||||
headers={"Content-type": "application/json"},
|
headers={"Content-type": "application/json"},
|
||||||
compatible=compatible,
|
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
@ -126,15 +125,11 @@ class Network(PodmanResource):
|
||||||
Raises:
|
Raises:
|
||||||
APIError: when Podman service reports an error
|
APIError: when Podman service reports an error
|
||||||
"""
|
"""
|
||||||
compatible = kwargs.get("compatible", True)
|
|
||||||
|
|
||||||
if isinstance(container, Container):
|
if isinstance(container, Container):
|
||||||
container = container.id
|
container = container.id
|
||||||
|
|
||||||
data = {"Container": container, "Force": kwargs.get("force")}
|
data = {"Container": container, "Force": kwargs.get("force")}
|
||||||
response = self.client.post(
|
response = self.client.post(f"/networks/{self.name}/disconnect", data=json.dumps(data))
|
||||||
f"/networks/{self.name}/disconnect", data=json.dumps(data), compatible=compatible
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
def remove(self, force: Optional[bool] = None, **kwargs) -> None:
|
def remove(self, force: Optional[bool] = None, **kwargs) -> None:
|
||||||
|
@ -143,9 +138,6 @@ class Network(PodmanResource):
|
||||||
Args:
|
Args:
|
||||||
force: Remove network and any associated containers
|
force: Remove network and any associated containers
|
||||||
|
|
||||||
Keyword Args:
|
|
||||||
compatible (bool): Should compatible API be used. Default: True
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
APIError: when Podman service reports an error
|
APIError: when Podman service reports an error
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
"""PodmanResource manager subclassed for Networks.
|
"""PodmanResource manager subclassed for Network resources.
|
||||||
|
|
||||||
By default, most methods in this module uses the Podman compatible API rather than the
|
Classes and methods for manipulating network resources via Podman API service.
|
||||||
libpod API as the results are so different. To use the libpod API add the keyword argument
|
|
||||||
compatible=False to any method call.
|
Example:
|
||||||
|
|
||||||
|
with PodmanClient(base_url="unix:///run/user/1000/podman/podman.sock") as client:
|
||||||
|
for net in client.networks.list():
|
||||||
|
print(net.id, "\n")
|
||||||
"""
|
"""
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
import podman.api.http_utils
|
|
||||||
from podman import api
|
from podman import api
|
||||||
|
from podman.api import http_utils
|
||||||
from podman.domain.manager import Manager
|
from podman.domain.manager import Manager
|
||||||
from podman.domain.networks import Network
|
from podman.domain.networks import Network
|
||||||
from podman.errors import APIError
|
from podman.errors import APIError
|
||||||
|
@ -27,7 +32,7 @@ class NetworksManager(Manager):
|
||||||
return Network
|
return Network
|
||||||
|
|
||||||
def create(self, name: str, **kwargs) -> Network:
|
def create(self, name: str, **kwargs) -> Network:
|
||||||
"""Create a Network.
|
"""Create a Network resource.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Name of network to be created
|
name: Name of network to be created
|
||||||
|
@ -35,14 +40,13 @@ class NetworksManager(Manager):
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
attachable (bool): Ignored, always False.
|
attachable (bool): Ignored, always False.
|
||||||
check_duplicate (bool): Ignored, always False.
|
check_duplicate (bool): Ignored, always False.
|
||||||
disabled_dns (bool): When True, do not provision DNS for this network.
|
dns_enabled (bool): When True, do not provision DNS for this network.
|
||||||
driver (str): Which network driver to use when creating network.
|
driver (str): Which network driver to use when creating network.
|
||||||
enable_ipv6 (bool): Enable IPv6 on the network.
|
enable_ipv6 (bool): Enable IPv6 on the network.
|
||||||
ingress (bool): Ignored, always False.
|
ingress (bool): Ignored, always False.
|
||||||
internal (bool): Restrict external access to the network.
|
internal (bool): Restrict external access to the network.
|
||||||
ipam (IPAMConfig): Optional custom IP scheme for the network.
|
ipam (IPAMConfig): Optional custom IP scheme for the network.
|
||||||
labels (Dict[str, str]): Map of labels to set on the network.
|
labels (Dict[str, str]): Map of labels to set on the network.
|
||||||
macvlan (str):
|
|
||||||
options (Dict[str, Any]): Driver options.
|
options (Dict[str, Any]): Driver options.
|
||||||
scope (str): Ignored, always "local".
|
scope (str): Ignored, always "local".
|
||||||
|
|
||||||
|
@ -50,85 +54,66 @@ class NetworksManager(Manager):
|
||||||
APIError: when Podman service reports an error
|
APIError: when Podman service reports an error
|
||||||
"""
|
"""
|
||||||
data = {
|
data = {
|
||||||
"DisabledDNS": kwargs.get("disabled_dns"),
|
"name": name,
|
||||||
"Driver": kwargs.get("driver"),
|
"driver": kwargs.get("driver"),
|
||||||
"Internal": kwargs.get("internal"),
|
"dns_enabled": kwargs.get("dns_enabled"),
|
||||||
"IPv6": kwargs.get("enable_ipv6"),
|
"subnets": kwargs.get("subnets"),
|
||||||
"Labels": kwargs.get("labels"),
|
"ipv6_enabled": kwargs.get("enable_ipv6"),
|
||||||
"MacVLAN": kwargs.get("macvlan"),
|
"internal": kwargs.get("internal"),
|
||||||
"Options": kwargs.get("options"),
|
"labels": kwargs.get("labels"),
|
||||||
|
"options": kwargs.get("options"),
|
||||||
}
|
}
|
||||||
|
|
||||||
with suppress(KeyError):
|
with suppress(KeyError):
|
||||||
ipam = kwargs["ipam"]
|
self._prepare_ipam(data, kwargs["ipam"])
|
||||||
if len(ipam["Config"]) > 0:
|
|
||||||
|
|
||||||
if len(ipam["Config"]) > 1:
|
|
||||||
raise ValueError("Podman service only supports one IPAM config.")
|
|
||||||
|
|
||||||
ip_config = ipam["Config"][0]
|
|
||||||
data["Gateway"] = ip_config.get("Gateway")
|
|
||||||
|
|
||||||
if "IPRange" in ip_config:
|
|
||||||
iprange = ipaddress.ip_network(ip_config["IPRange"])
|
|
||||||
iprange, mask = api.prepare_cidr(iprange)
|
|
||||||
data["Range"] = {
|
|
||||||
"IP": iprange,
|
|
||||||
"Mask": mask,
|
|
||||||
}
|
|
||||||
|
|
||||||
if "Subnet" in ip_config:
|
|
||||||
subnet = ipaddress.ip_network(ip_config["Subnet"])
|
|
||||||
subnet, mask = api.prepare_cidr(subnet)
|
|
||||||
data["Subnet"] = {
|
|
||||||
"IP": subnet,
|
|
||||||
"Mask": mask,
|
|
||||||
}
|
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/networks/create",
|
"/networks/create",
|
||||||
params={"name": name},
|
data=http_utils.prepare_body(data),
|
||||||
data=podman.api.http_utils.prepare_body(data),
|
|
||||||
headers={"Content-Type": "application/json"},
|
headers={"Content-Type": "application/json"},
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
sys.stderr.write(str(response.json()))
|
||||||
|
return self.prepare_model(attrs=response.json())
|
||||||
|
|
||||||
return self.get(name, **kwargs)
|
def _prepare_ipam(self, data: Dict[str, Any], ipam: Dict[str, Any]):
|
||||||
|
if "Config" not in ipam:
|
||||||
|
return
|
||||||
|
|
||||||
|
data["subnets"] = []
|
||||||
|
for cfg in ipam["Config"]:
|
||||||
|
subnet = {
|
||||||
|
"gateway": cfg.get("Gateway"),
|
||||||
|
"subnet": cfg.get("Subnet"),
|
||||||
|
}
|
||||||
|
|
||||||
|
with suppress(KeyError):
|
||||||
|
net = ipaddress.ip_network(cfg["IPRange"])
|
||||||
|
subnet["lease_range"] = {
|
||||||
|
"start_ip": str(net[1]),
|
||||||
|
"end_ip": str(net[-2]),
|
||||||
|
}
|
||||||
|
|
||||||
|
data["subnets"].append(subnet)
|
||||||
|
|
||||||
def exists(self, key: str) -> bool:
|
def exists(self, key: str) -> bool:
|
||||||
response = self.client.get(f"/networks/{key}/exists")
|
response = self.client.get(f"/networks/{key}/exists")
|
||||||
return response.ok
|
return response.ok
|
||||||
|
|
||||||
# pylint is flagging 'network_id' here vs. 'key' parameter in super.get()
|
def get(self, key: str) -> Network:
|
||||||
def get(self, network_id: str, *_, **kwargs) -> Network: # pylint: disable=arguments-differ
|
|
||||||
"""Return information for the network_id.
|
"""Return information for the network_id.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
network_id: Network name or id.
|
key: Network name or id.
|
||||||
|
|
||||||
Keyword Args:
|
|
||||||
compatible (bool): Should compatible API be used. Default: True
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
NotFound: when Network does not exist
|
NotFound: when Network does not exist
|
||||||
APIError: when error returned by service
|
APIError: when error returned by service
|
||||||
|
|
||||||
Note:
|
|
||||||
The compatible API is used, this allows the server to provide dynamic fields.
|
|
||||||
id is the most important example.
|
|
||||||
"""
|
"""
|
||||||
compatible = kwargs.get("compatible", True)
|
response = self.client.get(f"/networks/{key}")
|
||||||
|
|
||||||
path = f"/networks/{network_id}" + ("" if compatible else "/json")
|
|
||||||
|
|
||||||
response = self.client.get(path, compatible=compatible)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
body = response.json()
|
return self.prepare_model(attrs=response.json())
|
||||||
if not compatible:
|
|
||||||
body = body[0]
|
|
||||||
|
|
||||||
return self.prepare_model(attrs=body)
|
|
||||||
|
|
||||||
def list(self, **kwargs) -> List[Network]:
|
def list(self, **kwargs) -> List[Network]:
|
||||||
"""Report on networks.
|
"""Report on networks.
|
||||||
|
@ -162,23 +147,19 @@ class NetworksManager(Manager):
|
||||||
Raises:
|
Raises:
|
||||||
APIError: when error returned by service
|
APIError: when error returned by service
|
||||||
"""
|
"""
|
||||||
compatible = kwargs.get("compatible", True)
|
|
||||||
|
|
||||||
filters = kwargs.get("filters", {})
|
filters = kwargs.get("filters", {})
|
||||||
filters["name"] = kwargs.get("names")
|
filters["name"] = kwargs.get("names")
|
||||||
filters["id"] = kwargs.get("ids")
|
filters["id"] = kwargs.get("ids")
|
||||||
filters = api.prepare_filters(filters)
|
filters = api.prepare_filters(filters)
|
||||||
|
|
||||||
params = {"filters": filters}
|
params = {"filters": filters}
|
||||||
path = f"/networks{'' if compatible else '/json'}"
|
response = self.client.get("/networks/json", params=params)
|
||||||
|
|
||||||
response = self.client.get(path, params=params, compatible=compatible)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
return [self.prepare_model(i) for i in response.json()]
|
return [self.prepare_model(i) for i in response.json()]
|
||||||
|
|
||||||
def prune(
|
def prune(
|
||||||
self, filters: Optional[Dict[str, Any]] = None, **kwargs
|
self, filters: Optional[Dict[str, Any]] = None
|
||||||
) -> Dict[api.Literal["NetworksDeleted", "SpaceReclaimed"], Any]:
|
) -> Dict[api.Literal["NetworksDeleted", "SpaceReclaimed"], Any]:
|
||||||
"""Delete unused Networks.
|
"""Delete unused Networks.
|
||||||
|
|
||||||
|
@ -187,25 +168,14 @@ class NetworksManager(Manager):
|
||||||
Args:
|
Args:
|
||||||
filters: Criteria for selecting volumes to delete. Ignored.
|
filters: Criteria for selecting volumes to delete. Ignored.
|
||||||
|
|
||||||
Keyword Args:
|
|
||||||
compatible (bool): Should compatible API be used. Default: True
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
APIError: when service reports error
|
APIError: when service reports error
|
||||||
"""
|
"""
|
||||||
compatible = kwargs.get("compatible", True)
|
response = self.client.post("/networks/prune", filters=api.prepare_filters(filters))
|
||||||
|
|
||||||
response = self.client.post(
|
|
||||||
"/networks/prune", filters=api.prepare_filters(filters), compatible=compatible
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
body = response.json()
|
|
||||||
if compatible:
|
|
||||||
return body
|
|
||||||
|
|
||||||
deleted: List[str] = []
|
deleted: List[str] = []
|
||||||
for item in body:
|
for item in response.json():
|
||||||
if item["Error"] is not None:
|
if item["Error"] is not None:
|
||||||
raise APIError(
|
raise APIError(
|
||||||
item["Error"],
|
item["Error"],
|
||||||
|
@ -216,27 +186,18 @@ class NetworksManager(Manager):
|
||||||
|
|
||||||
return {"NetworksDeleted": deleted, "SpaceReclaimed": 0}
|
return {"NetworksDeleted": deleted, "SpaceReclaimed": 0}
|
||||||
|
|
||||||
def remove(self, name: [Network, str], force: Optional[bool] = None, **kwargs) -> None:
|
def remove(self, name: [Network, str], force: Optional[bool] = None) -> None:
|
||||||
"""Remove this network.
|
"""Remove Network resource.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Identifier of Network to delete.
|
name: Identifier of Network to delete.
|
||||||
force: Remove network and any associated containers
|
force: Remove network and any associated containers
|
||||||
|
|
||||||
Keyword Args:
|
|
||||||
compatible (bool): Should compatible API be used. Default: True
|
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
APIError: when Podman service reports an error
|
APIError: when Podman service reports an error
|
||||||
|
|
||||||
Notes:
|
|
||||||
Podman only.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(name, Network):
|
if isinstance(name, Network):
|
||||||
name = name.name
|
name = name.name
|
||||||
|
|
||||||
compatible = kwargs.get("compatible", True)
|
response = self.client.delete(f"/networks/{name}", params={"force": force})
|
||||||
response = self.client.delete(
|
|
||||||
f"/networks/{name}", params={"force": force}, compatible=compatible
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"""Model and Manager for Pod resources."""
|
"""Model and Manager for Pod resources."""
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict, Tuple, Union, Optional
|
from typing import Any, Dict, Optional, Tuple, Union
|
||||||
|
|
||||||
from podman.domain.manager import PodmanResource
|
from podman.domain.manager import PodmanResource
|
||||||
|
|
||||||
|
@ -104,6 +104,8 @@ class Pod(PodmanResource):
|
||||||
response = self.client.get(f"/pods/{self.id}/top", params=params)
|
response = self.client.get(f"/pods/{self.id}/top", params=params)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
|
if len(response.text) == 0:
|
||||||
|
return {"Processes": [], "Titles": []}
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
def unpause(self) -> None:
|
def unpause(self) -> None:
|
||||||
|
|
|
@ -94,9 +94,7 @@ class PodsManager(Manager):
|
||||||
Raises:
|
Raises:
|
||||||
APIError: when service reports error
|
APIError: when service reports error
|
||||||
"""
|
"""
|
||||||
response = self.client.post(
|
response = self.client.post("/pods/prune", params={"filters": api.prepare_filters(filters)})
|
||||||
"/pods/prune", params={"filters": api.prepare_filters(filters)}
|
|
||||||
)
|
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
deleted: List[str] = []
|
deleted: List[str] = []
|
||||||
|
|
|
@ -3,5 +3,5 @@
|
||||||
# Do not auto-update these from version.py,
|
# Do not auto-update these from version.py,
|
||||||
# as test code should be changed to reflect changes in Podman API versions
|
# as test code should be changed to reflect changes in Podman API versions
|
||||||
BASE_SOCK = "unix:///run/api.sock"
|
BASE_SOCK = "unix:///run/api.sock"
|
||||||
LIBPOD_URL = "http://%2Frun%2Fapi.sock/v3.2.1/libpod"
|
LIBPOD_URL = "http://%2Frun%2Fapi.sock/v4.0.0/libpod"
|
||||||
COMPATIBLE_URL = "http://%2Frun%2Fapi.sock/v1.40"
|
COMPATIBLE_URL = "http://%2Frun%2Fapi.sock/v1.40"
|
||||||
|
|
|
@ -37,24 +37,22 @@ class IntegrationTest(fixtures.TestWithFixtures):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls) -> None:
|
def setUpClass(cls) -> None:
|
||||||
|
super(fixtures.TestWithFixtures, cls).setUpClass()
|
||||||
|
|
||||||
command = os.environ.get("PODMAN_BINARY", "podman")
|
command = os.environ.get("PODMAN_BINARY", "podman")
|
||||||
if shutil.which(command) is None:
|
if shutil.which(command) is None:
|
||||||
raise AssertionError(f"'{command}' not found.")
|
raise AssertionError(f"'{command}' not found.")
|
||||||
IntegrationTest.podman = command
|
IntegrationTest.podman = command
|
||||||
|
|
||||||
# For testing, lock in logging configuration
|
# This log_level is for our python code
|
||||||
if "DEBUG" in os.environ:
|
log_level = os.environ.get("PODMAN_LOG_LEVEL", "INFO")
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
log_level = logging.getLevelName(log_level)
|
||||||
else:
|
logging.basicConfig(level=log_level)
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
# This is the log_level to pass to podman service
|
self.log_level = os.environ.get("PODMAN_LOG_LEVEL", "INFO")
|
||||||
self.log_level = logging.WARNING
|
|
||||||
if "DEBUG" in os.environ:
|
|
||||||
self.log_level = logging.DEBUG
|
|
||||||
|
|
||||||
self.test_dir = self.useFixture(fixtures.TempDir()).path
|
self.test_dir = self.useFixture(fixtures.TempDir()).path
|
||||||
self.socket_file = os.path.join(self.test_dir, uuid.uuid4().hex)
|
self.socket_file = os.path.join(self.test_dir, uuid.uuid4().hex)
|
||||||
|
|
|
@ -67,9 +67,13 @@ class ContainersIntegrationTest(base.IntegrationTest):
|
||||||
test['expected_value'],
|
test['expected_value'],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_container_kernel_memory(self):
|
def test_container_healtchecks(self):
|
||||||
"""Test passing kernel memory"""
|
"""Test passing various healthcheck options"""
|
||||||
self._test_memory_limit('kernel_memory', 'KernelMemory')
|
parameters = {}
|
||||||
|
parameters['healthcheck'] = {'Test': ['CMD-SHELL curl http://localhost || exit']}
|
||||||
|
parameters['health_check_on_failure_action'] = 1
|
||||||
|
container = self.client.containers.create(self.alpine_image, **parameters)
|
||||||
|
self.containers.append(container)
|
||||||
|
|
||||||
def test_container_mem_limit(self):
|
def test_container_mem_limit(self):
|
||||||
"""Test passing memory limit"""
|
"""Test passing memory limit"""
|
||||||
|
|
|
@ -105,9 +105,7 @@ class ContainersIntegrationTest(base.IntegrationTest):
|
||||||
self.assertIsInstance(logs_iter, Iterator)
|
self.assertIsInstance(logs_iter, Iterator)
|
||||||
|
|
||||||
logs = list(logs_iter)
|
logs = list(logs_iter)
|
||||||
self.assertIn(random_string.encode("utf-8"), logs)
|
self.assertIn((random_string + "\n").encode("utf-8"), logs)
|
||||||
# podman 4.0 API support...
|
|
||||||
# self.assertIn((random_string + "\n").encode("utf-8"), logs)
|
|
||||||
|
|
||||||
with self.subTest("Delete Container"):
|
with self.subTest("Delete Container"):
|
||||||
container.remove()
|
container.remove()
|
||||||
|
|
|
@ -25,23 +25,25 @@ class ManifestsIntegrationTest(base.IntegrationTest):
|
||||||
|
|
||||||
self.client.images.remove(self.alpine_image, force=True)
|
self.client.images.remove(self.alpine_image, force=True)
|
||||||
with suppress(ImageNotFound):
|
with suppress(ImageNotFound):
|
||||||
self.client.images.remove("quay.io/unittest/alpine:latest", force=True)
|
self.client.images.remove("localhost/unittest/alpine", force=True)
|
||||||
|
|
||||||
def test_manifest_crud(self):
|
def test_manifest_crud(self):
|
||||||
"""Test Manifest CRUD."""
|
"""Test Manifest CRUD."""
|
||||||
|
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
self.client.manifests.exists("quay.io/unittest/alpine:latest"),
|
self.client.manifests.exists("localhost/unittest/alpine"),
|
||||||
"Image store is corrupt from previous run",
|
"Image store is corrupt from previous run",
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.subTest("Create"):
|
with self.subTest("Create"):
|
||||||
manifest = self.client.manifests.create(["quay.io/unittest/alpine:latest"])
|
manifest = self.client.manifests.create(
|
||||||
self.assertEqual(len(manifest.attrs["manifests"]), 0)
|
"localhost/unittest/alpine", ["quay.io/libpod/alpine:latest"]
|
||||||
self.assertTrue(self.client.manifests.exists(manifest.id))
|
)
|
||||||
|
self.assertEqual(len(manifest.attrs["manifests"]), 1, manifest.attrs)
|
||||||
|
self.assertTrue(self.client.manifests.exists(manifest.names), manifest.id)
|
||||||
|
|
||||||
with self.assertRaises(APIError):
|
with self.assertRaises(APIError):
|
||||||
self.client.manifests.create(["123456!@#$%^"])
|
self.client.manifests.create("123456!@#$%^")
|
||||||
|
|
||||||
with self.subTest("Add"):
|
with self.subTest("Add"):
|
||||||
manifest.add([self.alpine_image])
|
manifest.add([self.alpine_image])
|
||||||
|
@ -54,12 +56,14 @@ class ManifestsIntegrationTest(base.IntegrationTest):
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.subTest("Inspect"):
|
with self.subTest("Inspect"):
|
||||||
actual = self.client.manifests.get("quay.io/unittest/alpine:latest")
|
actual = self.client.manifests.get("quay.io/libpod/alpine:latest")
|
||||||
self.assertEqual(actual.id, manifest.id)
|
self.assertEqual(actual.id, manifest.id)
|
||||||
|
|
||||||
actual = self.client.manifests.get(manifest.name)
|
actual = self.client.manifests.get(manifest.name)
|
||||||
self.assertEqual(actual.id, manifest.id)
|
self.assertEqual(actual.id, manifest.id)
|
||||||
|
|
||||||
|
self.assertEqual(actual.version, 2)
|
||||||
|
|
||||||
with self.subTest("Remove digest"):
|
with self.subTest("Remove digest"):
|
||||||
manifest.remove(self.alpine_image.attrs["RepoDigests"][0])
|
manifest.remove(self.alpine_image.attrs["RepoDigests"][0])
|
||||||
self.assertEqual(len(manifest.attrs["manifests"]), 0)
|
self.assertEqual(len(manifest.attrs["manifests"]), 0)
|
||||||
|
@ -67,7 +71,7 @@ class ManifestsIntegrationTest(base.IntegrationTest):
|
||||||
def test_create_409(self):
|
def test_create_409(self):
|
||||||
"""Test that invalid Image names are caught and not corrupt storage."""
|
"""Test that invalid Image names are caught and not corrupt storage."""
|
||||||
with self.assertRaises(APIError):
|
with self.assertRaises(APIError):
|
||||||
self.client.manifests.create([self.invalid_manifest_name])
|
self.client.manifests.create(self.invalid_manifest_name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -27,26 +27,29 @@ from podman.errors import NotFound
|
||||||
class NetworksIntegrationTest(base.IntegrationTest):
|
class NetworksIntegrationTest(base.IntegrationTest):
|
||||||
"""networks call integration test"""
|
"""networks call integration test"""
|
||||||
|
|
||||||
pool = IPAMPool(subnet="172.16.0.0/16", iprange="172.16.0.0/24", gateway="172.16.0.1")
|
pool = IPAMPool(subnet="10.11.13.0/24", iprange="10.11.13.0/26", gateway="10.11.13.1")
|
||||||
|
|
||||||
ipam = IPAMConfig(pool_configs=[pool])
|
ipam = IPAMConfig(pool_configs=[pool])
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.client = PodmanClient(base_url=self.socket_uri)
|
self.client = PodmanClient(base_url=self.socket_uri)
|
||||||
self.addCleanup(self.client.close)
|
self.addCleanup(self.client.close)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
with suppress(NotFound):
|
with suppress(NotFound):
|
||||||
self.client.networks.get("integration_test").remove(force=True)
|
self.client.networks.get("integration_test").remove(force=True)
|
||||||
|
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
def test_network_crud(self):
|
def test_network_crud(self):
|
||||||
"""integration: networks create and remove calls"""
|
"""integration: networks create and remove calls"""
|
||||||
|
|
||||||
with self.subTest("Create Network"):
|
with self.subTest("Create Network"):
|
||||||
network = self.client.networks.create(
|
network = self.client.networks.create(
|
||||||
"integration_test",
|
"integration_test",
|
||||||
disabled_dns=True,
|
dns_enabled=False,
|
||||||
enable_ipv6=False,
|
|
||||||
ipam=NetworksIntegrationTest.ipam,
|
ipam=NetworksIntegrationTest.ipam,
|
||||||
)
|
)
|
||||||
self.assertEqual(network.name, "integration_test")
|
self.assertEqual(network.name, "integration_test")
|
||||||
|
@ -68,7 +71,7 @@ class NetworksIntegrationTest(base.IntegrationTest):
|
||||||
with self.assertRaises(NotFound):
|
with self.assertRaises(NotFound):
|
||||||
self.client.networks.get("integration_test")
|
self.client.networks.get("integration_test")
|
||||||
|
|
||||||
@unittest.skipIf(os.geteuid() != 0, 'Skipping, not running as root')
|
@unittest.skip("Skipping, libpod endpoint does not report container count")
|
||||||
def test_network_connect(self):
|
def test_network_connect(self):
|
||||||
self.alpine_image = self.client.images.pull("quay.io/libpod/alpine", tag="latest")
|
self.alpine_image = self.client.images.pull("quay.io/libpod/alpine", tag="latest")
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,13 @@ class PodsIntegrationTest(base.IntegrationTest):
|
||||||
self.alpine_image = self.client.images.pull("quay.io/libpod/alpine", tag="latest")
|
self.alpine_image = self.client.images.pull("quay.io/libpod/alpine", tag="latest")
|
||||||
self.pod_name = f"pod_{random.getrandbits(160):x}"
|
self.pod_name = f"pod_{random.getrandbits(160):x}"
|
||||||
|
|
||||||
# TODO should this use podman binary instead?
|
|
||||||
for container in self.client.containers.list():
|
for container in self.client.containers.list():
|
||||||
container.remove(force=True)
|
container.remove(force=True)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
if self.client.pods.exists(self.pod_name):
|
if self.client.pods.exists(self.pod_name):
|
||||||
self.client.pods.remove(self.pod_name)
|
self.client.pods.remove(self.pod_name)
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
def test_pod_crud(self):
|
def test_pod_crud(self):
|
||||||
"""Test Pod CRUD."""
|
"""Test Pod CRUD."""
|
||||||
|
@ -62,6 +62,9 @@ class PodsIntegrationTest(base.IntegrationTest):
|
||||||
with self.assertRaises(NotFound):
|
with self.assertRaises(NotFound):
|
||||||
pod.reload()
|
pod.reload()
|
||||||
|
|
||||||
|
def test_pod_crud_infra(self):
|
||||||
|
"""Test Pod CRUD with infra container."""
|
||||||
|
|
||||||
with self.subTest("Create with infra"):
|
with self.subTest("Create with infra"):
|
||||||
pod = self.client.pods.create(
|
pod = self.client.pods.create(
|
||||||
self.pod_name,
|
self.pod_name,
|
||||||
|
@ -75,22 +78,7 @@ class PodsIntegrationTest(base.IntegrationTest):
|
||||||
actual = self.client.pods.get(pod.id)
|
actual = self.client.pods.get(pod.id)
|
||||||
self.assertEqual(actual.name, pod.name)
|
self.assertEqual(actual.name, pod.name)
|
||||||
self.assertIn("Containers", actual.attrs)
|
self.assertIn("Containers", actual.attrs)
|
||||||
|
self.assertEqual(actual.attrs["State"], "Created")
|
||||||
with self.subTest("Stop/Start"):
|
|
||||||
actual.stop()
|
|
||||||
actual.start()
|
|
||||||
|
|
||||||
with self.subTest("Restart"):
|
|
||||||
actual.restart()
|
|
||||||
|
|
||||||
with self.subTest("Pause/Unpause"):
|
|
||||||
actual.pause()
|
|
||||||
actual.reload()
|
|
||||||
self.assertEqual(actual.attrs["State"], "Paused")
|
|
||||||
|
|
||||||
actual.unpause()
|
|
||||||
actual.reload()
|
|
||||||
self.assertEqual(actual.attrs["State"], "Running")
|
|
||||||
|
|
||||||
with self.subTest("Add container"):
|
with self.subTest("Add container"):
|
||||||
container = self.client.containers.create(self.alpine_image, command=["ls"], pod=actual)
|
container = self.client.containers.create(self.alpine_image, command=["ls"], pod=actual)
|
||||||
|
@ -99,12 +87,6 @@ class PodsIntegrationTest(base.IntegrationTest):
|
||||||
ids = {c["Id"] for c in actual.attrs["Containers"]}
|
ids = {c["Id"] for c in actual.attrs["Containers"]}
|
||||||
self.assertIn(container.id, ids)
|
self.assertIn(container.id, ids)
|
||||||
|
|
||||||
with self.subTest("Ps"):
|
|
||||||
procs = actual.top()
|
|
||||||
|
|
||||||
self.assertGreater(len(procs["Processes"]), 0)
|
|
||||||
self.assertGreater(len(procs["Titles"]), 0)
|
|
||||||
|
|
||||||
with self.subTest("List"):
|
with self.subTest("List"):
|
||||||
pods = self.client.pods.list()
|
pods = self.client.pods.list()
|
||||||
self.assertGreaterEqual(len(pods), 1)
|
self.assertGreaterEqual(len(pods), 1)
|
||||||
|
@ -112,15 +94,64 @@ class PodsIntegrationTest(base.IntegrationTest):
|
||||||
ids = {p.id for p in pods}
|
ids = {p.id for p in pods}
|
||||||
self.assertIn(actual.id, ids)
|
self.assertIn(actual.id, ids)
|
||||||
|
|
||||||
with self.subTest("Stats"):
|
|
||||||
report = self.client.pods.stats(all=True)
|
|
||||||
self.assertGreaterEqual(len(report), 1)
|
|
||||||
|
|
||||||
with self.subTest("Delete"):
|
with self.subTest("Delete"):
|
||||||
pod.remove(force=True)
|
pod.remove(force=True)
|
||||||
with self.assertRaises(NotFound):
|
with self.assertRaises(NotFound):
|
||||||
pod.reload()
|
pod.reload()
|
||||||
|
|
||||||
|
def test_ps(self):
|
||||||
|
pod = self.client.pods.create(
|
||||||
|
self.pod_name,
|
||||||
|
labels={
|
||||||
|
"unittest": "true",
|
||||||
|
},
|
||||||
|
no_infra=True,
|
||||||
|
)
|
||||||
|
self.assertTrue(self.client.pods.exists(pod.id))
|
||||||
|
self.client.containers.create(
|
||||||
|
self.alpine_image, command=["top"], detach=True, tty=True, pod=pod
|
||||||
|
)
|
||||||
|
pod.start()
|
||||||
|
pod.reload()
|
||||||
|
|
||||||
|
with self.subTest("top"):
|
||||||
|
# this is the API top call not the
|
||||||
|
# top command running in the container
|
||||||
|
procs = pod.top()
|
||||||
|
|
||||||
|
self.assertGreater(len(procs["Processes"]), 0)
|
||||||
|
self.assertGreater(len(procs["Titles"]), 0)
|
||||||
|
|
||||||
|
with self.subTest("stats"):
|
||||||
|
report = self.client.pods.stats(all=True)
|
||||||
|
self.assertGreaterEqual(len(report), 1)
|
||||||
|
|
||||||
|
with self.subTest("Stop/Start"):
|
||||||
|
pod.stop()
|
||||||
|
pod.reload()
|
||||||
|
self.assertIn(pod.attrs["State"], ("Stopped", "Exited"))
|
||||||
|
|
||||||
|
pod.start()
|
||||||
|
pod.reload()
|
||||||
|
self.assertEqual(pod.attrs["State"], "Running")
|
||||||
|
|
||||||
|
with self.subTest("Restart"):
|
||||||
|
pod.stop()
|
||||||
|
pod.restart()
|
||||||
|
pod.reload()
|
||||||
|
self.assertEqual(pod.attrs["State"], "Running")
|
||||||
|
|
||||||
|
with self.subTest("Pause/Unpause"):
|
||||||
|
pod.pause()
|
||||||
|
pod.reload()
|
||||||
|
self.assertEqual(pod.attrs["State"], "Paused")
|
||||||
|
|
||||||
|
pod.unpause()
|
||||||
|
pod.reload()
|
||||||
|
self.assertEqual(pod.attrs["State"], "Running")
|
||||||
|
|
||||||
|
pod.stop()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -18,6 +18,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
from contextlib import suppress
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
@ -36,7 +37,7 @@ class PodmanLauncher:
|
||||||
podman_path: Optional[str] = None,
|
podman_path: Optional[str] = None,
|
||||||
timeout: int = 0,
|
timeout: int = 0,
|
||||||
privileged: bool = False,
|
privileged: bool = False,
|
||||||
log_level: int = logging.WARNING,
|
log_level: str = "WARNING",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""create a launcher and build podman command"""
|
"""create a launcher and build podman command"""
|
||||||
podman_exe: str = podman_path
|
podman_exe: str = podman_path
|
||||||
|
@ -57,7 +58,10 @@ class PodmanLauncher:
|
||||||
|
|
||||||
self.cmd.append(podman_exe)
|
self.cmd.append(podman_exe)
|
||||||
|
|
||||||
self.cmd.append(f"--log-level={logging.getLevelName(log_level).lower()}")
|
logger.setLevel(logging.getLevelName(log_level))
|
||||||
|
|
||||||
|
# Map from python to go logging levels, FYI trace level breaks cirrus logging
|
||||||
|
self.cmd.append(f"--log-level={log_level.lower()}")
|
||||||
|
|
||||||
if os.environ.get("container") == "oci":
|
if os.environ.get("container") == "oci":
|
||||||
self.cmd.append("--storage-driver=vfs")
|
self.cmd.append("--storage-driver=vfs")
|
||||||
|
@ -121,4 +125,7 @@ class PodmanLauncher:
|
||||||
return_code = self.proc.wait()
|
return_code = self.proc.wait()
|
||||||
self.proc = None
|
self.proc = None
|
||||||
|
|
||||||
|
with suppress(FileNotFoundError):
|
||||||
|
os.remove(self.socket_file)
|
||||||
|
|
||||||
logger.info("Command return Code: %d refid=%s", return_code, self.reference_id)
|
logger.info("Command return Code: %d refid=%s", return_code, self.reference_id)
|
||||||
|
|
|
@ -47,9 +47,7 @@ class PodmanConfigTestCase(unittest.TestCase):
|
||||||
|
|
||||||
expected = urllib.parse.urlparse("ssh://qe@localhost:2222/run/podman/podman.sock")
|
expected = urllib.parse.urlparse("ssh://qe@localhost:2222/run/podman/podman.sock")
|
||||||
self.assertEqual(config.active_service.url, expected)
|
self.assertEqual(config.active_service.url, expected)
|
||||||
self.assertEqual(
|
self.assertEqual(config.services["production"].identity, Path("/home/root/.ssh/id_rsa"))
|
||||||
config.services["production"].identity, Path("/home/root/.ssh/id_rsa")
|
|
||||||
)
|
|
||||||
|
|
||||||
PodmanConfigTestCase.opener.assert_called_with(
|
PodmanConfigTestCase.opener.assert_called_with(
|
||||||
Path("/home/developer/containers.conf"), encoding='utf-8'
|
Path("/home/developer/containers.conf"), encoding='utf-8'
|
||||||
|
|
|
@ -242,9 +242,7 @@ class ContainersManagerTestCase(unittest.TestCase):
|
||||||
json=FIRST_CONTAINER,
|
json=FIRST_CONTAINER,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch.multiple(
|
with patch.multiple(Container, logs=DEFAULT, wait=DEFAULT, autospec=True) as mock_container:
|
||||||
Container, logs=DEFAULT, wait=DEFAULT, autospec=True
|
|
||||||
) as mock_container:
|
|
||||||
mock_container["logs"].return_value = []
|
mock_container["logs"].return_value = []
|
||||||
mock_container["wait"].return_value = {"StatusCode": 0}
|
mock_container["wait"].return_value = {"StatusCode": 0}
|
||||||
|
|
||||||
|
@ -277,9 +275,7 @@ class ContainersManagerTestCase(unittest.TestCase):
|
||||||
b"This is a unittest - line 2",
|
b"This is a unittest - line 2",
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch.multiple(
|
with patch.multiple(Container, logs=DEFAULT, wait=DEFAULT, autospec=True) as mock_container:
|
||||||
Container, logs=DEFAULT, wait=DEFAULT, autospec=True
|
|
||||||
) as mock_container:
|
|
||||||
mock_container["wait"].return_value = {"StatusCode": 0}
|
mock_container["wait"].return_value = {"StatusCode": 0}
|
||||||
|
|
||||||
with self.subTest("Results not streamed"):
|
with self.subTest("Results not streamed"):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from podman import PodmanClient, tests
|
from podman import PodmanClient, tests
|
||||||
from podman.domain.manifests import ManifestsManager, Manifest
|
from podman.domain.manifests import Manifest, ManifestsManager
|
||||||
|
|
||||||
|
|
||||||
class ManifestTestCase(unittest.TestCase):
|
class ManifestTestCase(unittest.TestCase):
|
||||||
|
@ -9,11 +9,7 @@ class ManifestTestCase(unittest.TestCase):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
||||||
|
self.addCleanup(self.client.close)
|
||||||
def tearDown(self) -> None:
|
|
||||||
super().tearDown()
|
|
||||||
|
|
||||||
self.client.close()
|
|
||||||
|
|
||||||
def test_podmanclient(self):
|
def test_podmanclient(self):
|
||||||
manager = self.client.manifests
|
manager = self.client.manifests
|
||||||
|
@ -24,13 +20,8 @@ class ManifestTestCase(unittest.TestCase):
|
||||||
self.client.manifests.list()
|
self.client.manifests.list()
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
with self.assertRaises(ValueError):
|
manifest = Manifest()
|
||||||
manifest = Manifest(attrs={"names": ""})
|
self.assertIsNone(manifest.name)
|
||||||
_ = manifest.name
|
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
manifest = Manifest()
|
|
||||||
_ = manifest.name
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -28,29 +28,29 @@ FIRST_NETWORK = {
|
||||||
"Labels": {},
|
"Labels": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
FIRST_NETWORK_LIBPOD = [
|
FIRST_NETWORK_LIBPOD = {
|
||||||
{
|
"name": "podman",
|
||||||
"cniVersion": "0.4.0",
|
"id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
|
||||||
"name": "podman",
|
"driver": "bridge",
|
||||||
"plugins": [
|
"network_interface": "libpod_veth0",
|
||||||
{
|
"created": "2022-01-28T09:18:37.491308364-07:00",
|
||||||
"bridge": "cni-podman0",
|
"subnets": [
|
||||||
"hairpinMode": True,
|
{
|
||||||
"ipMasq": True,
|
"subnet": "10.11.12.0/24",
|
||||||
"ipam": {
|
"gateway": "10.11.12.1",
|
||||||
"ranges": [[{"gateway": "10.88.0.1", "subnet": "10.88.0.0/16"}]],
|
"lease_range": {
|
||||||
"routes": [{"dst": "0.0.0.0/0"}],
|
"start_ip": "10.11.12.1",
|
||||||
"type": "host-local",
|
"end_ip": "10.11.12.63",
|
||||||
},
|
|
||||||
"isGateway": True,
|
|
||||||
"type": "bridge",
|
|
||||||
},
|
},
|
||||||
{"capabilities": {"portMappings": True}, "type": "portmap"},
|
}
|
||||||
{"type": "firewall"},
|
],
|
||||||
{"type": "tuning"},
|
"ipv6_enabled": False,
|
||||||
],
|
"internal": False,
|
||||||
}
|
"dns_enabled": False,
|
||||||
]
|
"labels": {},
|
||||||
|
"options": {},
|
||||||
|
"ipam_options": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class NetworkTestCase(unittest.TestCase):
|
class NetworkTestCase(unittest.TestCase):
|
||||||
|
@ -58,11 +58,7 @@ class NetworkTestCase(unittest.TestCase):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
||||||
|
self.addCleanup(self.client.close)
|
||||||
def tearDown(self) -> None:
|
|
||||||
super().tearDown()
|
|
||||||
|
|
||||||
self.client.close()
|
|
||||||
|
|
||||||
def test_id(self):
|
def test_id(self):
|
||||||
expected = {"Id": "1cf06390-709d-4ffa-a054-c3083abe367c"}
|
expected = {"Id": "1cf06390-709d-4ffa-a054-c3083abe367c"}
|
||||||
|
@ -84,7 +80,7 @@ class NetworkTestCase(unittest.TestCase):
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_remove(self, mock):
|
def test_remove(self, mock):
|
||||||
adapter = mock.delete(
|
adapter = mock.delete(
|
||||||
tests.COMPATIBLE_URL + "/networks/podman?force=True",
|
tests.LIBPOD_URL + "/networks/podman?force=True",
|
||||||
status_code=204,
|
status_code=204,
|
||||||
json={"Name": "podman", "Err": None},
|
json={"Name": "podman", "Err": None},
|
||||||
)
|
)
|
||||||
|
@ -96,7 +92,7 @@ class NetworkTestCase(unittest.TestCase):
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_connect(self, mock):
|
def test_connect(self, mock):
|
||||||
adapter = mock.post(tests.COMPATIBLE_URL + "/networks/podman/connect")
|
adapter = mock.post(tests.LIBPOD_URL + "/networks/podman/connect")
|
||||||
net = Network(attrs=FIRST_NETWORK, client=self.client.api)
|
net = Network(attrs=FIRST_NETWORK, client=self.client.api)
|
||||||
|
|
||||||
net.connect(
|
net.connect(
|
||||||
|
@ -120,7 +116,7 @@ class NetworkTestCase(unittest.TestCase):
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_disconnect(self, mock):
|
def test_disconnect(self, mock):
|
||||||
adapter = mock.post(tests.COMPATIBLE_URL + "/networks/podman/disconnect")
|
adapter = mock.post(tests.LIBPOD_URL + "/networks/podman/disconnect")
|
||||||
net = Network(attrs=FIRST_NETWORK, client=self.client.api)
|
net = Network(attrs=FIRST_NETWORK, client=self.client.api)
|
||||||
|
|
||||||
net.disconnect("podman_ctnr", force=True)
|
net.disconnect("podman_ctnr", force=True)
|
||||||
|
|
|
@ -3,7 +3,6 @@ import unittest
|
||||||
import requests_mock
|
import requests_mock
|
||||||
|
|
||||||
from podman import PodmanClient, tests
|
from podman import PodmanClient, tests
|
||||||
from podman.domain.ipam import IPAMConfig, IPAMPool
|
|
||||||
from podman.domain.networks import Network
|
from podman.domain.networks import Network
|
||||||
from podman.domain.networks_manager import NetworksManager
|
from podman.domain.networks_manager import NetworksManager
|
||||||
|
|
||||||
|
@ -51,53 +50,53 @@ SECOND_NETWORK = {
|
||||||
"Labels": {},
|
"Labels": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
FIRST_NETWORK_LIBPOD = [
|
FIRST_NETWORK_LIBPOD = {
|
||||||
{
|
"name": "podman",
|
||||||
"cniVersion": "0.4.0",
|
"id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
|
||||||
"name": "podman",
|
"driver": "bridge",
|
||||||
"plugins": [
|
"network_interface": "libpod_veth0",
|
||||||
{
|
"created": "2022-01-28T09:18:37.491308364-07:00",
|
||||||
"bridge": "cni-podman0",
|
"subnets": [
|
||||||
"hairpinMode": True,
|
{
|
||||||
"ipMasq": True,
|
"subnet": "10.11.12.0/24",
|
||||||
"ipam": {
|
"gateway": "10.11.12.1",
|
||||||
"ranges": [[{"gateway": "10.88.0.1", "subnet": "10.88.0.0/16"}]],
|
"lease_range": {
|
||||||
"routes": [{"dst": "0.0.0.0/0"}],
|
"start_ip": "10.11.12.1",
|
||||||
"type": "host-local",
|
"end_ip": "10.11.12.63",
|
||||||
},
|
|
||||||
"isGateway": True,
|
|
||||||
"type": "bridge",
|
|
||||||
},
|
},
|
||||||
{"capabilities": {"portMappings": True}, "type": "portmap"},
|
}
|
||||||
{"type": "firewall"},
|
],
|
||||||
{"type": "tuning"},
|
"ipv6_enabled": False,
|
||||||
],
|
"internal": False,
|
||||||
}
|
"dns_enabled": False,
|
||||||
]
|
"labels": {},
|
||||||
|
"options": {},
|
||||||
|
"ipam_options": {},
|
||||||
|
}
|
||||||
|
|
||||||
SECOND_NETWORK_LIBPOD = [
|
SECOND_NETWORK_LIBPOD = {
|
||||||
{
|
"name": "database",
|
||||||
"cniVersion": "0.4.0",
|
"id": "3549b0028b75d981cdda2e573e9cb49dedc200185876df299f912b79f69dabd8",
|
||||||
"name": "database",
|
"created": "2021-03-01T09:18:37.491308364-07:00",
|
||||||
"plugins": [
|
"driver": "bridge",
|
||||||
{
|
"network_interface": "libpod_veth1",
|
||||||
"bridge": "cni-podman0",
|
"subnets": [
|
||||||
"hairpinMode": True,
|
{
|
||||||
"ipMasq": True,
|
"subnet": "10.11.12.0/24",
|
||||||
"ipam": {
|
"gateway": "10.11.12.1",
|
||||||
"ranges": [[{"gateway": "10.88.0.1", "subnet": "10.88.0.0/16"}]],
|
"lease_range": {
|
||||||
"routes": [{"dst": "0.0.0.0/0"}],
|
"start_ip": "10.11.12.1",
|
||||||
"type": "host-local",
|
"end_ip": "10.11.12.63",
|
||||||
},
|
|
||||||
"isGateway": True,
|
|
||||||
"type": "bridge",
|
|
||||||
},
|
},
|
||||||
{"capabilities": {"portMappings": True}, "type": "portmap"},
|
}
|
||||||
{"type": "firewall"},
|
],
|
||||||
{"type": "tuning"},
|
"ipv6_enabled": False,
|
||||||
],
|
"internal": False,
|
||||||
}
|
"dns_enabled": False,
|
||||||
]
|
"labels": {},
|
||||||
|
"options": {},
|
||||||
|
"ipam_options": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class NetworksManagerTestCase(unittest.TestCase):
|
class NetworksManagerTestCase(unittest.TestCase):
|
||||||
|
@ -112,11 +111,7 @@ class NetworksManagerTestCase(unittest.TestCase):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
||||||
|
self.addCleanup(self.client.close)
|
||||||
def tearDown(self) -> None:
|
|
||||||
super().tearDown()
|
|
||||||
|
|
||||||
self.client.close()
|
|
||||||
|
|
||||||
def test_podmanclient(self):
|
def test_podmanclient(self):
|
||||||
manager = self.client.networks
|
manager = self.client.networks
|
||||||
|
@ -124,10 +119,7 @@ class NetworksManagerTestCase(unittest.TestCase):
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_get(self, mock):
|
def test_get(self, mock):
|
||||||
mock.get(
|
mock.get(tests.LIBPOD_URL + "/networks/podman", json=FIRST_NETWORK)
|
||||||
tests.COMPATIBLE_URL + "/networks/podman",
|
|
||||||
json=FIRST_NETWORK,
|
|
||||||
)
|
|
||||||
|
|
||||||
actual = self.client.networks.get("podman")
|
actual = self.client.networks.get("podman")
|
||||||
self.assertIsInstance(actual, Network)
|
self.assertIsInstance(actual, Network)
|
||||||
|
@ -135,47 +127,14 @@ class NetworksManagerTestCase(unittest.TestCase):
|
||||||
actual.id, "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9"
|
actual.id, "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9"
|
||||||
)
|
)
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
|
||||||
def test_get_libpod(self, mock):
|
|
||||||
mock.get(
|
|
||||||
tests.LIBPOD_URL + "/networks/podman/json",
|
|
||||||
json=FIRST_NETWORK_LIBPOD,
|
|
||||||
)
|
|
||||||
|
|
||||||
actual = self.client.networks.get("podman", compatible=False)
|
|
||||||
self.assertIsInstance(actual, Network)
|
|
||||||
self.assertEqual(actual.attrs["name"], "podman")
|
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
|
||||||
def test_list(self, mock):
|
|
||||||
mock.get(
|
|
||||||
tests.COMPATIBLE_URL + "/networks",
|
|
||||||
json=[FIRST_NETWORK, SECOND_NETWORK],
|
|
||||||
)
|
|
||||||
|
|
||||||
actual = self.client.networks.list()
|
|
||||||
self.assertEqual(len(actual), 2)
|
|
||||||
|
|
||||||
self.assertIsInstance(actual[0], Network)
|
|
||||||
self.assertEqual(
|
|
||||||
actual[0].id, "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9"
|
|
||||||
)
|
|
||||||
self.assertEqual(actual[0].attrs["Name"], "podman")
|
|
||||||
|
|
||||||
self.assertIsInstance(actual[1], Network)
|
|
||||||
self.assertEqual(
|
|
||||||
actual[1].id, "3549b0028b75d981cdda2e573e9cb49dedc200185876df299f912b79f69dabd8"
|
|
||||||
)
|
|
||||||
self.assertEqual(actual[1].name, "database")
|
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_list_libpod(self, mock):
|
def test_list_libpod(self, mock):
|
||||||
mock.get(
|
mock.get(
|
||||||
tests.LIBPOD_URL + "/networks/json",
|
tests.LIBPOD_URL + "/networks/json",
|
||||||
json=FIRST_NETWORK_LIBPOD + SECOND_NETWORK_LIBPOD,
|
json=[FIRST_NETWORK_LIBPOD, SECOND_NETWORK_LIBPOD],
|
||||||
)
|
)
|
||||||
|
|
||||||
actual = self.client.networks.list(compatible=False)
|
actual = self.client.networks.list()
|
||||||
self.assertEqual(len(actual), 2)
|
self.assertEqual(len(actual), 2)
|
||||||
|
|
||||||
self.assertIsInstance(actual[0], Network)
|
self.assertIsInstance(actual[0], Network)
|
||||||
|
@ -191,68 +150,33 @@ class NetworksManagerTestCase(unittest.TestCase):
|
||||||
self.assertEqual(actual[1].name, "database")
|
self.assertEqual(actual[1].name, "database")
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_create(self, mock):
|
def test_create_libpod(self, mock):
|
||||||
adapter = mock.post(
|
adapter = mock.post(tests.LIBPOD_URL + "/networks/create", json=FIRST_NETWORK_LIBPOD)
|
||||||
tests.LIBPOD_URL + "/networks/create?name=podman",
|
|
||||||
json={
|
|
||||||
"Filename": "/home/developer/.config/cni/net.d/podman.conflist",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
mock.get(
|
|
||||||
tests.COMPATIBLE_URL + "/networks/podman",
|
|
||||||
json=FIRST_NETWORK,
|
|
||||||
)
|
|
||||||
|
|
||||||
pool = IPAMPool(subnet="172.16.0.0/12", iprange="172.16.0.0/16", gateway="172.31.255.254")
|
network = self.client.networks.create("podman", dns_enabled=True, enable_ipv6=True)
|
||||||
ipam = IPAMConfig(pool_configs=[pool])
|
|
||||||
|
|
||||||
network = self.client.networks.create(
|
|
||||||
"podman", disabled_dns=True, enable_ipv6=False, ipam=ipam
|
|
||||||
)
|
|
||||||
self.assertIsInstance(network, Network)
|
self.assertIsInstance(network, Network)
|
||||||
|
|
||||||
self.assertEqual(adapter.call_count, 1)
|
self.assertEqual(adapter.call_count, 1)
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
adapter.last_request.json(),
|
adapter.last_request.json(),
|
||||||
{
|
{
|
||||||
'DisabledDNS': True,
|
"name": "podman",
|
||||||
'Gateway': '172.31.255.254',
|
"ipv6_enabled": True,
|
||||||
'IPv6': False,
|
"dns_enabled": True,
|
||||||
'Range': {'IP': '172.16.0.0', 'Mask': "//8AAA=="},
|
|
||||||
'Subnet': {'IP': '172.16.0.0', 'Mask': "//AAAA=="},
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(network.name, "podman")
|
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_create_defaults(self, mock):
|
def test_create_defaults(self, mock):
|
||||||
adapter = mock.post(
|
adapter = mock.post(tests.LIBPOD_URL + "/networks/create", json=FIRST_NETWORK_LIBPOD)
|
||||||
tests.LIBPOD_URL + "/networks/create?name=podman",
|
|
||||||
json={
|
|
||||||
"Filename": "/home/developer/.config/cni/net.d/podman.conflist",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
mock.get(
|
|
||||||
tests.COMPATIBLE_URL + "/networks/podman",
|
|
||||||
json=FIRST_NETWORK,
|
|
||||||
)
|
|
||||||
|
|
||||||
network = self.client.networks.create("podman")
|
network = self.client.networks.create("podman")
|
||||||
self.assertEqual(adapter.call_count, 1)
|
self.assertEqual(adapter.call_count, 1)
|
||||||
self.assertEqual(network.name, "podman")
|
self.assertDictEqual(
|
||||||
self.assertEqual(len(adapter.last_request.json()), 0)
|
adapter.last_request.json(),
|
||||||
|
{"name": "podman"},
|
||||||
@requests_mock.Mocker()
|
|
||||||
def test_prune(self, mock):
|
|
||||||
mock.post(
|
|
||||||
tests.COMPATIBLE_URL + "/networks/prune",
|
|
||||||
json={"NetworksDeleted": ["podman", "database"]},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
actual = self.client.networks.prune()
|
|
||||||
self.assertListEqual(actual["NetworksDeleted"], ["podman", "database"])
|
|
||||||
|
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_prune_libpod(self, mock):
|
def test_prune_libpod(self, mock):
|
||||||
mock.post(
|
mock.post(
|
||||||
|
@ -263,7 +187,7 @@ class NetworksManagerTestCase(unittest.TestCase):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
actual = self.client.networks.prune(compatible=False)
|
actual = self.client.networks.prune()
|
||||||
self.assertListEqual(actual["NetworksDeleted"], ["podman", "database"])
|
self.assertListEqual(actual["NetworksDeleted"], ["podman", "database"])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,11 +38,7 @@ class VolumesManagerTestCase(unittest.TestCase):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
||||||
|
self.addCleanup(self.client.close)
|
||||||
def tearDown(self) -> None:
|
|
||||||
super().tearDown()
|
|
||||||
|
|
||||||
self.client.close()
|
|
||||||
|
|
||||||
def test_podmanclient(self):
|
def test_podmanclient(self):
|
||||||
manager = self.client.volumes
|
manager = self.client.volumes
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""Version of PodmanPy."""
|
"""Version of PodmanPy."""
|
||||||
|
|
||||||
__version__ = "3.2.1"
|
__version__ = "4.0.1"
|
||||||
__compatible_version__ = "1.40"
|
__compatible_version__ = "1.40"
|
||||||
|
|
|
@ -19,12 +19,20 @@ exclude = '''
|
||||||
profile = "black"
|
profile = "black"
|
||||||
line_length = 100
|
line_length = 100
|
||||||
[build-system]
|
[build-system]
|
||||||
|
# Any changes should be copied into requirements.txt, setup.cfg, and/or test-requirements.txt
|
||||||
requires = [
|
requires = [
|
||||||
|
"pyxdg>=0.26",
|
||||||
"requests>=2.24",
|
"requests>=2.24",
|
||||||
|
"setuptools>=46.4",
|
||||||
|
"sphinx",
|
||||||
"toml>=0.10.2",
|
"toml>=0.10.2",
|
||||||
"urllib3>=1.24.2",
|
"urllib3>=1.24.2",
|
||||||
"pyxdg>=0.26",
|
|
||||||
"setuptools>=46.4",
|
|
||||||
"wheel",
|
"wheel",
|
||||||
]
|
]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
log_cli = true
|
||||||
|
log_cli_level = "DEBUG"
|
||||||
|
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
|
||||||
|
log_cli_date_format = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
# Any changes should be copied into pyproject.toml
|
||||||
|
pyxdg>=0.26
|
||||||
requests>=2.24
|
requests>=2.24
|
||||||
|
setuptools
|
||||||
|
sphinx
|
||||||
toml>=0.10.2
|
toml>=0.10.2
|
||||||
urllib3>=1.24.2
|
urllib3>=1.24.2
|
||||||
pyxdg>=0.26
|
|
||||||
sphinx
|
|
||||||
wheel
|
wheel
|
||||||
setuptools
|
|
||||||
|
|
|
@ -31,11 +31,12 @@ keywords = podman, libpod
|
||||||
include_package_data = True
|
include_package_data = True
|
||||||
python_requires = >=3.6
|
python_requires = >=3.6
|
||||||
test_suite =
|
test_suite =
|
||||||
|
# Any changes should be copied into pyproject.toml
|
||||||
install_requires =
|
install_requires =
|
||||||
|
pyxdg>=0.26
|
||||||
requests>=2.24
|
requests>=2.24
|
||||||
toml>=0.10.2
|
toml>=0.10.2
|
||||||
urllib3>=1.24.2
|
urllib3>=1.24.2
|
||||||
pyxdg>=0.26
|
|
||||||
|
|
||||||
# typing_extensions are included for RHEL 8.5
|
# typing_extensions are included for RHEL 8.5
|
||||||
# typing_extensions;python_version<'3.8'
|
# typing_extensions;python_version<'3.8'
|
||||||
|
|
12
setup.py
12
setup.py
|
@ -1,18 +1,10 @@
|
||||||
import setuptools
|
|
||||||
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
|
|
||||||
|
import setuptools
|
||||||
from setuptools import find_packages
|
from setuptools import find_packages
|
||||||
from setuptools.command.build_py import build_py as build_py_orig
|
from setuptools.command.build_py import build_py as build_py_orig
|
||||||
|
|
||||||
excluded = [
|
excluded = [
|
||||||
"podman/api_connection.py",
|
|
||||||
"podman/containers/*",
|
|
||||||
"podman/images/*",
|
|
||||||
"podman/manifests/*",
|
|
||||||
"podman/networks/*",
|
|
||||||
"podman/pods/*",
|
|
||||||
"podman/system/*",
|
|
||||||
"podman/system/*",
|
|
||||||
"podman/tests/*",
|
"podman/tests/*",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
# Any changes should be copied into pyproject.toml
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
black
|
black
|
||||||
coverage
|
coverage
|
||||||
fixtures~=3.0.0
|
fixtures~=3.0.0
|
||||||
pytest
|
|
||||||
pylint
|
pylint
|
||||||
|
pytest
|
||||||
requests-mock
|
requests-mock
|
||||||
|
tox
|
||||||
|
|
8
tox.ini
8
tox.ini
|
@ -1,6 +1,6 @@
|
||||||
[tox]
|
[tox]
|
||||||
minversion = 3.2.0
|
minversion = 3.2.0
|
||||||
envlist = py36,py38,py39,py310,pylint,coverage
|
envlist = pylint,coverage,py36,py38,py39,py310
|
||||||
ignore_basepython_conflict = true
|
ignore_basepython_conflict = true
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
|
@ -8,7 +8,11 @@ basepython = python3
|
||||||
usedevelop = True
|
usedevelop = True
|
||||||
install_command = pip install {opts} {packages}
|
install_command = pip install {opts} {packages}
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
commands = pytest
|
commands = pytest {posargs}
|
||||||
|
setenv =
|
||||||
|
PODMAN_LOG_LEVEL = {env:PODMAN_LOG_LEVEL:INFO}
|
||||||
|
PODMAN_BINARY = {env:PODMAN_BINARY:podman}
|
||||||
|
DEBUG = {env:DEBUG:0}
|
||||||
|
|
||||||
[testenv:venv]
|
[testenv:venv]
|
||||||
commands = {posargs}
|
commands = {posargs}
|
||||||
|
|
Loading…
Reference in New Issue