Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions colin/checks/.fmf/version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
24 changes: 24 additions & 0 deletions colin/checks/best_practices.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
test: "best_practices.py"

/cmd_or_entrypoint:
class: "CmdOrEntrypointCheck"
message: "Cmd or Entrypoint has to be specified"
description: "An ENTRYPOINT allows you to configure a container that will run as an executable. The main purpose of a CMD is to provide defaults for an executing container."
reference_url: "https://fedoraproject.org/wiki/Container:Guidelines#CMD.2FENTRYPOINT_2"
tags: ["cmd", "entrypoint"]

/help_file_or_readme:
class: "HelpFileOrReadmeCheck"
message: "The 'helpfile' has to be provided."
description: "Just like traditional packages, containers need some 'man page' information about how they are to be used, configured, and integrated into a larger stack."
reference_url: "https://fedoraproject.org/wiki/Container:Guidelines#Help_File"
files: ['/help.1', '/README.md']
tags: ['filesystem', 'helpfile', 'man']
all_must_be_present: False

/no_root:
class: "NoRootCheck"
message: "Service should not run as root by default."
description: "It can be insecure to run service as root."
reference_url: "?????"
tags: ["root", "user"]
19 changes: 19 additions & 0 deletions colin/checks/dockerfile.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
description: "Generic description for dockerfile test if not specific description given"
test: "dockerfile.py"
tags: ["dockerfile"]

/from_tag_not_latest:
class: "FromTagNotLatestCheck"
message: "In FROM, tag has to be specified and not 'latest'."
description: "Using the 'latest' tag may cause unpredictable builds.It is recommended that a specific tag is used in the FROM."
reference_url: "https://fedoraproject.org/wiki/Container:Guidelines#FROM"
tags+: ["from", "baseimage", "latest"]

/maintainer_deprecated:
class: "MaintainerDeprecatedCheck"
message: "Dockerfile instruction `MAINTAINER` is deprecated."
description: "Replace with label 'maintainer'."
reference_url: "https://docs.docker.com/engine/reference/builder/#maintainer-deprecated"
tags+: ["maintainer", "deprecated"]
instruction: "MAINTAINER"
max_count: 0
22 changes: 3 additions & 19 deletions colin/checks/dockerfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,12 @@
from colin.core.checks.dockerfile import DockerfileAbstractCheck, InstructionCountAbstractCheck
from colin.core.result import CheckResult
from colin.core.target import ImageName
from colin.core.checks.fmf_check import FMFAbstractCheck


class FromTagNotLatestCheck(DockerfileAbstractCheck):
class FromTagNotLatestCheck(FMFAbstractCheck, DockerfileAbstractCheck):
name = "from_tag_not_latest"

def __init__(self):
super(FromTagNotLatestCheck, self) \
.__init__(message="In FROM, tag has to be specified and not 'latest'.",
description="Using the 'latest' tag may cause unpredictable builds."
"It is recommended that a specific tag is used in the FROM.",
reference_url="https://fedoraproject.org/wiki/Container:Guidelines#FROM",
tags=["from", "dockerfile", "baseimage", "latest"])

def check(self, target):
im = ImageName.parse(target.instance.baseimage)
passed = im.tag and im.tag != "latest"
Expand All @@ -41,14 +34,5 @@ def check(self, target):
logs=[])


class MaintainerDeprecatedCheck(InstructionCountAbstractCheck):
class MaintainerDeprecatedCheck(FMFAbstractCheck, InstructionCountAbstractCheck):
name = "maintainer_deprecated"

def __init__(self):
super(MaintainerDeprecatedCheck, self) \
.__init__(message="Dockerfile instruction `MAINTAINER` is deprecated.",
description="Replace with label 'maintainer'.",
reference_url="https://docs.docker.com/engine/reference/builder/#maintainer-deprecated",
tags=["maintainer", "dockerfile", "deprecated"],
instruction="MAINTAINER",
max_count=0)
11 changes: 11 additions & 0 deletions colin/checks/labels.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
test: labels.py

