| |
@@ -0,0 +1,439 @@
|
| |
+ diff --git a/.gitignore b/.gitignore
|
| |
+ index 3236f9c..613647e 100644
|
| |
+ --- a/.gitignore
|
| |
+ +++ b/.gitignore
|
| |
+ @@ -2,3 +2,4 @@
|
| |
+ .pytest_cache/
|
| |
+ noarch/
|
| |
+ *.tar.gz
|
| |
+ +.coverage
|
| |
+ diff --git a/Dockerfile.tests b/Dockerfile.tests
|
| |
+ index c799fcf..f7704bf 100644
|
| |
+ --- a/Dockerfile.tests
|
| |
+ +++ b/Dockerfile.tests
|
| |
+ @@ -2,24 +2,23 @@ FROM registry.fedoraproject.org/fedora:29
|
| |
+
|
| |
+ ENV PYTHONDONTWRITEBYTECODE=yes-please
|
| |
+
|
| |
+ -# atomic in F28 can't mount: can't find the image
|
| |
+ -RUN dnf install -y make python3-pytest python3-pyxattr python3-pytest-cov \
|
| |
+ - skopeo \
|
| |
+ - https://kojipkgs.fedoraproject.org/packages/podman/0.9.1/3.gitaba58d1.fc29/x86_64/podman-0.9.1-3.gitaba58d1.fc29.x86_64.rpm \
|
| |
+ - https://kojipkgs.fedoraproject.org/packages/containernetworking-plugins/0.7.3/2.fc29/x86_64/containernetworking-plugins-0.7.3-2.fc29.x86_64.rpm \
|
| |
+ - https://kojipkgs.fedoraproject.org/packages/buildah/1.4/1.dev.git0a7389c.fc29/x86_64/buildah-1.4-1.dev.git0a7389c.fc29.x86_64.rpm \
|
| |
+ - https://kojipkgs.fedoraproject.org//packages/atomic/1.22.1/27.gitb507039.fc29/x86_64/atomic-1.22.1-27.gitb507039.fc29.x86_64.rpm \
|
| |
+ +RUN dnf install -y \
|
| |
+ + make python3-pytest python3-pyxattr python3-pytest-cov \
|
| |
+ + skopeo podman buildah atomic \
|
| |
+ + python3-ipdb \
|
| |
+ && dnf clean all
|
| |
+
|
| |
+ -# podman
|
| |
+ -RUN useradd podm
|
| |
+ -RUN echo "podm:231072:65536" > /etc/subuid
|
| |
+ -RUN echo "podm:231072:65536" > /etc/subgid
|
| |
+ -USER podm
|
| |
+ +RUN sed '/^graphroot/s/.*/graphroot="\/var\/tmp\/containers"/' -i /etc/containers/storage.conf \
|
| |
+ + && printf 'checkout_path: /var/tmp/containers/atomic\n' >>/etc/atomic.conf
|
| |
+ +
|
| |
+ +# # podman
|
| |
+ +# RUN useradd podm
|
| |
+ +# RUN echo "podm:231072:65536" > /etc/subuid
|
| |
+ +# RUN echo "podm:231072:65536" > /etc/subgid
|
| |
+ +# USER podm
|
| |
+
|
| |
+ WORKDIR /src
|
| |
+
|
| |
+ -COPY ./tests /tests
|
| |
+ COPY . /src
|
| |
+
|
| |
+ RUN pip3 install --user .
|
| |
+ diff --git a/Makefile b/Makefile
|
| |
+ index 3390e80..3258437 100644
|
| |
+ --- a/Makefile
|
| |
+ +++ b/Makefile
|
| |
+ @@ -12,17 +12,19 @@ test-in-container: build-test-image test-in-container-now
|
| |
+
|
| |
+ test-in-container-now:
|
| |
+ @# use it like this: `make test-in-container TEST_TARGET=./tests/integration/test_utils.py`
|
| |
+ - docker run --rm --privileged --security-opt label=disable --cap-add SYS_ADMIN -ti -v $(CURDIR):/src $(TEST_IMAGE_NAME) make exec-test TEST_TARGET="$(TEST_TARGET)"
|
| |
+ + docker run --rm --privileged --security-opt label=disable \
|
| |
+ + --name=colin \
|
| |
+ + --cap-add SYS_ADMIN -ti \
|
| |
+ + -v /var/tmp/ \
|
| |
+ + -v $(CURDIR):/src \
|
| |
+ + $(TEST_IMAGE_NAME) \
|
| |
+ + make exec-test TEST_TARGET="$(TEST_TARGET)"
|
| |
+
|
| |
+ -test-in-ci: setup-ci
|
| |
+ - PYTHONPATH=$(CURDIR) py.test --cov=colin $(TEST_TARGET)
|
| |
+ +test-in-ci: test-in-container
|
| |
+
|
| |
+ exec-test:
|
| |
+ PYTHONPATH=$(CURDIR) py.test-3 --cov=colin $(TEST_TARGET)
|
| |
+
|
| |
+ -setup-ci:
|
| |
+ - ./setup-ci.sh
|
| |
+ -
|
| |
+ check-code-style: check-pylint check-bandit
|
| |
+
|
| |
+ check-pylint:
|
| |
+ diff --git a/colin/cli/colin.py b/colin/cli/colin.py
|
| |
+ index 40f57c8..e2dad2d 100644
|
| |
+ --- a/colin/cli/colin.py
|
| |
+ +++ b/colin/cli/colin.py
|
| |
+ @@ -47,7 +47,7 @@ def cli():
|
| |
+
|
| |
+ @click.command(name="check",
|
| |
+ context_settings=CONTEXT_SETTINGS)
|
| |
+ -@click.argument('target', type=click.STRING)
|
| |
+ +@click.argument('TARGET', type=click.STRING)
|
| |
+ @click.option('--ruleset', '-r', type=click.STRING, envvar='COLIN_RULESET',
|
| |
+ help="Select a predefined ruleset (e.g. fedora).")
|
| |
+ @click.option('--ruleset-file', '-f', type=click.File(mode='r'),
|
| |
+ diff --git a/colin/core/loader.py b/colin/core/loader.py
|
| |
+ index a8a4566..3d7d1db 100644
|
| |
+ --- a/colin/core/loader.py
|
| |
+ +++ b/colin/core/loader.py
|
| |
+ @@ -21,9 +21,9 @@ loads AbstractCheck classes from it.
|
| |
+ import inspect
|
| |
+ import logging
|
| |
+ import os
|
| |
+ -import warnings
|
| |
+ -
|
| |
+ -import six
|
| |
+ +from importlib import import_module
|
| |
+ +from importlib.util import module_from_spec
|
| |
+ +from importlib.util import spec_from_file_location
|
| |
+
|
| |
+ from ..core.checks.fmf_check import receive_fmf_metadata, FMFAbstractCheck
|
| |
+
|
| |
+ @@ -44,22 +44,11 @@ def path_to_module(path):
|
| |
+ def _load_module(path):
|
| |
+ module_name = path_to_module(path)
|
| |
+ logger.debug("Will try to load selected file as module '%s'.", module_name)
|
| |
+ - if six.PY3:
|
| |
+ - from importlib.util import module_from_spec
|
| |
+ - from importlib.util import spec_from_file_location
|
| |
+ -
|
| |
+ - s = spec_from_file_location(module_name, path)
|
| |
+ - m = module_from_spec(s)
|
| |
+ - s.loader.exec_module(m)
|
| |
+ - return m
|
| |
+
|
| |
+ - elif six.PY2:
|
| |
+ - import imp
|
| |
+ -
|
| |
+ - with warnings.catch_warnings():
|
| |
+ - warnings.simplefilter("ignore") # FIXME: let's at least debug log other warnings
|
| |
+ - m = imp.load_source(module_name, path)
|
| |
+ - return m
|
| |
+ + s = spec_from_file_location(module_name, path)
|
| |
+ + m = module_from_spec(s)
|
| |
+ + s.loader.exec_module(m)
|
| |
+ + return m
|
| |
+
|
| |
+
|
| |
+ def should_we_load(kls):
|
| |
+ @@ -124,6 +113,20 @@ class CheckLoader(object):
|
| |
+ load_check_classes_from_file(path)))
|
| |
+ return list(check_classes)
|
| |
+
|
| |
+ + def import_class(self, import_name):
|
| |
+ + """
|
| |
+ + import selected class
|
| |
+ +
|
| |
+ + :param import_name, str, e.g. some.module.MyClass
|
| |
+ + :return the class
|
| |
+ + """
|
| |
+ + module_name, class_name = import_name.rsplit(".", 1)
|
| |
+ + mod = import_module(module_name)
|
| |
+ + check_class = getattr(mod, class_name)
|
| |
+ + self.mapping[check_class.name] = check_class
|
| |
+ + logger.info("successfully loaded class %s", check_class)
|
| |
+ + return check_class
|
| |
+ +
|
| |
+ @property
|
| |
+ def check_classes(self):
|
| |
+ if self._check_classes is None:
|
| |
+ diff --git a/colin/core/ruleset/loader.py b/colin/core/ruleset/loader.py
|
| |
+ index fb7d925..476f0d9 100644
|
| |
+ --- a/colin/core/ruleset/loader.py
|
| |
+ +++ b/colin/core/ruleset/loader.py
|
| |
+ @@ -93,6 +93,11 @@ class CheckStruct(object):
|
| |
+ def __str__(self):
|
| |
+ return "%s" % self.name
|
| |
+
|
| |
+ + @property
|
| |
+ + def import_name(self):
|
| |
+ + """ module name + class, e.g. our.colin.checks.CustomCheck """
|
| |
+ + return self._get(False, "import_name")
|
| |
+ +
|
| |
+ @property
|
| |
+ def name(self):
|
| |
+ return self._get(True, "name")
|
| |
+ diff --git a/colin/core/ruleset/ruleset.py b/colin/core/ruleset/ruleset.py
|
| |
+ index 37bf90d..fd17671 100644
|
| |
+ --- a/colin/core/ruleset/ruleset.py
|
| |
+ +++ b/colin/core/ruleset/ruleset.py
|
| |
+ @@ -76,19 +76,16 @@ class Ruleset(object):
|
| |
+ logger.info("Skipping... Target type does not match.")
|
| |
+ continue
|
| |
+
|
| |
+ - try:
|
| |
+ - check_class = self.check_loader.mapping[check_struct.name]
|
| |
+ - check_instance = check_class()
|
| |
+ - except KeyError as ke:
|
| |
+ - check_instance = NotLoadedCheck(check_name=check_struct.name,
|
| |
+ - reason="not found")
|
| |
+ - logger.debug("Cannot find a code for the check {}. ({})".format(check_struct.name,
|
| |
+ - ke))
|
| |
+ - except Exception as ex:
|
| |
+ - check_instance = NotLoadedCheck(check_name=check_struct.name,
|
| |
+ - reason="not instantiated")
|
| |
+ - logger.debug("Cannot instantiate the check {}. ({})".format(check_struct.name,
|
| |
+ - ex))
|
| |
+ + if check_struct.import_name:
|
| |
+ + check_class = self.check_loader.import_class(check_struct.import_name)
|
| |
+ + else:
|
| |
+ + try:
|
| |
+ + check_class = self.check_loader.mapping[check_struct.name]
|
| |
+ + except KeyError:
|
| |
+ + logger.error("Check %s was not found -- it can't be loaded", check_struct.name)
|
| |
+ + raise ColinRulesetException(
|
| |
+ + "Check {} can't be loaded, we couldn't find it.".format(check_struct.name))
|
| |
+ + check_instance = check_class()
|
| |
+
|
| |
+ if check_struct.tags:
|
| |
+ logger.info("Overriding check's tags %s with the one defined in ruleset: %s",
|
| |
+ @@ -131,10 +128,11 @@ def get_checks_paths(checks_paths=None):
|
| |
+ :param checks_paths: list of str, directories where the checks are present
|
| |
+ :return: list of str (absolute path of directory with checks)
|
| |
+ """
|
| |
+ - if checks_paths:
|
| |
+ - return [os.path.abspath(x) for x in checks_paths]
|
| |
+ p = os.path.join(__file__, os.pardir, os.pardir, os.pardir, "checks")
|
| |
+ p = os.path.abspath(p)
|
| |
+ + # let's utilize the default upstream checks always
|
| |
+ + if checks_paths:
|
| |
+ + p += [os.path.abspath(x) for x in checks_paths]
|
| |
+ return [p]
|
| |
+
|
| |
+
|
| |
+ diff --git a/colin/core/target.py b/colin/core/target.py
|
| |
+ index 923fbea..ac083de 100644
|
| |
+ --- a/colin/core/target.py
|
| |
+ +++ b/colin/core/target.py
|
| |
+ @@ -413,7 +413,8 @@ class OstreeTarget(AbstractImageTarget):
|
| |
+ kwargs["cwd"] = wd
|
| |
+ try:
|
| |
+ out = subprocess.check_output(cmd, **kwargs)
|
| |
+ - except subprocess.CalledProcessError:
|
| |
+ + except subprocess.CalledProcessError as ex:
|
| |
+ + logger.error(ex.output)
|
| |
+ logger.error(error_msg)
|
| |
+ raise
|
| |
+ logger.debug("%s", out)
|
| |
+ diff --git a/setup-ci.sh b/setup-ci.sh
|
| |
+ deleted file mode 100755
|
| |
+ index 246c5d6..0000000
|
| |
+ --- a/setup-ci.sh
|
| |
+ +++ /dev/null
|
| |
+ @@ -1,23 +0,0 @@
|
| |
+ -#!/bin/bash
|
| |
+ -
|
| |
+ -echo "yum -y install yum-plugin-copr"
|
| |
+ -yum -y install yum-plugin-copr
|
| |
+ -
|
| |
+ -echo "yum copr enable -y shosca/python"
|
| |
+ -yum copr enable -y shosca/python
|
| |
+ -
|
| |
+ -echo "yum -y install make gcc python36 python36-devel python36-setuptools python36-pip"
|
| |
+ -yum -y install make gcc python36 python36-devel python36-setuptools python36-pip python36-cffi python36-pycparser
|
| |
+ -
|
| |
+ -echo "pip3 install pytest xattr"
|
| |
+ -pip3.6 install pytest pytest-cov xattr
|
| |
+ -
|
| |
+ -echo "yum copr enable -y baude/Upstream_CRIO_Family"
|
| |
+ -yum copr enable -y baude/Upstream_CRIO_Family
|
| |
+ -
|
| |
+ -echo "yum -y install skopeo podman atomic ostree"
|
| |
+ -yum -y install skopeo podman atomic ostree
|
| |
+ -
|
| |
+ -
|
| |
+ -echo "pip3.6 install ."
|
| |
+ -pip3.6 install .
|
| |
+ diff --git a/tests/integration/conftest.py b/tests/conftest.py
|
| |
+ similarity index 84%
|
| |
+ rename from tests/integration/conftest.py
|
| |
+ rename to tests/conftest.py
|
| |
+ index 5eecedf..ef69f52 100644
|
| |
+ --- a/tests/integration/conftest.py
|
| |
+ +++ b/tests/conftest.py
|
| |
+ @@ -13,7 +13,7 @@ _set_logging(level=logging.DEBUG)
|
| |
+
|
| |
+ BASH_IMAGE = "colin-test-bash"
|
| |
+ LS_IMAGE = "colin-test-ls"
|
| |
+ -BUSYBOX_IMAGE = "busybox"
|
| |
+ +BUSYBOX_IMAGE = "busybox:latest"
|
| |
+ LABELS_IMAGE = "colin-labels"
|
| |
+ IMAGES = {
|
| |
+ BASH_IMAGE: {
|
| |
+ @@ -30,25 +30,21 @@ IMAGES = {
|
| |
+
|
| |
+ def build_image_if_not_exists(image_name):
|
| |
+ try:
|
| |
+ - subprocess.check_call(["podman", "image", "inspect", image_name],
|
| |
+ - stdout=subprocess.PIPE)
|
| |
+ + subprocess.check_call(["podman", "image", "exists", image_name])
|
| |
+ except subprocess.CalledProcessError:
|
| |
+ this_dir = os.path.abspath(os.path.dirname(__file__))
|
| |
+ - data_dir = os.path.join(this_dir, os.path.pardir, "data")
|
| |
+ + data_dir = os.path.join(this_dir, "data")
|
| |
+
|
| |
+ dockerfile_path = IMAGES[image_name]["dockerfile_path"]
|
| |
+ cmd_create = ["podman", "build", "-t", image_name, "-f", dockerfile_path, data_dir]
|
| |
+ - output = subprocess.check_output(cmd_create)
|
| |
+ - assert output
|
| |
+ + subprocess.check_call(cmd_create)
|
| |
+
|
| |
+
|
| |
+ def pull_image_if_not_exists(image_name):
|
| |
+ try:
|
| |
+ - subprocess.check_call(["podman", "image", "inspect", image_name],
|
| |
+ - stdout=subprocess.PIPE)
|
| |
+ + subprocess.check_call(["podman", "image", "exists", image_name])
|
| |
+ except subprocess.CalledProcessError:
|
| |
+ - subprocess.check_call(["podman", "pull", image_name],
|
| |
+ - stdout=subprocess.PIPE)
|
| |
+ + subprocess.check_call(["podman", "pull", image_name])
|
| |
+
|
| |
+
|
| |
+ def convert_image_to_ostree(image_name):
|
| |
+ @@ -88,7 +84,7 @@ def get_target(name, type):
|
| |
+ elif type == "dockerfile":
|
| |
+
|
| |
+ this_dir = os.path.abspath(os.path.dirname(__file__))
|
| |
+ - data_dir = os.path.join(this_dir, os.path.pardir, "data")
|
| |
+ + data_dir = os.path.join(this_dir, "data")
|
| |
+ dockerfile_path = os.path.join(data_dir, IMAGES[name]["dockerfile_path"])
|
| |
+
|
| |
+ yield DockerfileTarget(target=dockerfile_path)
|
| |
+ @@ -98,7 +94,7 @@ def get_skopeo_path(image_name, ostree_path):
|
| |
+ return "ostree:%s@%s" % (image_name, ostree_path)
|
| |
+
|
| |
+
|
| |
+ -@pytest.fixture(scope="session")
|
| |
+ +@pytest.fixture(scope="session", autouse=True)
|
| |
+ def label_image():
|
| |
+ build_image_if_not_exists(LABELS_IMAGE)
|
| |
+
|
| |
+ @@ -119,7 +115,7 @@ def target_label_image_and_dockerfile(request, label_image):
|
| |
+ yield t
|
| |
+
|
| |
+
|
| |
+ -@pytest.fixture(scope="session")
|
| |
+ +@pytest.fixture(scope="session", autouse=True)
|
| |
+ def target_bash_image():
|
| |
+ build_image_if_not_exists(BASH_IMAGE)
|
| |
+
|
| |
+ @@ -132,7 +128,7 @@ def target_bash(request, target_bash_image):
|
| |
+ yield t
|
| |
+
|
| |
+
|
| |
+ -@pytest.fixture(scope="session")
|
| |
+ +@pytest.fixture(scope="session", autouse=True)
|
| |
+ def target_ls_image():
|
| |
+ build_image_if_not_exists(LS_IMAGE)
|
| |
+
|
| |
+ @@ -154,7 +150,7 @@ def target_help_file(request, target_ls, target_bash):
|
| |
+ return target_bash, True
|
| |
+
|
| |
+
|
| |
+ -@pytest.fixture(scope="session")
|
| |
+ +@pytest.fixture(scope="session", autouse=True)
|
| |
+ def target_busybox_image():
|
| |
+ pull_image_if_not_exists(image_name=BUSYBOX_IMAGE)
|
| |
+
|
| |
+ diff --git a/tests/integration/test_ruleset_file.py b/tests/integration/test_ruleset_file.py
|
| |
+ index 2e7d5aa..3f47ec3 100644
|
| |
+ --- a/tests/integration/test_ruleset_file.py
|
| |
+ +++ b/tests/integration/test_ruleset_file.py
|
| |
+ @@ -18,6 +18,7 @@ import tempfile
|
| |
+
|
| |
+ import pytest
|
| |
+ import yaml
|
| |
+ +from colin.core.exceptions import ColinRulesetException
|
| |
+
|
| |
+ import colin
|
| |
+ from colin.core.checks.check_utils import NotLoadedCheck
|
| |
+ @@ -140,16 +141,9 @@ def test_coupled_ruleset(ruleset_coupled):
|
| |
+
|
| |
+
|
| |
+ def test_unknown_check(ruleset_unknown_check):
|
| |
+ - checks = colin.get_checks(ruleset=ruleset_unknown_check)
|
| |
+ - assert checks
|
| |
+ - assert len(checks) == 2
|
| |
+ - for check in checks:
|
| |
+ - if check.name == 'i_forgot_the_name':
|
| |
+ - assert isinstance(check, NotLoadedCheck)
|
| |
+ - elif check.name == 'maintainer_label':
|
| |
+ - assert isinstance(check, LabelAbstractCheck)
|
| |
+ - else:
|
| |
+ - assert False
|
| |
+ + with pytest.raises(ColinRulesetException) as ex:
|
| |
+ + colin.get_checks(ruleset=ruleset_unknown_check)
|
| |
+ + assert str(ex.value) == "Check i_forgot_the_name can't be loaded, we couldn't find it."
|
| |
+
|
| |
+
|
| |
+ def test_skip(ruleset):
|
| |
+ diff --git a/tests/integration/test_targets.py b/tests/integration/test_targets.py
|
| |
+ index 65b1066..5b1b52f 100644
|
| |
+ --- a/tests/integration/test_targets.py
|
| |
+ +++ b/tests/integration/test_targets.py
|
| |
+ @@ -5,7 +5,7 @@ Test different target types.
|
| |
+ import pytest
|
| |
+
|
| |
+ import colin
|
| |
+ -from tests.integration.conftest import LABELS_IMAGE, convert_image_to_ostree, get_skopeo_path
|
| |
+ +from tests.conftest import LABELS_IMAGE, convert_image_to_ostree, get_skopeo_path
|
| |
+
|
| |
+
|
| |
+ @pytest.fixture()
|
| |
+ diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py
|
| |
+ index 2e06459..e5df11e 100644
|
| |
+ --- a/tests/unit/test_cli.py
|
| |
+ +++ b/tests/unit/test_cli.py
|
| |
+ @@ -41,13 +41,11 @@ def _common_help_options(result):
|
| |
+
|
| |
+ def test_check_command():
|
| |
+ result = _call_colin(check)
|
| |
+ - expected_output = """Usage: check [OPTIONS] TARGET
|
| |
+ -Try "check -h" for help.
|
| |
+ -
|
| |
+ -Error: Missing argument "TARGET".
|
| |
+ -"""
|
| |
+ + expected_output1 = "Usage: check [OPTIONS] TARGET"
|
| |
+ + expected_output2 = 'Error: Missing argument "TARGET"'
|
| |
+ assert result.exit_code == 2
|
| |
+ - assert result.output == expected_output
|
| |
+ + assert expected_output1 in result.output
|
| |
+ + assert expected_output2 in result.output
|
| |
+
|
| |
+
|
| |
+ def test_check_help_command():
|
| |
+ diff --git a/tests/unit/test_loader.py b/tests/unit/test_loader.py
|
| |
+ index df21644..30ffbc5 100644
|
| |
+ --- a/tests/unit/test_loader.py
|
| |
+ +++ b/tests/unit/test_loader.py
|
| |
+ @@ -73,3 +73,10 @@ def test_loading_custom_check(tmpdir):
|
| |
+ assert len(l.check_classes) == 3
|
| |
+ assert l.mapping["a-peter-file-check"]
|
| |
+ assert l.mapping["this-is-a-funky-check"]
|
| |
+ +
|
| |
+ +
|
| |
+ +def test_import_class():
|
| |
+ + l = CheckLoader([])
|
| |
+ + check_name = "ArchitectureLabelCheck"
|
| |
+ + c = l.import_class("colin.checks.labels.%s" % check_name)
|
| |
+ + assert c.name == "architecture_label"
|
| |
description