From e496c641273326c7314e01f434313a56f48ba7b0 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 | 85 +++++++++++++++++++++++++++++++++++++ compose/project.py | 11 +++++ compose/service.py | 18 ++++++++ 3 files changed, 114 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..4ebe2ceca6a --- /dev/null +++ b/compose/cli/scan_suggest.py @@ -0,0 +1,85 @@ +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, d): + self.optin = False + vars(self).update(d) + + +def display_scan_suggest_msg(): + if environment_scan_avoid_suggest() or \ + scan_available() is None or \ + scan_already_invoked(): + 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 + + try: + 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) + except Exception: # pylint:disable=broad-except + return True + + +def scan_available(): + docker_folder = docker_config_folder() + if docker_folder: + home_scan_bin = os.path.join(docker_folder, 'cli-plugins', SCAN_BINARY_NAME) + if os.path.isfile(home_scan_bin) or os.path.islink(home_scan_bin): + return home_scan_bin + + if IS_WINDOWS_PLATFORM: + program_data_scan_bin = os.path.join('C:\\', 'ProgramData', 'Docker', 'cli-plugins', + SCAN_BINARY_NAME) + if os.path.isfile(program_data_scan_bin) or os.path.islink(program_data_scan_bin): + return program_data_scan_bin + else: + lib_scan_bin = os.path.join('/usr', 'local', 'lib', 'docker', 'cli-plugins', SCAN_BINARY_NAME) + if os.path.isfile(lib_scan_bin) or os.path.islink(lib_scan_bin): + return lib_scan_bin + lib_exec_scan_bin = os.path.join('/usr', 'local', 'libexec', 'docker', 'cli-plugins', + SCAN_BINARY_NAME) + if os.path.isfile(lib_exec_scan_bin) or os.path.islink(lib_exec_scan_bin): + return lib_exec_scan_bin + lib_scan_bin = os.path.join('/usr', 'lib', 'docker', 'cli-plugins', SCAN_BINARY_NAME) + if os.path.isfile(lib_scan_bin) or os.path.islink(lib_scan_bin): + return lib_scan_bin + lib_exec_scan_bin = os.path.join('/usr', 'libexec', 'docker', 'cli-plugins', SCAN_BINARY_NAME) + if os.path.isfile(lib_exec_scan_bin) or os.path.islink(lib_exec_scan_bin): + return lib_exec_scan_bin + return None + + +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)