From 5773a115f964404c053cf958ecc4ea08718f78af Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Thu, 1 Apr 2021 03:16:05 -0300 Subject: [PATCH] Add Snyk scan suggestion when building Signed-off-by: Ulysses Souza --- compose/cli/scan_suggest.py | 59 +++++++++++++++++++++++++++++++++++++ compose/project.py | 11 +++++++ compose/service.py | 18 +++++++++++ 3 files changed, 88 insertions(+) create mode 100644 compose/cli/scan_suggest.py diff --git a/compose/cli/scan_suggest.py b/compose/cli/scan_suggest.py new file mode 100644 index 00000000000..e6b0c8968ce --- /dev/null +++ b/compose/cli/scan_suggest.py @@ -0,0 +1,59 @@ +import json +import logging +import os +from distutils.util import strtobool + +from docker.constants import IS_WINDOWS_PLATFORM +from docker.utils.config import find_config_file + + +SCAN_BINARY_NAME = "docker-scan" + (".exe" if IS_WINDOWS_PLATFORM else "") + +log = logging.getLogger(__name__) + + +class ScanConfig: + def __init__(self, dict): + self.optin = False + vars(self).update(dict) + + +def display_scan_suggest_msg(): + if environment_scan_avoid_suggest() or \ + scan_already_invoked() or \ + not scan_available(): + return + log.info("Use 'docker scan' to run Snyk tests against images to find vulnerabilities " + "and learn how to fix them") + + +def environment_scan_avoid_suggest(): + return os.getenv('DOCKER_SCAN_SUGGEST', 'true').lower() == 'false' + + +def scan_already_invoked(): + docker_folder = docker_config_folder() + if docker_folder is None: + return False + + scan_config_file = os.path.join(docker_folder, 'scan', "config.json") + if not os.path.exists(scan_config_file): + return False + + data = '' + with open(scan_config_file) as f: + data = f.read() + scan_config = json.loads(data, object_hook=ScanConfig) + return scan_config.optin if isinstance(scan_config.optin, bool) else strtobool(scan_config.optin) + + +def scan_available(): + docker_folder = docker_config_folder() + scan_config_file = os.path.join(docker_folder, 'cli-plugins', SCAN_BINARY_NAME) + return os.path.isfile(scan_config_file) or os.path.islink(scan_config_file) + + +def docker_config_folder(): + docker_config_file = find_config_file() + return None if not docker_config_file \ + else os.path.dirname(os.path.abspath(docker_config_file)) diff --git a/compose/project.py b/compose/project.py index e862464d863..6ae024ce7d5 100644 --- a/compose/project.py +++ b/compose/project.py @@ -13,6 +13,7 @@ from . import parallel from .cli.errors import UserError +from .cli.scan_suggest import display_scan_suggest_msg from .config import ConfigurationError from .config.config import V1 from .config.sort_services import get_container_name_from_network_mode @@ -518,6 +519,9 @@ def build_service(service): for service in services: build_service(service) + if services: + display_scan_suggest_msg() + def create( self, service_names=None, @@ -660,8 +664,15 @@ def up(self, service_names, include_deps=start_deps) + must_build = False for svc in services: + if svc.must_build(do_build=do_build): + must_build = True svc.ensure_image_exists(do_build=do_build, silent=silent, cli=cli) + + if must_build: + display_scan_suggest_msg() + plans = self._get_convergence_plans( services, strategy, diff --git a/compose/service.py b/compose/service.py index 716a7557476..fda1edb2e73 100644 --- a/compose/service.py +++ b/compose/service.py @@ -366,6 +366,24 @@ def ensure_image_exists(self, do_build=BuildAction.none, silent=False, cli=False "rebuild this image you must use `docker-compose build` or " "`docker-compose up --build`.".format(self.name)) + def must_build(self, do_build=BuildAction.none): + if self.can_be_built() and do_build == BuildAction.force: + return True + + try: + self.image() + return False + except NoSuchImageError: + pass + + if not self.can_be_built(): + return False + + if do_build == BuildAction.skip: + return False + + return True + def get_image_registry_data(self): try: return self.client.inspect_distribution(self.image_name)