/maintainer_label:
class: "MaintainerLabelCheck"
message: "Label 'maintainer' has to be specified."
description: "The name and email of the maintainer (usually the submitter)."
reference_url: "https://fedoraproject.org/wiki/Container:Guidelines#LABELS"
tags: ["maintainer", "label"]
labels: ["maintainer"]
required: True
value_regex: Null
13 changes: 2 additions & 11 deletions colin/checks/labels.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#

from colin.core.checks.labels import LabelAbstractCheck
from colin.core.checks.fmf_check import FMFAbstractCheck


class ArchitectureLabelCheck(LabelAbstractCheck):
Expand Down Expand Up @@ -228,19 +229,9 @@ def __init__(self):
value_regex=None)


class MaintainerLabelCheck(LabelAbstractCheck):
class MaintainerLabelCheck(FMFAbstractCheck, LabelAbstractCheck):
name = "maintainer_label"

def __init__(self):
super(MaintainerLabelCheck, self) \
.__init__(message="Label 'maintainer' has to be specified.",
description="The name and email of the maintainer (usually the submitter).",
reference_url="https://fedoraproject.org/wiki/Container:Guidelines#LABELS",
tags=["maintainer", "label"],
labels=["maintainer"],
required=True,
value_regex=None)


class NameLabelCheck(LabelAbstractCheck):
name = "name_label"
Expand Down
1 change: 0 additions & 1 deletion colin/cli/colin.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ def list_checks(ruleset, ruleset_file, debug, json, tag, verbose, checkpath):

log_level = _get_log_level(debug=debug,
verbose=verbose)

