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
|
||||
# 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
|
||||
# pygtk.require().
|
||||
|
|
22
Makefile
22
Makefile
|
@ -8,25 +8,23 @@ DESTDIR ?=
|
|||
EPOCH_TEST_COMMIT ?= $(shell git merge-base $${DEST_BRANCH:-main} HEAD)
|
||||
HEAD ?= HEAD
|
||||
|
||||
export PODMAN_VERSION ?= "3.2.0"
|
||||
export PODMAN_VERSION ?= "4.0.1"
|
||||
|
||||
.PHONY: podman
|
||||
podman:
|
||||
rm dist/* || :
|
||||
python -m pip install --user -r requirements.txt
|
||||
$(PYTHON) -m pip install --user -r requirements.txt
|
||||
PODMAN_VERSION=$(PODMAN_VERSION) \
|
||||
$(PYTHON) setup.py sdist bdist bdist_wheel
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
$(PYTHON) -m pylint podman || exit $$(($$? % 4));
|
||||
lint: tox
|
||||
$(PYTHON) -m tox -e black,pylint
|
||||
|
||||
.PHONY: tests
|
||||
tests:
|
||||
python -m pip install --user -r test-requirements.txt
|
||||
DEBUG=1 coverage run -m unittest discover -s podman/tests
|
||||
coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=.tox/* \
|
||||
--omit=/usr/lib/* --omit=*/lib/python*
|
||||
tests: tox
|
||||
# see tox.ini for environment variable settings
|
||||
$(PYTHON) -m tox -e pylint,coverage,py36,py38,py39,py310
|
||||
|
||||
.PHONY: unittest
|
||||
unittest:
|
||||
|
@ -38,6 +36,12 @@ 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/*
|
||||
|
||||
.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
|
||||
test-release: SOURCE = $(shell find dist -regex '.*/podman-[0-9][0-9\.]*.tar.gz' -print)
|
||||
test-release:
|
||||
|
|
|
@ -2,13 +2,9 @@
|
|||
|
||||
set -xeo pipefail
|
||||
|
||||
mkdir -p "$GOPATH/src/github.com/containers/"
|
||||
cd "$GOPATH/src/github.com/containers/"
|
||||
systemctl stop podman.socket || :
|
||||
|
||||
systemctl stop podman.socket ||:
|
||||
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
|
||||
|
||||
|
||||
|
||||
make tests
|
||||
|
|
|
@ -84,7 +84,7 @@ def create_tar(
|
|||
return None
|
||||
|
||||
# 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)
|
||||
|
||||
# do not leak client information to service
|
||||
|
@ -97,9 +97,8 @@ def create_tar(
|
|||
return info
|
||||
|
||||
if name is None:
|
||||
name = tempfile.NamedTemporaryFile(
|
||||
prefix="podman_context", suffix=".tar"
|
||||
) # pylint: disable=consider-using-with
|
||||
# pylint: disable=consider-using-with
|
||||
name = tempfile.NamedTemporaryFile(prefix="podman_context", suffix=".tar")
|
||||
else:
|
||||
name = pathlib.Path(name)
|
||||
|
||||
|
|
|
@ -884,7 +884,6 @@ elif _geqv_defined:
|
|||
return collections.deque(*args, **kwds)
|
||||
return _generic_new(collections.deque, cls, *args, **kwds)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
class Deque(
|
||||
|
@ -912,7 +911,6 @@ elif hasattr(contextlib, 'AbstractContextManager'):
|
|||
):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
else:
|
||||
|
||||
class ContextManager(typing.Generic[T_co]):
|
||||
|
@ -994,7 +992,6 @@ elif _geqv_defined:
|
|||
return collections.defaultdict(*args, **kwds)
|
||||
return _generic_new(collections.defaultdict, cls, *args, **kwds)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
class DefaultDict(
|
||||
|
@ -1032,7 +1029,6 @@ elif _geqv_defined:
|
|||
return collections.OrderedDict(*args, **kwds)
|
||||
return _generic_new(collections.OrderedDict, cls, *args, **kwds)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
class OrderedDict(
|
||||
|
@ -1073,7 +1069,6 @@ elif (3, 5, 0) <= sys.version_info[:3] <= (3, 5, 1):
|
|||
return collections.Counter(*args, **kwds)
|
||||
return _generic_new(collections.Counter, cls, *args, **kwds)
|
||||
|
||||
|
||||
elif _geqv_defined:
|
||||
|
||||
class Counter(
|
||||
|
@ -1090,7 +1085,6 @@ elif _geqv_defined:
|
|||
return collections.Counter(*args, **kwds)
|
||||
return _generic_new(collections.Counter, cls, *args, **kwds)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
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)
|
||||
namespace.update({'__origin__': origin, '__extra__': extra})
|
||||
self = super(GenericMeta, cls).__new__(cls, name, bases, namespace, _root=True)
|
||||
super(GenericMeta, self).__setattr__(
|
||||
'_gorg', self if not origin else _gorg(origin)
|
||||
)
|
||||
super(GenericMeta, self).__setattr__('_gorg', self if not origin else _gorg(origin))
|
||||
self.__parameters__ = tvars
|
||||
self.__args__ = (
|
||||
tuple(
|
||||
|
@ -1479,9 +1471,7 @@ elif HAVE_PROTOCOLS and not PEP_560:
|
|||
if not isinstance(params, tuple):
|
||||
params = (params,)
|
||||
if not params and _gorg(self) is not Tuple:
|
||||
raise TypeError(
|
||||
"Parameter list to %s[...] cannot be empty" % self.__qualname__
|
||||
)
|
||||
raise TypeError("Parameter list to %s[...] cannot be empty" % self.__qualname__)
|
||||
msg = "Parameters to generic types must be types."
|
||||
params = tuple(_type_check(p, msg) for p in params)
|
||||
if self in (Generic, Protocol):
|
||||
|
@ -2108,7 +2098,6 @@ elif PEP_560:
|
|||
return hint
|
||||
return {k: _strip_annotations(t) for k, t in hint.items()}
|
||||
|
||||
|
||||
elif HAVE_ANNOTATED:
|
||||
|
||||
def _is_dunder(name):
|
||||
|
@ -2344,7 +2333,6 @@ elif sys.version_info[:2] >= (3, 9):
|
|||
"""
|
||||
raise TypeError("{} is not subscriptable".format(self))
|
||||
|
||||
|
||||
elif sys.version_info[:2] >= (3, 7):
|
||||
|
||||
class _TypeAliasForm(typing._SpecialForm, _root=True):
|
||||
|
@ -2672,7 +2660,6 @@ elif sys.version_info[:2] >= (3, 9):
|
|||
"""
|
||||
return _concatenate_getitem(self, parameters)
|
||||
|
||||
|
||||
elif sys.version_info[:2] >= (3, 7):
|
||||
|
||||
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))
|
||||
return _GenericAlias(self, (item,))
|
||||
|
||||
|
||||
elif sys.version_info[:2] >= (3, 7):
|
||||
|
||||
class _TypeGuardForm(typing._SpecialForm, _root=True):
|
||||
|
|
|
@ -74,6 +74,7 @@ class CreateMixin: # pylint: disable=too-few-public-methods
|
|||
process will run as.
|
||||
healthcheck (Dict[str,Any]): Specify a test to perform to check that the
|
||||
container is healthy.
|
||||
health_check_on_failure_action (int): Specify an action if a healthcheck fails.
|
||||
hostname (str): Optional hostname for the container.
|
||||
init (bool): Run an init inside the container that forwards signals and reaps processes
|
||||
init_path (str): Path to the docker-init binary
|
||||
|
@ -310,8 +311,7 @@ class CreateMixin: # pylint: disable=too-few-public-methods
|
|||
if search:
|
||||
return int(search.group(1)) * (1024 ** mapping[search.group(2)])
|
||||
raise TypeError(
|
||||
f"Passed string size {size} should be in format\\d+[bBkKmMgG] (e.g."
|
||||
" '100m')"
|
||||
f"Passed string size {size} should be in format\\d+[bBkKmMgG] (e.g. '100m')"
|
||||
) from bad_size
|
||||
else:
|
||||
raise TypeError(
|
||||
|
@ -341,6 +341,7 @@ class CreateMixin: # pylint: disable=too-few-public-methods
|
|||
"expose": {},
|
||||
"groups": pop("group_add"),
|
||||
"healthconfig": pop("healthcheck"),
|
||||
"health_check_on_failure_action": pop("health_check_on_failure_action"),
|
||||
"hostadd": [],
|
||||
"hostname": pop("hostname"),
|
||||
"httpproxy": pop("use_config_proxy"),
|
||||
|
@ -415,9 +416,7 @@ class CreateMixin: # pylint: disable=too-few-public-methods
|
|||
if "Config" in args["log_config"]:
|
||||
params["log_configuration"]["path"] = args["log_config"]["Config"].get("path")
|
||||
params["log_configuration"]["size"] = args["log_config"]["Config"].get("size")
|
||||
params["log_configuration"]["options"] = args["log_config"]["Config"].get(
|
||||
"options"
|
||||
)
|
||||
params["log_configuration"]["options"] = args["log_config"]["Config"].get("options")
|
||||
args.pop("log_config")
|
||||
|
||||
for item in args.pop("mounts", []):
|
||||
|
|
|
@ -25,10 +25,7 @@ class ContainersManager(RunMixin, CreateMixin, Manager):
|
|||
response = self.client.get(f"/containers/{key}/exists")
|
||||
return response.ok
|
||||
|
||||
# pylint is flagging 'container_id' here vs. 'key' parameter in super.get()
|
||||
def get(
|
||||
self, container_id: str
|
||||
) -> Container: # pylint: disable=arguments-differ,arguments-renamed
|
||||
def get(self, key: str) -> Container:
|
||||
"""Get container by name or id.
|
||||
|
||||
Args:
|
||||
|
@ -38,7 +35,7 @@ class ContainersManager(RunMixin, CreateMixin, Manager):
|
|||
NotFound: when Container does not exist
|
||||
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.raise_for_status()
|
||||
return self.prepare_model(attrs=response.json())
|
||||
|
|
|
@ -27,6 +27,7 @@ class ImagesManager(BuildMixin, Manager):
|
|||
return Image
|
||||
|
||||
def exists(self, key: str) -> bool:
|
||||
"""Return true when image exists."""
|
||||
key = urllib.parse.quote_plus(key)
|
||||
response = self.client.get(f"/images/{key}/exists")
|
||||
return response.ok
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import logging
|
||||
import urllib.parse
|
||||
from contextlib import suppress
|
||||
from typing import List, Optional, Union
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
from podman import api
|
||||
from podman.domain.images import Image
|
||||
|
@ -17,20 +17,18 @@ class Manifest(PodmanResource):
|
|||
|
||||
@property
|
||||
def id(self):
|
||||
"""str: Returns the identifier of the manifest."""
|
||||
"""str: Returns the identifier of the manifest list."""
|
||||
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
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""str: Returns the identifier of the manifest."""
|
||||
try:
|
||||
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
|
||||
"""str: Returns the human-formatted identifier of the manifest list."""
|
||||
return self.attrs.get("names")
|
||||
|
||||
@property
|
||||
def quoted_name(self):
|
||||
|
@ -40,7 +38,7 @@ class Manifest(PodmanResource):
|
|||
@property
|
||||
def names(self):
|
||||
"""List[str]: Returns the identifier of the manifest."""
|
||||
return self.attrs.get("names")
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def media_type(self):
|
||||
|
@ -71,7 +69,7 @@ class Manifest(PodmanResource):
|
|||
ImageNotFound: when Image(s) could not be found
|
||||
APIError: when service reports an error
|
||||
"""
|
||||
params = {
|
||||
data = {
|
||||
"all": kwargs.get("all"),
|
||||
"annotation": kwargs.get("annotation"),
|
||||
"arch": kwargs.get("arch"),
|
||||
|
@ -80,14 +78,15 @@ class Manifest(PodmanResource):
|
|||
"os": kwargs.get("os"),
|
||||
"os_version": kwargs.get("os_version"),
|
||||
"variant": kwargs.get("variant"),
|
||||
"operation": "update",
|
||||
}
|
||||
for item in images:
|
||||
if isinstance(item, Image):
|
||||
item = item.attrs["RepoTags"][0]
|
||||
params["images"].append(item)
|
||||
data["images"].append(item)
|
||||
|
||||
data = api.prepare_body(params)
|
||||
response = self.client.post(f"/manifests/{self.quoted_name}/add", data=data)
|
||||
data = api.prepare_body(data)
|
||||
response = self.client.put(f"/manifests/{self.quoted_name}", data=data)
|
||||
response.raise_for_status(not_found=ImageNotFound)
|
||||
return self.reload()
|
||||
|
||||
|
@ -127,7 +126,10 @@ class Manifest(PodmanResource):
|
|||
if "@" in digest:
|
||||
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)
|
||||
return self.reload()
|
||||
|
||||
|
@ -147,14 +149,14 @@ class ManifestsManager(Manager):
|
|||
|
||||
def create(
|
||||
self,
|
||||
names: List[str],
|
||||
name: str,
|
||||
images: Optional[List[Union[Image, str]]] = None,
|
||||
all: Optional[bool] = None, # pylint: disable=redefined-builtin
|
||||
) -> Manifest:
|
||||
"""Create a Manifest.
|
||||
|
||||
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.
|
||||
all: When True, add all contents from images given.
|
||||
|
||||
|
@ -162,26 +164,24 @@ class ManifestsManager(Manager):
|
|||
ValueError: when no names are provided
|
||||
NotFoundImage: when a given image does not exist
|
||||
"""
|
||||
if names is None or len(names) == 0:
|
||||
raise ValueError("At least one manifest name is required.")
|
||||
|
||||
params = {"name": names}
|
||||
params: Dict[str, Any] = {}
|
||||
if images is not None:
|
||||
params["image"] = []
|
||||
params["images"] = []
|
||||
for item in images:
|
||||
if isinstance(item, Image):
|
||||
item = item.attrs["RepoTags"][0]
|
||||
params["image"].append(item)
|
||||
params["images"].append(item)
|
||||
|
||||
if all is not None:
|
||||
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)
|
||||
|
||||
body = response.json()
|
||||
manifest = self.get(body["Id"])
|
||||
manifest.attrs["names"] = names
|
||||
manifest.attrs["names"] = name
|
||||
|
||||
if manifest.attrs["manifests"] is None:
|
||||
manifest.attrs["manifests"] = []
|
||||
|
@ -198,9 +198,6 @@ class ManifestsManager(Manager):
|
|||
To have Manifest conform with other PodmanResource's, we use the key that
|
||||
retrieved the Manifest be its name.
|
||||
|
||||
See https://issues.redhat.com/browse/RUN-1217 for details on refactoring Podman service
|
||||
manifests API.
|
||||
|
||||
Args:
|
||||
key: Manifest name for which to search
|
||||
|
||||
|
@ -213,10 +210,23 @@ class ManifestsManager(Manager):
|
|||
response.raise_for_status()
|
||||
|
||||
body = response.json()
|
||||
body["names"] = [key]
|
||||
if "names" not in body:
|
||||
body["names"] = key
|
||||
return self.prepare_model(attrs=body)
|
||||
|
||||
def list(self, **kwargs) -> List[Manifest]:
|
||||
"""Not Implemented."""
|
||||
|
||||
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
|
||||
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:
|
||||
net = client.networks.get("db_network")
|
||||
print(net.name, "\n")
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
|
@ -42,11 +44,12 @@ class Network(PodmanResource):
|
|||
with suppress(KeyError):
|
||||
container_manager = ContainersManager(client=self.client)
|
||||
return [container_manager.get(ident) for ident in self.attrs["Containers"].keys()]
|
||||
return {}
|
||||
return []
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""str: Returns the name of the network."""
|
||||
|
||||
if "Name" in self.attrs:
|
||||
return self.attrs["Name"]
|
||||
|
||||
|
@ -68,7 +71,6 @@ class Network(PodmanResource):
|
|||
|
||||
Keyword Args:
|
||||
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
|
||||
ipv4_address (str): IPv4 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:
|
||||
APIError: when Podman service reports an error
|
||||
"""
|
||||
compatible = kwargs.get("compatible", True)
|
||||
|
||||
if isinstance(container, Container):
|
||||
container = container.id
|
||||
|
||||
|
@ -110,7 +110,6 @@ class Network(PodmanResource):
|
|||
f"/networks/{self.name}/connect",
|
||||
data=json.dumps(data),
|
||||
headers={"Content-type": "application/json"},
|
||||
compatible=compatible,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
@ -126,15 +125,11 @@ class Network(PodmanResource):
|
|||
Raises:
|
||||
APIError: when Podman service reports an error
|
||||
"""
|
||||
compatible = kwargs.get("compatible", True)
|
||||
|
||||
if isinstance(container, Container):
|
||||
container = container.id
|
||||
|
||||
data = {"Container": container, "Force": kwargs.get("force")}
|
||||
response = self.client.post(
|
||||
f"/networks/{self.name}/disconnect", data=json.dumps(data), compatible=compatible
|
||||
)
|
||||
response = self.client.post(f"/networks/{self.name}/disconnect", data=json.dumps(data))
|
||||
response.raise_for_status()
|
||||
|
||||
def remove(self, force: Optional[bool] = None, **kwargs) -> None:
|
||||
|
@ -143,9 +138,6 @@ class Network(PodmanResource):
|
|||
Args:
|
||||
force: Remove network and any associated containers
|
||||
|
||||
Keyword Args:
|
||||
compatible (bool): Should compatible API be used. Default: True
|
||||
|
||||
Raises:
|
||||
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
|
||||
libpod API as the results are so different. To use the libpod API add the keyword argument
|
||||
compatible=False to any method call.
|
||||
Classes and methods for manipulating network resources via Podman API service.
|
||||
|
||||
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 logging
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import podman.api.http_utils
|
||||
from podman import api
|
||||
from podman.api import http_utils
|
||||
from podman.domain.manager import Manager
|
||||
from podman.domain.networks import Network
|
||||
from podman.errors import APIError
|
||||
|
@ -27,7 +32,7 @@ class NetworksManager(Manager):
|
|||
return Network
|
||||
|
||||
def create(self, name: str, **kwargs) -> Network:
|
||||
"""Create a Network.
|
||||
"""Create a Network resource.
|
||||
|
||||
Args:
|
||||
name: Name of network to be created
|
||||
|
@ -35,14 +40,13 @@ class NetworksManager(Manager):
|
|||
Keyword Args:
|
||||
attachable (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.
|
||||
enable_ipv6 (bool): Enable IPv6 on the network.
|
||||
ingress (bool): Ignored, always False.
|
||||
internal (bool): Restrict external access to the network.
|
||||
ipam (IPAMConfig): Optional custom IP scheme for the network.
|
||||
labels (Dict[str, str]): Map of labels to set on the network.
|
||||
macvlan (str):
|
||||
options (Dict[str, Any]): Driver options.
|
||||
scope (str): Ignored, always "local".
|
||||
|
||||
|
@ -50,85 +54,66 @@ class NetworksManager(Manager):
|
|||
APIError: when Podman service reports an error
|
||||
"""
|
||||
data = {
|
||||
"DisabledDNS": kwargs.get("disabled_dns"),
|
||||
"Driver": kwargs.get("driver"),
|
||||
"Internal": kwargs.get("internal"),
|
||||
"IPv6": kwargs.get("enable_ipv6"),
|
||||
"Labels": kwargs.get("labels"),
|
||||
"MacVLAN": kwargs.get("macvlan"),
|
||||
"Options": kwargs.get("options"),
|
||||
"name": name,
|
||||
"driver": kwargs.get("driver"),
|
||||
"dns_enabled": kwargs.get("dns_enabled"),
|
||||
"subnets": kwargs.get("subnets"),
|
||||
"ipv6_enabled": kwargs.get("enable_ipv6"),
|
||||
"internal": kwargs.get("internal"),
|
||||
"labels": kwargs.get("labels"),
|
||||
"options": kwargs.get("options"),
|
||||
}
|
||||
|
||||
with suppress(KeyError):
|
||||
ipam = 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,
|
||||
}
|
||||
self._prepare_ipam(data, kwargs["ipam"])
|
||||
|
||||
response = self.client.post(
|
||||
"/networks/create",
|
||||
params={"name": name},
|
||||
data=podman.api.http_utils.prepare_body(data),
|
||||
data=http_utils.prepare_body(data),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
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:
|
||||
response = self.client.get(f"/networks/{key}/exists")
|
||||
return response.ok
|
||||
|
||||
# pylint is flagging 'network_id' here vs. 'key' parameter in super.get()
|
||||
def get(self, network_id: str, *_, **kwargs) -> Network: # pylint: disable=arguments-differ
|
||||
def get(self, key: str) -> Network:
|
||||
"""Return information for the network_id.
|
||||
|
||||
Args:
|
||||
network_id: Network name or id.
|
||||
|
||||
Keyword Args:
|
||||
compatible (bool): Should compatible API be used. Default: True
|
||||
key: Network name or id.
|
||||
|
||||
Raises:
|
||||
NotFound: when Network does not exist
|
||||
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)
|
||||
|
||||
path = f"/networks/{network_id}" + ("" if compatible else "/json")
|
||||
|
||||
response = self.client.get(path, compatible=compatible)
|
||||
response = self.client.get(f"/networks/{key}")
|
||||
response.raise_for_status()
|
||||
|
||||
body = response.json()
|
||||
if not compatible:
|
||||
body = body[0]
|
||||
|
||||
return self.prepare_model(attrs=body)
|
||||
return self.prepare_model(attrs=response.json())
|
||||
|
||||
def list(self, **kwargs) -> List[Network]:
|
||||
"""Report on networks.
|
||||
|
@ -162,23 +147,19 @@ class NetworksManager(Manager):
|
|||
Raises:
|
||||
APIError: when error returned by service
|
||||
"""
|
||||
compatible = kwargs.get("compatible", True)
|
||||
|
||||
filters = kwargs.get("filters", {})
|
||||
filters["name"] = kwargs.get("names")
|
||||
filters["id"] = kwargs.get("ids")
|
||||
filters = api.prepare_filters(filters)
|
||||
|
||||
params = {"filters": filters}
|
||||
path = f"/networks{'' if compatible else '/json'}"
|
||||
|
||||
response = self.client.get(path, params=params, compatible=compatible)
|
||||
response = self.client.get("/networks/json", params=params)
|
||||
response.raise_for_status()
|
||||
|
||||
return [self.prepare_model(i) for i in response.json()]
|
||||
|
||||
def prune(
|
||||
self, filters: Optional[Dict[str, Any]] = None, **kwargs
|
||||
self, filters: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[api.Literal["NetworksDeleted", "SpaceReclaimed"], Any]:
|
||||
"""Delete unused Networks.
|
||||
|
||||
|
@ -187,25 +168,14 @@ class NetworksManager(Manager):
|
|||
Args:
|
||||
filters: Criteria for selecting volumes to delete. Ignored.
|
||||
|
||||
Keyword Args:
|
||||
compatible (bool): Should compatible API be used. Default: True
|
||||
|
||||
Raises:
|
||||
APIError: when service reports error
|
||||
"""
|
||||
compatible = kwargs.get("compatible", True)
|
||||
|
||||
response = self.client.post(
|
||||
"/networks/prune", filters=api.prepare_filters(filters), compatible=compatible
|
||||
)
|
||||
response = self.client.post("/networks/prune", filters=api.prepare_filters(filters))
|
||||
response.raise_for_status()
|
||||
|
||||
body = response.json()
|
||||
if compatible:
|
||||
return body
|
||||
|
||||
deleted: List[str] = []
|
||||
for item in body:
|
||||
for item in response.json():
|
||||
if item["Error"] is not None:
|
||||
raise APIError(
|
||||
item["Error"],
|
||||
|
@ -216,27 +186,18 @@ class NetworksManager(Manager):
|
|||
|
||||
return {"NetworksDeleted": deleted, "SpaceReclaimed": 0}
|
||||
|
||||
def remove(self, name: [Network, str], force: Optional[bool] = None, **kwargs) -> None:
|
||||
"""Remove this network.
|
||||
def remove(self, name: [Network, str], force: Optional[bool] = None) -> None:
|
||||
"""Remove Network resource.
|
||||
|
||||
Args:
|
||||
name: Identifier of Network to delete.
|
||||
force: Remove network and any associated containers
|
||||
|
||||
Keyword Args:
|
||||
compatible (bool): Should compatible API be used. Default: True
|
||||
|
||||
Raises:
|
||||
APIError: when Podman service reports an error
|
||||
|
||||
Notes:
|
||||
Podman only.
|
||||
"""
|
||||
if isinstance(name, Network):
|
||||
name = name.name
|
||||
|
||||
compatible = kwargs.get("compatible", True)
|
||||
response = self.client.delete(
|
||||
f"/networks/{name}", params={"force": force}, compatible=compatible
|
||||
)
|
||||
response = self.client.delete(f"/networks/{name}", params={"force": force})
|
||||
response.raise_for_status()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Model and Manager for Pod resources."""
|
||||
import logging
|
||||
from typing import Any, Dict, Tuple, Union, Optional
|
||||
from typing import Any, Dict, Optional, Tuple, Union
|
||||
|
||||
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.raise_for_status()
|
||||
|
||||
if len(response.text) == 0:
|
||||
return {"Processes": [], "Titles": []}
|
||||
return response.json()
|
||||
|
||||
def unpause(self) -> None:
|
||||
|
|
|
@ -94,9 +94,7 @@ class PodsManager(Manager):
|
|||
Raises:
|
||||
APIError: when service reports error
|
||||
"""
|
||||
response = self.client.post(
|
||||
"/pods/prune", params={"filters": api.prepare_filters(filters)}
|
||||
)
|
||||
response = self.client.post("/pods/prune", params={"filters": api.prepare_filters(filters)})
|
||||
response.raise_for_status()
|
||||
|
||||
deleted: List[str] = []
|
||||
|
|
|
@ -3,5 +3,5 @@
|
|||
# Do not auto-update these from version.py,
|
||||
# as test code should be changed to reflect changes in Podman API versions
|
||||
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"
|
||||
|
|
|
@ -37,24 +37,22 @@ class IntegrationTest(fixtures.TestWithFixtures):
|
|||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
super(fixtures.TestWithFixtures, cls).setUpClass()
|
||||
|
||||
command = os.environ.get("PODMAN_BINARY", "podman")
|
||||
if shutil.which(command) is None:
|
||||
raise AssertionError(f"'{command}' not found.")
|
||||
IntegrationTest.podman = command
|
||||
|
||||
# For testing, lock in logging configuration
|
||||
if "DEBUG" in os.environ:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
# This log_level is for our python code
|
||||
log_level = os.environ.get("PODMAN_LOG_LEVEL", "INFO")
|
||||
log_level = logging.getLevelName(log_level)
|
||||
logging.basicConfig(level=log_level)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# This is the log_level to pass to podman service
|
||||
self.log_level = logging.WARNING
|
||||
if "DEBUG" in os.environ:
|
||||
self.log_level = logging.DEBUG
|
||||
self.log_level = os.environ.get("PODMAN_LOG_LEVEL", "INFO")
|
||||
|
||||
self.test_dir = self.useFixture(fixtures.TempDir()).path
|
||||
self.socket_file = os.path.join(self.test_dir, uuid.uuid4().hex)
|
||||
|
|
|
@ -67,9 +67,13 @@ class ContainersIntegrationTest(base.IntegrationTest):
|
|||
test['expected_value'],
|
||||
)
|
||||
|
||||
def test_container_kernel_memory(self):
|
||||
"""Test passing kernel memory"""
|
||||
self._test_memory_limit('kernel_memory', 'KernelMemory')
|
||||
def test_container_healtchecks(self):
|
||||
"""Test passing various healthcheck options"""
|
||||
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):
|
||||
"""Test passing memory limit"""
|
||||
|
|
|
@ -105,9 +105,7 @@ class ContainersIntegrationTest(base.IntegrationTest):
|
|||
self.assertIsInstance(logs_iter, Iterator)
|
||||
|
||||
logs = list(logs_iter)
|
||||
self.assertIn(random_string.encode("utf-8"), logs)
|
||||
# podman 4.0 API support...
|
||||
# self.assertIn((random_string + "\n").encode("utf-8"), logs)
|
||||
self.assertIn((random_string + "\n").encode("utf-8"), logs)
|
||||
|
||||
with self.subTest("Delete Container"):
|
||||
container.remove()
|
||||
|
|
|
@ -25,23 +25,25 @@ class ManifestsIntegrationTest(base.IntegrationTest):
|
|||
|
||||
self.client.images.remove(self.alpine_image, force=True)
|
||||
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):
|
||||
"""Test Manifest CRUD."""
|
||||
|
||||
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",
|
||||
)
|
||||
|
||||
with self.subTest("Create"):
|
||||
manifest = self.client.manifests.create(["quay.io/unittest/alpine:latest"])
|
||||
self.assertEqual(len(manifest.attrs["manifests"]), 0)
|
||||
self.assertTrue(self.client.manifests.exists(manifest.id))
|
||||
manifest = self.client.manifests.create(
|
||||
"localhost/unittest/alpine", ["quay.io/libpod/alpine:latest"]
|
||||
)
|
||||
self.assertEqual(len(manifest.attrs["manifests"]), 1, manifest.attrs)
|
||||
self.assertTrue(self.client.manifests.exists(manifest.names), manifest.id)
|
||||
|
||||
with self.assertRaises(APIError):
|
||||
self.client.manifests.create(["123456!@#$%^"])
|
||||
self.client.manifests.create("123456!@#$%^")
|
||||
|
||||
with self.subTest("Add"):
|
||||
manifest.add([self.alpine_image])
|
||||
|
@ -54,12 +56,14 @@ class ManifestsIntegrationTest(base.IntegrationTest):
|
|||
)
|
||||
|
||||
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)
|
||||
|
||||
actual = self.client.manifests.get(manifest.name)
|
||||
self.assertEqual(actual.id, manifest.id)
|
||||
|
||||
self.assertEqual(actual.version, 2)
|
||||
|
||||
with self.subTest("Remove digest"):
|
||||
manifest.remove(self.alpine_image.attrs["RepoDigests"][0])
|
||||
self.assertEqual(len(manifest.attrs["manifests"]), 0)
|
||||
|
@ -67,7 +71,7 @@ class ManifestsIntegrationTest(base.IntegrationTest):
|
|||
def test_create_409(self):
|
||||
"""Test that invalid Image names are caught and not corrupt storage."""
|
||||
with self.assertRaises(APIError):
|
||||
self.client.manifests.create([self.invalid_manifest_name])
|
||||
self.client.manifests.create(self.invalid_manifest_name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -27,26 +27,29 @@ from podman.errors import NotFound
|
|||
class NetworksIntegrationTest(base.IntegrationTest):
|
||||
"""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])
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.client = PodmanClient(base_url=self.socket_uri)
|
||||
self.addCleanup(self.client.close)
|
||||
|
||||
def tearDown(self):
|
||||
with suppress(NotFound):
|
||||
self.client.networks.get("integration_test").remove(force=True)
|
||||
|
||||
super().tearDown()
|
||||
|
||||
def test_network_crud(self):
|
||||
"""integration: networks create and remove calls"""
|
||||
|
||||
with self.subTest("Create Network"):
|
||||
network = self.client.networks.create(
|
||||
"integration_test",
|
||||
disabled_dns=True,
|
||||
enable_ipv6=False,
|
||||
dns_enabled=False,
|
||||
ipam=NetworksIntegrationTest.ipam,
|
||||
)
|
||||
self.assertEqual(network.name, "integration_test")
|
||||
|
@ -68,7 +71,7 @@ class NetworksIntegrationTest(base.IntegrationTest):
|
|||
with self.assertRaises(NotFound):
|
||||
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):
|
||||
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.pod_name = f"pod_{random.getrandbits(160):x}"
|
||||
|
||||
# TODO should this use podman binary instead?
|
||||
for container in self.client.containers.list():
|
||||
container.remove(force=True)
|
||||
|
||||
def tearDown(self):
|
||||
if self.client.pods.exists(self.pod_name):
|
||||
self.client.pods.remove(self.pod_name)
|
||||
super().tearDown()
|
||||
|
||||
def test_pod_crud(self):
|
||||
"""Test Pod CRUD."""
|
||||
|
@ -62,6 +62,9 @@ class PodsIntegrationTest(base.IntegrationTest):
|
|||
with self.assertRaises(NotFound):
|
||||
pod.reload()
|
||||
|
||||
def test_pod_crud_infra(self):
|
||||
"""Test Pod CRUD with infra container."""
|
||||
|
||||
with self.subTest("Create with infra"):
|
||||
pod = self.client.pods.create(
|
||||
self.pod_name,
|
||||
|
@ -75,22 +78,7 @@ class PodsIntegrationTest(base.IntegrationTest):
|
|||
actual = self.client.pods.get(pod.id)
|
||||
self.assertEqual(actual.name, pod.name)
|
||||
self.assertIn("Containers", actual.attrs)
|
||||
|
||||
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")
|
||||
self.assertEqual(actual.attrs["State"], "Created")
|
||||
|
||||
with self.subTest("Add container"):
|
||||
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"]}
|
||||
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"):
|
||||
pods = self.client.pods.list()
|
||||
self.assertGreaterEqual(len(pods), 1)
|
||||
|
@ -112,15 +94,64 @@ class PodsIntegrationTest(base.IntegrationTest):
|
|||
ids = {p.id for p in pods}
|
||||
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"):
|
||||
pod.remove(force=True)
|
||||
with self.assertRaises(NotFound):
|
||||
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__':
|
||||
unittest.main()
|
||||
|
|
|
@ -18,6 +18,7 @@ import os
|
|||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
from contextlib import suppress
|
||||
from typing import List, Optional
|
||||
|
||||
import time
|
||||
|
@ -36,7 +37,7 @@ class PodmanLauncher:
|
|||
podman_path: Optional[str] = None,
|
||||
timeout: int = 0,
|
||||
privileged: bool = False,
|
||||
log_level: int = logging.WARNING,
|
||||
log_level: str = "WARNING",
|
||||
) -> None:
|
||||
"""create a launcher and build podman command"""
|
||||
podman_exe: str = podman_path
|
||||
|
@ -57,7 +58,10 @@ class PodmanLauncher:
|
|||
|
||||
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":
|
||||
self.cmd.append("--storage-driver=vfs")
|
||||
|
@ -121,4 +125,7 @@ class PodmanLauncher:
|
|||
return_code = self.proc.wait()
|
||||
self.proc = None
|
||||
|
||||
with suppress(FileNotFoundError):
|
||||
os.remove(self.socket_file)
|
||||
|
||||
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")
|
||||
self.assertEqual(config.active_service.url, expected)
|
||||
self.assertEqual(
|
||||
config.services["production"].identity, Path("/home/root/.ssh/id_rsa")
|
||||
)
|
||||
self.assertEqual(config.services["production"].identity, Path("/home/root/.ssh/id_rsa"))
|
||||
|
||||
PodmanConfigTestCase.opener.assert_called_with(
|
||||
Path("/home/developer/containers.conf"), encoding='utf-8'
|
||||
|
|
|
@ -242,9 +242,7 @@ class ContainersManagerTestCase(unittest.TestCase):
|
|||
json=FIRST_CONTAINER,
|
||||
)
|
||||
|
||||
with patch.multiple(
|
||||
Container, logs=DEFAULT, wait=DEFAULT, autospec=True
|
||||
) as mock_container:
|
||||
with patch.multiple(Container, logs=DEFAULT, wait=DEFAULT, autospec=True) as mock_container:
|
||||
mock_container["logs"].return_value = []
|
||||
mock_container["wait"].return_value = {"StatusCode": 0}
|
||||
|
||||
|
@ -277,9 +275,7 @@ class ContainersManagerTestCase(unittest.TestCase):
|
|||
b"This is a unittest - line 2",
|
||||
)
|
||||
|
||||
with patch.multiple(
|
||||
Container, logs=DEFAULT, wait=DEFAULT, autospec=True
|
||||
) as mock_container:
|
||||
with patch.multiple(Container, logs=DEFAULT, wait=DEFAULT, autospec=True) as mock_container:
|
||||
mock_container["wait"].return_value = {"StatusCode": 0}
|
||||
|
||||
with self.subTest("Results not streamed"):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import unittest
|
||||
|
||||
from podman import PodmanClient, tests
|
||||
from podman.domain.manifests import ManifestsManager, Manifest
|
||||
from podman.domain.manifests import Manifest, ManifestsManager
|
||||
|
||||
|
||||
class ManifestTestCase(unittest.TestCase):
|
||||
|
@ -9,11 +9,7 @@ class ManifestTestCase(unittest.TestCase):
|
|||
super().setUp()
|
||||
|
||||
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
|
||||
self.client.close()
|
||||
self.addCleanup(self.client.close)
|
||||
|
||||
def test_podmanclient(self):
|
||||
manager = self.client.manifests
|
||||
|
@ -24,13 +20,8 @@ class ManifestTestCase(unittest.TestCase):
|
|||
self.client.manifests.list()
|
||||
|
||||
def test_name(self):
|
||||
with self.assertRaises(ValueError):
|
||||
manifest = Manifest(attrs={"names": ""})
|
||||
_ = manifest.name
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
manifest = Manifest()
|
||||
_ = manifest.name
|
||||
manifest = Manifest()
|
||||
self.assertIsNone(manifest.name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -28,29 +28,29 @@ FIRST_NETWORK = {
|
|||
"Labels": {},
|
||||
}
|
||||
|
||||
FIRST_NETWORK_LIBPOD = [
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "podman",
|
||||
"plugins": [
|
||||
{
|
||||
"bridge": "cni-podman0",
|
||||
"hairpinMode": True,
|
||||
"ipMasq": True,
|
||||
"ipam": {
|
||||
"ranges": [[{"gateway": "10.88.0.1", "subnet": "10.88.0.0/16"}]],
|
||||
"routes": [{"dst": "0.0.0.0/0"}],
|
||||
"type": "host-local",
|
||||
},
|
||||
"isGateway": True,
|
||||
"type": "bridge",
|
||||
FIRST_NETWORK_LIBPOD = {
|
||||
"name": "podman",
|
||||
"id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
|
||||
"driver": "bridge",
|
||||
"network_interface": "libpod_veth0",
|
||||
"created": "2022-01-28T09:18:37.491308364-07:00",
|
||||
"subnets": [
|
||||
{
|
||||
"subnet": "10.11.12.0/24",
|
||||
"gateway": "10.11.12.1",
|
||||
"lease_range": {
|
||||
"start_ip": "10.11.12.1",
|
||||
"end_ip": "10.11.12.63",
|
||||
},
|
||||
{"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):
|
||||
|
@ -58,11 +58,7 @@ class NetworkTestCase(unittest.TestCase):
|
|||
super().setUp()
|
||||
|
||||
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
|
||||
self.client.close()
|
||||
self.addCleanup(self.client.close)
|
||||
|
||||
def test_id(self):
|
||||
expected = {"Id": "1cf06390-709d-4ffa-a054-c3083abe367c"}
|
||||
|
@ -84,7 +80,7 @@ class NetworkTestCase(unittest.TestCase):
|
|||
@requests_mock.Mocker()
|
||||
def test_remove(self, mock):
|
||||
adapter = mock.delete(
|
||||
tests.COMPATIBLE_URL + "/networks/podman?force=True",
|
||||
tests.LIBPOD_URL + "/networks/podman?force=True",
|
||||
status_code=204,
|
||||
json={"Name": "podman", "Err": None},
|
||||
)
|
||||
|
@ -96,7 +92,7 @@ class NetworkTestCase(unittest.TestCase):
|
|||
|
||||
@requests_mock.Mocker()
|
||||
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.connect(
|
||||
|
@ -120,7 +116,7 @@ class NetworkTestCase(unittest.TestCase):
|
|||
|
||||
@requests_mock.Mocker()
|
||||
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.disconnect("podman_ctnr", force=True)
|
||||
|
|
|
@ -3,7 +3,6 @@ import unittest
|
|||
import requests_mock
|
||||
|
||||
from podman import PodmanClient, tests
|
||||
from podman.domain.ipam import IPAMConfig, IPAMPool
|
||||
from podman.domain.networks import Network
|
||||
from podman.domain.networks_manager import NetworksManager
|
||||
|
||||
|
@ -51,53 +50,53 @@ SECOND_NETWORK = {
|
|||
"Labels": {},
|
||||
}
|
||||
|
||||
FIRST_NETWORK_LIBPOD = [
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "podman",
|
||||
"plugins": [
|
||||
{
|
||||
"bridge": "cni-podman0",
|
||||
"hairpinMode": True,
|
||||
"ipMasq": True,
|
||||
"ipam": {
|
||||
"ranges": [[{"gateway": "10.88.0.1", "subnet": "10.88.0.0/16"}]],
|
||||
"routes": [{"dst": "0.0.0.0/0"}],
|
||||
"type": "host-local",
|
||||
},
|
||||
"isGateway": True,
|
||||
"type": "bridge",
|
||||
FIRST_NETWORK_LIBPOD = {
|
||||
"name": "podman",
|
||||
"id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
|
||||
"driver": "bridge",
|
||||
"network_interface": "libpod_veth0",
|
||||
"created": "2022-01-28T09:18:37.491308364-07:00",
|
||||
"subnets": [
|
||||
{
|
||||
"subnet": "10.11.12.0/24",
|
||||
"gateway": "10.11.12.1",
|
||||
"lease_range": {
|
||||
"start_ip": "10.11.12.1",
|
||||
"end_ip": "10.11.12.63",
|
||||
},
|
||||
{"capabilities": {"portMappings": True}, "type": "portmap"},
|
||||
{"type": "firewall"},
|
||||
{"type": "tuning"},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ipv6_enabled": False,
|
||||
"internal": False,
|
||||
"dns_enabled": False,
|
||||
"labels": {},
|
||||
"options": {},
|
||||
"ipam_options": {},
|
||||
}
|
||||
|
||||
SECOND_NETWORK_LIBPOD = [
|
||||
{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "database",
|
||||
"plugins": [
|
||||
{
|
||||
"bridge": "cni-podman0",
|
||||
"hairpinMode": True,
|
||||
"ipMasq": True,
|
||||
"ipam": {
|
||||
"ranges": [[{"gateway": "10.88.0.1", "subnet": "10.88.0.0/16"}]],
|
||||
"routes": [{"dst": "0.0.0.0/0"}],
|
||||
"type": "host-local",
|
||||
},
|
||||
"isGateway": True,
|
||||
"type": "bridge",
|
||||
SECOND_NETWORK_LIBPOD = {
|
||||
"name": "database",
|
||||
"id": "3549b0028b75d981cdda2e573e9cb49dedc200185876df299f912b79f69dabd8",
|
||||
"created": "2021-03-01T09:18:37.491308364-07:00",
|
||||
"driver": "bridge",
|
||||
"network_interface": "libpod_veth1",
|
||||
"subnets": [
|
||||
{
|
||||
"subnet": "10.11.12.0/24",
|
||||
"gateway": "10.11.12.1",
|
||||
"lease_range": {
|
||||
"start_ip": "10.11.12.1",
|
||||
"end_ip": "10.11.12.63",
|
||||
},
|
||||
{"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):
|
||||
|
@ -112,11 +111,7 @@ class NetworksManagerTestCase(unittest.TestCase):
|
|||
super().setUp()
|
||||
|
||||
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
|
||||
self.client.close()
|
||||
self.addCleanup(self.client.close)
|
||||
|
||||
def test_podmanclient(self):
|
||||
manager = self.client.networks
|
||||
|
@ -124,10 +119,7 @@ class NetworksManagerTestCase(unittest.TestCase):
|
|||
|
||||
@requests_mock.Mocker()
|
||||
def test_get(self, mock):
|
||||
mock.get(
|
||||
tests.COMPATIBLE_URL + "/networks/podman",
|
||||
json=FIRST_NETWORK,
|
||||
)
|
||||
mock.get(tests.LIBPOD_URL + "/networks/podman", json=FIRST_NETWORK)
|
||||
|
||||
actual = self.client.networks.get("podman")
|
||||
self.assertIsInstance(actual, Network)
|
||||
|
@ -135,47 +127,14 @@ class NetworksManagerTestCase(unittest.TestCase):
|
|||
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()
|
||||
def test_list_libpod(self, mock):
|
||||
mock.get(
|
||||
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.assertIsInstance(actual[0], Network)
|
||||
|
@ -191,68 +150,33 @@ class NetworksManagerTestCase(unittest.TestCase):
|
|||
self.assertEqual(actual[1].name, "database")
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_create(self, mock):
|
||||
adapter = mock.post(
|
||||
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,
|
||||
)
|
||||
def test_create_libpod(self, mock):
|
||||
adapter = mock.post(tests.LIBPOD_URL + "/networks/create", json=FIRST_NETWORK_LIBPOD)
|
||||
|
||||
pool = IPAMPool(subnet="172.16.0.0/12", iprange="172.16.0.0/16", gateway="172.31.255.254")
|
||||
ipam = IPAMConfig(pool_configs=[pool])
|
||||
|
||||
network = self.client.networks.create(
|
||||
"podman", disabled_dns=True, enable_ipv6=False, ipam=ipam
|
||||
)
|
||||
network = self.client.networks.create("podman", dns_enabled=True, enable_ipv6=True)
|
||||
self.assertIsInstance(network, Network)
|
||||
|
||||
self.assertEqual(adapter.call_count, 1)
|
||||
self.assertDictEqual(
|
||||
adapter.last_request.json(),
|
||||
{
|
||||
'DisabledDNS': True,
|
||||
'Gateway': '172.31.255.254',
|
||||
'IPv6': False,
|
||||
'Range': {'IP': '172.16.0.0', 'Mask': "//8AAA=="},
|
||||
'Subnet': {'IP': '172.16.0.0', 'Mask': "//AAAA=="},
|
||||
"name": "podman",
|
||||
"ipv6_enabled": True,
|
||||
"dns_enabled": True,
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(network.name, "podman")
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_create_defaults(self, mock):
|
||||
adapter = mock.post(
|
||||
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,
|
||||
)
|
||||
adapter = mock.post(tests.LIBPOD_URL + "/networks/create", json=FIRST_NETWORK_LIBPOD)
|
||||
|
||||
network = self.client.networks.create("podman")
|
||||
self.assertEqual(adapter.call_count, 1)
|
||||
self.assertEqual(network.name, "podman")
|
||||
self.assertEqual(len(adapter.last_request.json()), 0)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_prune(self, mock):
|
||||
mock.post(
|
||||
tests.COMPATIBLE_URL + "/networks/prune",
|
||||
json={"NetworksDeleted": ["podman", "database"]},
|
||||
self.assertDictEqual(
|
||||
adapter.last_request.json(),
|
||||
{"name": "podman"},
|
||||
)
|
||||
|
||||
actual = self.client.networks.prune()
|
||||
self.assertListEqual(actual["NetworksDeleted"], ["podman", "database"])
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_prune_libpod(self, mock):
|
||||
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"])
|
||||
|
||||
|
||||
|
|
|
@ -38,11 +38,7 @@ class VolumesManagerTestCase(unittest.TestCase):
|
|||
super().setUp()
|
||||
|
||||
self.client = PodmanClient(base_url=tests.BASE_SOCK)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
super().tearDown()
|
||||
|
||||
self.client.close()
|
||||
self.addCleanup(self.client.close)
|
||||
|
||||
def test_podmanclient(self):
|
||||
manager = self.client.volumes
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
"""Version of PodmanPy."""
|
||||
|
||||
__version__ = "3.2.1"
|
||||
__version__ = "4.0.1"
|
||||
__compatible_version__ = "1.40"
|
||||
|
|
|
@ -19,12 +19,20 @@ exclude = '''
|
|||
profile = "black"
|
||||
line_length = 100
|
||||
[build-system]
|
||||
# Any changes should be copied into requirements.txt, setup.cfg, and/or test-requirements.txt
|
||||
requires = [
|
||||
"pyxdg>=0.26",
|
||||
"requests>=2.24",
|
||||
"setuptools>=46.4",
|
||||
"sphinx",
|
||||
"toml>=0.10.2",
|
||||
"urllib3>=1.24.2",
|
||||
"pyxdg>=0.26",
|
||||
"setuptools>=46.4",
|
||||
"wheel",
|
||||
]
|
||||
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
|
||||
setuptools
|
||||
sphinx
|
||||
toml>=0.10.2
|
||||
urllib3>=1.24.2
|
||||
pyxdg>=0.26
|
||||
sphinx
|
||||
wheel
|
||||
setuptools
|
||||
|
|
|
@ -31,11 +31,12 @@ keywords = podman, libpod
|
|||
include_package_data = True
|
||||
python_requires = >=3.6
|
||||
test_suite =
|
||||
# Any changes should be copied into pyproject.toml
|
||||
install_requires =
|
||||
pyxdg>=0.26
|
||||
requests>=2.24
|
||||
toml>=0.10.2
|
||||
urllib3>=1.24.2
|
||||
pyxdg>=0.26
|
||||
|
||||
# typing_extensions are included for RHEL 8.5
|
||||
# typing_extensions;python_version<'3.8'
|
||||
|
|
12
setup.py
12
setup.py
|
@ -1,18 +1,10 @@
|
|||
import setuptools
|
||||
|
||||
import fnmatch
|
||||
|
||||
import setuptools
|
||||
from setuptools import find_packages
|
||||
from setuptools.command.build_py import build_py as build_py_orig
|
||||
|
||||
excluded = [
|
||||
"podman/api_connection.py",
|
||||
"podman/containers/*",
|
||||
"podman/images/*",
|
||||
"podman/manifests/*",
|
||||
"podman/networks/*",
|
||||
"podman/pods/*",
|
||||
"podman/system/*",
|
||||
"podman/system/*",
|
||||
"podman/tests/*",
|
||||
]
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Any changes should be copied into pyproject.toml
|
||||
-r requirements.txt
|
||||
black
|
||||
coverage
|
||||
fixtures~=3.0.0
|
||||
pytest
|
||||
pylint
|
||||
pytest
|
||||
requests-mock
|
||||
tox
|
||||
|
|
8
tox.ini
8
tox.ini
|
@ -1,6 +1,6 @@
|
|||
[tox]
|
||||
minversion = 3.2.0
|
||||
envlist = py36,py38,py39,py310,pylint,coverage
|
||||
envlist = pylint,coverage,py36,py38,py39,py310
|
||||
ignore_basepython_conflict = true
|
||||
|
||||
[testenv]
|
||||
|
@ -8,7 +8,11 @@ basepython = python3
|
|||
usedevelop = True
|
||||
install_command = pip install {opts} {packages}
|
||||
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]
|
||||
commands = {posargs}
|
||||
|
|
Loading…
Reference in New Issue