checks = get_checks(ruleset_name=ruleset,
ruleset_file=ruleset_file,
logging_level=log_level,
Expand Down
59 changes: 59 additions & 0 deletions colin/core/checks/fmf_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Module with FMF abstract check class
"""

import copy
import logging

from .abstract_check import AbstractCheck
from ..fmf_extension import ExtendedTree

logger = logging.getLogger(__name__)


def receive_fmf_metadata(name, path, object_list=False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish this function was integrated here: https://github.com/user-cont/colin/blob/master/colin/core/loader.py

so that you don't have to pass the path in such a hacky way (from cli via changing attribute on a class)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fixed, metadata loaded in loader.py

"""
search node identified by name fmfpath

:param path: path to filesystem
:param name: str - name as pattern to search (substring)
:param object_list: bool, if true, return whole list of found items
:return: Tree Object or list
"""
output = {}
fmf_tree = ExtendedTree(path)
logger.debug("get FMF metadata for test (path:%s name=%s)", path, name)
# ignore items with @ in names, to avoid using unreferenced items
items = [x for x in fmf_tree.climb() if name in x.name and "@" not in x.name]
if object_list:
return items
if len(items) == 1:
output = items[0]
elif len(items) > 1:
raise Exception("There is more FMF test metadata for item by name:{}({}) {}".format(
name, len(items), [x.name for x in items]))
elif not items:
raise Exception("Unable to get FMF metadata for: {}".format(name))
return output


class FMFAbstractCheck(AbstractCheck):
"""
Abstract class for checks and loading metadata from FMF format
"""
metadata = None
name = None
fmf_metadata_path = None

def __init__(self):
"""
wraps parameters to COLIN __init__ method format
"""
if not self.metadata:
self.metadata = receive_fmf_metadata(name=self.name, path=self.fmf_metadata_path)
kwargs = copy.deepcopy(self.metadata.data)
if "class" in kwargs:
del kwargs["class"]
if "test" in kwargs:
del kwargs["test"]
super(FMFAbstractCheck, self).__init__(**kwargs)
76 changes: 76 additions & 0 deletions colin/core/fmf_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""
Module handling FMF stored metadata for classes
"""

import logging
import re

from fmf import Tree

logger = logging.getLogger(__name__)


class ExtendedTree(Tree):
"""
FMF Extension. Allows to use references via @ to another items -> usefull for rulesets
"""

def __remove_append_items(self, whole=False):
"""
internal method, delete all append items (ends with +)
:param whole: pass thru 'whole' param to climb
:return: None
"""
for node in self.climb(whole=whole):
for key in sorted(node.data.keys()):
if key.endswith('+'):
del node.data[key]

def references(self, datatree, whole=False):
"""
Reference name resolver (eg. /a/b/c/d@.x.y or /a/b/c/@y will search data in .x.y or y nodes)
there are used regular expressions (re.search) to match names
it uses simple references schema, do not use references to another references,
avoid usind / in reference because actual solution creates also these tree items.

datatree contains for example data like (original check data)
/dockerfile/maintainer_check:
class: SomeClass
tags: [dockerfile]

and reference could be like (ruleset)
/default/check1@maintainer_check:
tags+: [required]

will produce output (output ruleset tree):
/default/check1@maintainer_check:
class: SomeClass
tags: [dockerfile, required]


:param whole: 'whole' param of original climb method, in colin this is not used anyhow now
iterate over all items not only leaves if True
:param datatree: original tree with testcases to contain parent nodes
:return: None
"""
reference_nodes = self.prune(whole=whole, names=["@"])
for node in reference_nodes:
node.data = node.original_data
ref_item_name = node.name.rsplit("@", 1)[1]
# match item what does not contain @ before name, otherwise it
# match same item
reference_node = datatree.search("[^@]%s" % ref_item_name)
logger.debug("MERGING: %s @ %s", node.name, reference_node.name)
if not reference_node:
raise ValueError("Unable to find reference for node: %s via name search: %s" %
(node.name, ref_item_name))
node.merge(parent=reference_node)

self.__remove_append_items(whole=whole)

def search(self, name):
""" Search node with given name based on regexp, basic method (find) uses equality"""
for node in self.climb():
if re.search(name, node.name):
return node
return None
5 changes: 5 additions & 0 deletions colin/core/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

import six

from ..core.checks.fmf_check import receive_fmf_metadata, FMFAbstractCheck

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -84,6 +86,9 @@ def load_check_classes_from_file(path, top_path):
check_classes = []
for _, obj in inspect.getmembers(m, inspect.isclass):
if should_we_load(obj):
if issubclass(obj, FMFAbstractCheck):
node_metadata = receive_fmf_metadata(name=obj.name, path=os.path.dirname(path))
obj.metadata = node_metadata
check_classes.append(obj)
logger.debug("Check class '{}' found.".format(obj.__name__))
return check_classes
Expand Down
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
'Click',
'six',
'conu>=0.3.0',
'dockerfile_parse'
'dockerfile_parse',
'fmf'
],
entry_points='''
[console_scripts]
Expand Down Expand Up @@ -67,4 +68,6 @@
author='Red Hat',
author_email='user-cont-team@redhat.com',
url='https://github.com/user-cont/colin',
package_data={'': ['*.fmf']},
include_package_data=True,
)
4 changes: 2 additions & 2 deletions tests/unit/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ def test_list_checks():
-> In FROM, tag has to be specified and not 'latest'.
-> Using the 'latest' tag may cause unpredictable builds.It is recommended that a specific tag is used in the FROM.
-> https://fedoraproject.org/wiki/Container:Guidelines#FROM
-> from, dockerfile, baseimage, latest, required
-> dockerfile, from, baseimage, latest, required

maintainer_deprecated
-> Dockerfile instruction `MAINTAINER` is deprecated.
-> Replace with label 'maintainer'.
-> https://docs.docker.com/engine/reference/builder/#maintainer-deprecated
-> maintainer, dockerfile, deprecated, required
-> dockerfile, maintainer, deprecated, required

"""
assert result.exit_code == 0
Expand Down