From 13c669fc9050635efc4cbbed8ba54f0c221850cc Mon Sep 17 00:00:00 2001 From: Koreloc23 Date: Sun, 9 Aug 2026 20:52:41 -0600 Subject: [PATCH 1/8] fix: correct YAML schema issues and add missing docs --- docs/assets/style.css | 319 +++++ docs/docker_scout.yaml | 32 +- docs/reporte.html | 1094 +++++++++++++++++ pytest.ini | 4 + reporte.html | 1094 +++++++++++++++++ requirements.txt | 3 + .../conftest.cpython-314-pytest-9.0.2.pyc | Bin 0 -> 3641 bytes ...t_yaml_schema.cpython-314-pytest-9.0.2.pyc | Bin 0 -> 36980 bytes tests/conftest.py | 106 ++ tests/test_yaml_schema.py | 248 ++++ 10 files changed, 2875 insertions(+), 25 deletions(-) create mode 100644 docs/assets/style.css create mode 100644 docs/reporte.html create mode 100644 pytest.ini create mode 100644 reporte.html create mode 100644 requirements.txt create mode 100644 tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc create mode 100644 tests/__pycache__/test_yaml_schema.cpython-314-pytest-9.0.2.pyc create mode 100644 tests/conftest.py create mode 100644 tests/test_yaml_schema.py diff --git a/docs/assets/style.css b/docs/assets/style.css new file mode 100644 index 00000000..561524c6 --- /dev/null +++ b/docs/assets/style.css @@ -0,0 +1,319 @@ +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 12px; + /* do not increase min-width as some may use split screens */ + min-width: 800px; + color: #999; +} + +h1 { + font-size: 24px; + color: black; +} + +h2 { + font-size: 16px; + color: black; +} + +p { + color: black; +} + +a { + color: #999; +} + +table { + border-collapse: collapse; +} + +/****************************** + * SUMMARY INFORMATION + ******************************/ +#environment td { + padding: 5px; + border: 1px solid #e6e6e6; + vertical-align: top; +} +#environment tr:nth-child(odd) { + background-color: #f6f6f6; +} +#environment ul { + margin: 0; + padding: 0 20px; +} + +/****************************** + * TEST RESULT COLORS + ******************************/ +span.passed, +.passed .col-result { + color: green; +} + +span.skipped, +span.xfailed, +span.rerun, +.skipped .col-result, +.xfailed .col-result, +.rerun .col-result { + color: orange; +} + +span.error, +span.failed, +span.xpassed, +.error .col-result, +.failed .col-result, +.xpassed .col-result { + color: red; +} + +.col-links__extra { + margin-right: 3px; +} + +/****************************** + * RESULTS TABLE + * + * 1. Table Layout + * 2. Extra + * 3. Sorting items + * + ******************************/ +/*------------------ + * 1. Table Layout + *------------------*/ +#results-table { + border: 1px solid #e6e6e6; + color: #999; + font-size: 12px; + width: 100%; +} +#results-table th, +#results-table td { + padding: 5px; + border: 1px solid #e6e6e6; + text-align: left; +} +#results-table th { + font-weight: bold; +} + +/*------------------ + * 2. Extra + *------------------*/ +.logwrapper { + max-height: 230px; + overflow-y: scroll; + background-color: #e6e6e6; +} +.logwrapper.expanded { + max-height: none; +} +.logwrapper.expanded .logexpander:after { + content: "collapse [-]"; +} +.logwrapper .logexpander { + z-index: 1; + position: sticky; + top: 10px; + width: max-content; + border: 1px solid; + border-radius: 3px; + padding: 5px 7px; + margin: 10px 0 10px calc(100% - 80px); + cursor: pointer; + background-color: #e6e6e6; +} +.logwrapper .logexpander:after { + content: "expand [+]"; +} +.logwrapper .logexpander:hover { + color: #000; + border-color: #000; +} +.logwrapper .log { + min-height: 40px; + position: relative; + top: -50px; + height: calc(100% + 50px); + border: 1px solid #e6e6e6; + color: black; + display: block; + font-family: "Courier New", Courier, monospace; + padding: 5px; + padding-right: 80px; + white-space: pre-wrap; +} + +div.media { + border: 1px solid #e6e6e6; + float: right; + height: 240px; + margin: 0 5px; + overflow: hidden; + width: 320px; +} + +.media-container { + display: grid; + grid-template-columns: 25px auto 25px; + align-items: center; + flex: 1 1; + overflow: hidden; + height: 200px; +} + +.media-container--fullscreen { + grid-template-columns: 0px auto 0px; +} + +.media-container__nav--right, +.media-container__nav--left { + text-align: center; + cursor: pointer; +} + +.media-container__viewport { + cursor: pointer; + text-align: center; + height: inherit; +} +.media-container__viewport img, +.media-container__viewport video { + object-fit: cover; + width: 100%; + max-height: 100%; +} + +.media__name, +.media__counter { + display: flex; + flex-direction: row; + justify-content: space-around; + flex: 0 0 25px; + align-items: center; +} + +.collapsible td:not(.col-links) { + cursor: pointer; +} +.collapsible td:not(.col-links):hover::after { + color: #bbb; + font-style: italic; + cursor: pointer; +} + +.col-result { + width: 130px; +} +.col-result:hover::after { + content: " (hide details)"; +} + +.col-result.collapsed:hover::after { + content: " (show details)"; +} + +#environment-header h2:hover::after { + content: " (hide details)"; + color: #bbb; + font-style: italic; + cursor: pointer; + font-size: 12px; +} + +#environment-header.collapsed h2:hover::after { + content: " (show details)"; + color: #bbb; + font-style: italic; + cursor: pointer; + font-size: 12px; +} + +/*------------------ + * 3. Sorting items + *------------------*/ +.sortable { + cursor: pointer; +} +.sortable.desc:after { + content: " "; + position: relative; + left: 5px; + bottom: -12.5px; + border: 10px solid #4caf50; + border-bottom: 0; + border-left-color: transparent; + border-right-color: transparent; +} +.sortable.asc:after { + content: " "; + position: relative; + left: 5px; + bottom: 12.5px; + border: 10px solid #4caf50; + border-top: 0; + border-left-color: transparent; + border-right-color: transparent; +} + +.hidden, .summary__reload__button.hidden { + display: none; +} + +.summary__data { + flex: 0 0 550px; +} +.summary__reload { + flex: 1 1; + display: flex; + justify-content: center; +} +.summary__reload__button { + flex: 0 0 300px; + display: flex; + color: white; + font-weight: bold; + background-color: #4caf50; + text-align: center; + justify-content: center; + align-items: center; + border-radius: 3px; + cursor: pointer; +} +.summary__reload__button:hover { + background-color: #46a049; +} +.summary__spacer { + flex: 0 0 550px; +} + +.controls { + display: flex; + justify-content: space-between; +} + +.filters, +.collapse { + display: flex; + align-items: center; +} +.filters button, +.collapse button { + color: #999; + border: none; + background: none; + cursor: pointer; + text-decoration: underline; +} +.filters button:hover, +.collapse button:hover { + color: #ccc; +} + +.filter__label { + margin-right: 10px; +} diff --git a/docs/docker_scout.yaml b/docs/docker_scout.yaml index 37b012fa..99acc8ba 100644 --- a/docs/docker_scout.yaml +++ b/docs/docker_scout.yaml @@ -2,14 +2,13 @@ command: docker scout short: Command line tool for Docker Scout long: Command line tool for Docker Scout usage: docker scout [command] -pname: docker -plink: docker.yaml cname: - docker scout attestation - docker scout cache - docker scout compare - docker scout config - docker scout cves + - docker scout docker-cli-plugin-hooks - docker scout enroll - docker scout environment - docker scout help @@ -19,6 +18,8 @@ cname: - docker scout quickview - docker scout recommendations - docker scout repo + - docker scout sbom + - docker scout stream - docker scout version - docker scout vex - docker scout watch @@ -28,6 +29,7 @@ clink: - docker_scout_compare.yaml - docker_scout_config.yaml - docker_scout_cves.yaml + - docker_scout_docker-cli-plugin-hooks.yaml - docker_scout_enroll.yaml - docker_scout_environment.yaml - docker_scout_help.yaml @@ -37,33 +39,13 @@ clink: - docker_scout_quickview.yaml - docker_scout_recommendations.yaml - docker_scout_repo.yaml + - docker_scout_sbom.yaml + - docker_scout_stream.yaml - docker_scout_version.yaml - docker_scout_vex.yaml - docker_scout_watch.yaml -options: - - option: debug - value_type: bool - default_value: "false" - description: Debug messages - deprecated: false - hidden: true - experimental: false - experimentalcli: false - kubernetes: false - swarm: false - - option: verbose-debug - value_type: bool - default_value: "false" - description: Verbose debug - deprecated: false - hidden: true - experimental: false - experimentalcli: false - kubernetes: false - swarm: false deprecated: false experimental: false experimentalcli: false kubernetes: false -swarm: false - +swarm: false \ No newline at end of file diff --git a/docs/reporte.html b/docs/reporte.html new file mode 100644 index 00000000..6b4ecb4c --- /dev/null +++ b/docs/reporte.html @@ -0,0 +1,1094 @@ + + + + + reporte.html + + + + +

reporte.html

+

Report generated on 03-Aug-2026 at 20:16:24 by pytest-html + v4.2.0

+
+

Environment

+
+
+ + + + + +
+
+

Summary

+
+
+

0 test took 60 ms.

+

(Un)check the boxes to filter the results.

+
+ +
+
+
+
+ + 0 Failed, + + 0 Passed, + + 0 Skipped, + + 0 Expected failures, + + 0 Unexpected passes, + + 0 Errors, + + 0 Reruns + + 0 Retried, +
+
+  /  +
+
+
+
+
+
+
+
+ + + + + + + + + +
ResultTestDurationLinks
+ + + \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..ca92d5e6 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +testpaths = tests +python_files = test_*.py +addopts = -ra --tb=short diff --git a/reporte.html b/reporte.html new file mode 100644 index 00000000..cab9eb9a --- /dev/null +++ b/reporte.html @@ -0,0 +1,1094 @@ + + + + + reporte.html + + + + +

reporte.html

+

Report generated on 03-Aug-2026 at 20:35:21 by pytest-html + v4.2.0

+
+

Environment

+
+
+ + + + + +
+
+

Summary

+
+
+

660 tests took 00:00:01.

+

(Un)check the boxes to filter the results.

+
+ +
+
+
+
+ + 0 Failed, + + 660 Passed, + + 0 Skipped, + + 0 Expected failures, + + 0 Unexpected passes, + + 0 Errors, + + 0 Reruns + + 0 Retried, +
+
+  /  +
+
+
+
+
+
+
+
+ + + + + + + + + +
ResultTestDurationLinks
+ + + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..b3c4c2c8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pytest>=8.0 +pyyaml>=6.0 +pytest-html>=4.1 diff --git a/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc b/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..130cfcd098f837503fbd8a6442a916d34a7eaee6 GIT binary patch literal 3641 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 literal 0 HcmV?d00001 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..6926c692 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,106 @@ +""" +Fixtures compartidos para las pruebas de validación de los archivos +docker_scout_*.yaml del repositorio docker/scout-cli. + +Estas pruebas NO prueban el binario de la CLI (closed-source), sino los +artefactos de documentación/configuración versionados en docs/: los YAML +que describen cada comando (y que generan los .md) y su consistencia +estructural y referencial. +""" +import pathlib +import re + +import pytest +import yaml + +# Carpeta docs/ del repo. Por defecto apunta a la carpeta de ejemplo +# incluida en este proyecto. Para correr las pruebas contra el repo +# real, exporta la variable de entorno SCOUT_DOCS_DIR, por ejemplo: +# +# export SCOUT_DOCS_DIR=/ruta/a/scout-cli/docs +# pytest +# +import os + +DEFAULT_DOCS_DIR = pathlib.Path(__file__).parent.parent / "docs" +DOCS_DIR = pathlib.Path(os.environ.get("SCOUT_DOCS_DIR", DEFAULT_DOCS_DIR)) + +YAML_FILENAME_RE = re.compile(r"^docker_scout(_[a-z0-9-]+)*\.yaml$") + +# value_type conocidos usados por los comandos de docker scout +KNOWN_VALUE_TYPES = { + "bool", + "string", + "stringSlice", + "stringArray", + "int", + "int64", + "float32", + "float64", + "duration", +} + +REQUIRED_TOP_LEVEL_FIELDS = { + "command", + "short", + "long", + "deprecated", + "experimental", + "experimentalcli", + "kubernetes", + "swarm", +} + +REQUIRED_OPTION_FIELDS = { + "option", + "value_type", + "description", + "deprecated", + "hidden", + "experimental", + "experimentalcli", + "kubernetes", + "swarm", +} + +BOOL_FIELDS = {"deprecated", "experimental", "experimentalcli", "kubernetes", "swarm"} + + +def discover_yaml_files(): + if not DOCS_DIR.exists(): + return [] + return sorted(DOCS_DIR.glob("docker_scout*.yaml")) + + +@pytest.fixture(scope="session") +def docs_dir(): + return DOCS_DIR + + +@pytest.fixture(scope="session") +def all_yaml_paths(): + paths = discover_yaml_files() + if not paths: + pytest.fail( + f"No se encontraron archivos docker_scout*.yaml en {DOCS_DIR}. " + "Define SCOUT_DOCS_DIR apuntando a la carpeta docs/ del repo clonado." + ) + return paths + + +@pytest.fixture(scope="session") +def loaded_docs(all_yaml_paths): + """dict: {path -> contenido parseado del yaml}""" + docs = {} + for path in all_yaml_paths: + with open(path, "r", encoding="utf-8") as fh: + docs[path] = yaml.safe_load(fh) + return docs + + +def pytest_generate_tests(metafunc): + """Parametriza automáticamente cualquier test que pida 'yaml_path'.""" + if "yaml_path" in metafunc.fixturenames: + paths = discover_yaml_files() + ids = [p.name for p in paths] + metafunc.parametrize("yaml_path", paths, ids=ids) diff --git a/tests/test_yaml_schema.py b/tests/test_yaml_schema.py new file mode 100644 index 00000000..2b0036e8 --- /dev/null +++ b/tests/test_yaml_schema.py @@ -0,0 +1,248 @@ +""" +Pruebas unitarias de los archivos docker_scout_*.yaml (docs/) del +repositorio docker/scout-cli. + +Qué se valida: + 1. Sintaxis: cada YAML parsea correctamente y produce un dict. + 2. Esquema base: campos obligatorios presentes y con el tipo correcto. + 3. Comandos "hoja": usage/options bien formados. + 4. Comandos "padre": cname/clink consistentes entre sí. + 5. Referencias cruzadas: plink/pname apuntan a un archivo/comando real + y esa relación es recíproca (el padre lista al hijo y viceversa). + 6. Documentación: existe un .md correspondiente a cada .yaml. +""" +import yaml + +from conftest import ( + BOOL_FIELDS, + KNOWN_VALUE_TYPES, + REQUIRED_OPTION_FIELDS, + REQUIRED_TOP_LEVEL_FIELDS, + YAML_FILENAME_RE, +) + + +# --------------------------------------------------------------------------- +# 1. Sintaxis +# --------------------------------------------------------------------------- + +def test_filename_matches_naming_convention(yaml_path): + assert YAML_FILENAME_RE.match(yaml_path.name), ( + f"'{yaml_path.name}' no sigue la convención docker_scout(_subcomando)*.yaml" + ) + + +def test_yaml_parses_and_is_a_mapping(yaml_path): + with open(yaml_path, "r", encoding="utf-8") as fh: + content = yaml.safe_load(fh) + assert isinstance(content, dict), f"{yaml_path.name} no produce un mapeo YAML válido" + + +# --------------------------------------------------------------------------- +# 2. Esquema base (todos los archivos) +# --------------------------------------------------------------------------- + +def test_required_top_level_fields_present(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + missing = REQUIRED_TOP_LEVEL_FIELDS - doc.keys() + assert not missing, f"{yaml_path.name}: faltan campos obligatorios {missing}" + + +def test_boolean_fields_have_bool_type(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + for field in BOOL_FIELDS: + if field in doc: + assert isinstance(doc[field], bool), ( + f"{yaml_path.name}: el campo '{field}' debería ser booleano, " + f"se encontró {type(doc[field]).__name__}" + ) + + +def test_command_field_starts_with_docker_scout(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + assert doc["command"].startswith("docker scout"), ( + f"{yaml_path.name}: 'command' debería empezar con 'docker scout', " + f"tiene '{doc['command']}'" + ) + + +def test_short_and_long_are_non_empty_strings(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + for field in ("short", "long"): + value = doc.get(field, "") + assert isinstance(value, str) and value.strip(), ( + f"{yaml_path.name}: '{field}' debe ser texto no vacío" + ) + + +def test_command_name_matches_filename(yaml_path, loaded_docs): + """docker scout integration list -> docker_scout_integration_list.yaml""" + doc = loaded_docs[yaml_path] + expected_filename = doc["command"].replace(" ", "_") + ".yaml" + assert yaml_path.name == expected_filename, ( + f"El nombre de archivo '{yaml_path.name}' no coincide con el comando " + f"'{doc['command']}' (se esperaba '{expected_filename}')" + ) + + +# --------------------------------------------------------------------------- +# 3. Comandos "hoja" (los que tienen 'usage' / 'options' propias) +# --------------------------------------------------------------------------- + +def _validate_option_entry(entry, context): + missing = REQUIRED_OPTION_FIELDS - entry.keys() + assert not missing, f"{context}: opción incompleta, faltan {missing}: {entry}" + assert entry["value_type"] in KNOWN_VALUE_TYPES, ( + f"{context}: value_type desconocido '{entry['value_type']}' en opción " + f"'{entry['option']}'" + ) + if "shorthand" in entry and entry["shorthand"] is not None: + assert len(entry["shorthand"]) == 1, ( + f"{context}: el shorthand de '{entry['option']}' debe ser un solo caracter" + ) + for field in ("deprecated", "hidden", "experimental", "experimentalcli", "kubernetes", "swarm"): + assert isinstance(entry[field], bool), ( + f"{context}: el campo '{field}' de la opción '{entry['option']}' debe ser booleano" + ) + + +def test_usage_present_when_command_is_leaf(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + if "options" in doc: + assert "usage" in doc and doc["usage"].strip(), ( + f"{yaml_path.name}: tiene 'options' pero no define 'usage'" + ) + assert doc["usage"].startswith(doc["command"]), ( + f"{yaml_path.name}: 'usage' debería iniciar con el propio comando" + ) + + +def test_options_are_well_formed(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + for entry in doc.get("options", []): + _validate_option_entry(entry, f"{yaml_path.name} [options]") + + +def test_inherited_options_are_well_formed(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + for entry in doc.get("inherited_options", []): + _validate_option_entry(entry, f"{yaml_path.name} [inherited_options]") + + +def test_option_names_are_unique_within_file(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + names = [e["option"] for e in doc.get("options", [])] + duplicates = {n for n in names if names.count(n) > 1} + assert not duplicates, f"{yaml_path.name}: opciones duplicadas en 'options': {duplicates}" + + +def test_options_and_inherited_options_do_not_collide(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + own = {e["option"] for e in doc.get("options", [])} + inherited = {e["option"] for e in doc.get("inherited_options", [])} + overlap = own & inherited + assert not overlap, ( + f"{yaml_path.name}: las opciones {overlap} están tanto en 'options' " + "como en 'inherited_options'" + ) + + +# --------------------------------------------------------------------------- +# 4. Comandos "padre" (los que tienen 'cname' / 'clink') +# --------------------------------------------------------------------------- + +def test_cname_and_clink_have_same_length(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + if "cname" in doc or "clink" in doc: + assert "cname" in doc and "clink" in doc, ( + f"{yaml_path.name}: debe tener tanto 'cname' como 'clink' o ninguno" + ) + assert len(doc["cname"]) == len(doc["clink"]), ( + f"{yaml_path.name}: 'cname' ({len(doc['cname'])} items) y 'clink' " + f"({len(doc['clink'])} items) tienen longitudes distintas" + ) + + +def test_clink_filenames_match_cname_entries(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + for cname, clink in zip(doc.get("cname", []), doc.get("clink", [])): + expected = cname.replace(" ", "_") + ".yaml" + assert clink == expected, ( + f"{yaml_path.name}: cname '{cname}' debería enlazar a '{expected}', " + f"pero clink dice '{clink}'" + ) + + +def test_clink_files_exist(yaml_path, loaded_docs, docs_dir): + doc = loaded_docs[yaml_path] + for clink in doc.get("clink", []): + target = docs_dir / clink + assert target.exists(), f"{yaml_path.name}: clink '{clink}' no existe en {docs_dir}" + + +def test_a_leaf_command_has_no_children(yaml_path, loaded_docs): + """Un comando no debería mezclar 'options' propias con 'cname' (hoja vs. padre).""" + doc = loaded_docs[yaml_path] + if "options" in doc: + assert "cname" not in doc, ( + f"{yaml_path.name}: tiene 'options' y 'cname' a la vez; se esperaba " + "que fuera un comando hoja o un comando padre, no ambos" + ) + + +# --------------------------------------------------------------------------- +# 5. Referencias cruzadas plink/pname (relación bidireccional padre-hijo) +# --------------------------------------------------------------------------- + +def test_plink_file_exists(yaml_path, loaded_docs, docs_dir): + doc = loaded_docs[yaml_path] + if "plink" in doc: + target = docs_dir / doc["plink"] + assert target.exists(), f"{yaml_path.name}: plink '{doc['plink']}' no existe en {docs_dir}" + + +def test_pname_and_plink_are_consistent(yaml_path, loaded_docs): + doc = loaded_docs[yaml_path] + if "pname" in doc or "plink" in doc: + assert "pname" in doc and "plink" in doc, ( + f"{yaml_path.name}: debe tener tanto 'pname' como 'plink' o ninguno" + ) + expected_plink = doc["pname"].replace(" ", "_") + ".yaml" + assert doc["plink"] == expected_plink, ( + f"{yaml_path.name}: pname '{doc['pname']}' debería enlazar a " + f"'{expected_plink}', pero plink dice '{doc['plink']}'" + ) + + +def test_parent_actually_lists_this_command_as_child(yaml_path, loaded_docs, docs_dir): + """Si A dice que su padre es B, entonces B debe listar a A en su cname/clink.""" + doc = loaded_docs[yaml_path] + if "plink" not in doc: + return # es la raíz (docker_scout.yaml), no tiene padre + parent_path = docs_dir / doc["plink"] + with open(parent_path, "r", encoding="utf-8") as fh: + parent_doc = yaml.safe_load(fh) + assert doc["command"] in parent_doc.get("cname", []), ( + f"{yaml_path.name}: el padre '{doc['plink']}' no lista a " + f"'{doc['command']}' en su 'cname'" + ) + assert yaml_path.name in parent_doc.get("clink", []), ( + f"{yaml_path.name}: el padre '{doc['plink']}' no lista '{yaml_path.name}' " + "en su 'clink'" + ) + + +# --------------------------------------------------------------------------- +# 6. Documentación asociada (.md) +# --------------------------------------------------------------------------- + +def test_corresponding_md_file_exists(yaml_path, docs_dir): + # docker_scout_cves.yaml -> scout_cves.md ; docker_scout.yaml -> scout.md + md_name = yaml_path.stem.replace("docker_scout", "scout", 1) + ".md" + if md_name == ".md": + md_name = "scout.md" + md_path = docs_dir / md_name + assert md_path.exists(), ( + f"{yaml_path.name}: no se encontró la documentación correspondiente " + f"'{md_name}' en {docs_dir}" + ) From d3583ed633a81c51c443584ea387e5e349872a0b Mon Sep 17 00:00:00 2001 From: Koreloc23 Date: Sun, 9 Aug 2026 21:01:14 -0600 Subject: [PATCH 2/8] ci: add YAML schema validation workflow --- .github/workflows/validate_yaml.yml | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/validate_yaml.yml diff --git a/.github/workflows/validate_yaml.yml b/.github/workflows/validate_yaml.yml new file mode 100644 index 00000000..4aec00e0 --- /dev/null +++ b/.github/workflows/validate_yaml.yml @@ -0,0 +1,36 @@ +name: Validate YAML Schema + +on: + push: + paths: + - 'docs/**.yaml' + - 'tests/**' + pull_request: + paths: + - 'docs/**.yaml' + - 'tests/**' + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: pip install pytest pytest-html pyyaml + + - name: Run YAML schema tests + run: pytest tests/ --html=reporte.html --self-contained-html + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v4 + with: + name: reporte-pruebas + path: reporte.html \ No newline at end of file From 86130259100f3828af47f6a358da18bfcb78fdd9 Mon Sep 17 00:00:00 2001 From: Koreloc23 Date: Sun, 9 Aug 2026 21:11:04 -0600 Subject: [PATCH 3/8] ci: add manual trigger to YAML schema validation workflow --- .github/workflows/validate_yaml.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/validate_yaml.yml b/.github/workflows/validate_yaml.yml index 4aec00e0..43c1f057 100644 --- a/.github/workflows/validate_yaml.yml +++ b/.github/workflows/validate_yaml.yml @@ -9,6 +9,8 @@ on: paths: - 'docs/**.yaml' - 'tests/**' + + workflow_dispatch: jobs: validate: From ba676f9a5bb8f408060eadec450f19fa0c9e3b70 Mon Sep 17 00:00:00 2001 From: Koreloc23 Date: Sun, 9 Aug 2026 21:17:21 -0600 Subject: [PATCH 4/8] fix: move options to inherited_options in docker_scout_policy.yaml --- docs/docker_scout_policy.yaml | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/docs/docker_scout_policy.yaml b/docs/docker_scout_policy.yaml index e2772f5e..32478195 100644 --- a/docs/docker_scout_policy.yaml +++ b/docs/docker_scout_policy.yaml @@ -6,14 +6,13 @@ long: |- The image analysis is uploaded to Docker Scout where policies get evaluated. The policy evaluation results may take a few minutes to become available. -usage: docker scout policy [IMAGE | REPO] pname: docker scout plink: docker_scout.yaml cname: - docker scout policy publish clink: - docker_scout_policy_publish.yaml -options: +inherited_options: - option: exit-code shorthand: e value_type: bool @@ -113,7 +112,6 @@ options: experimentalcli: false kubernetes: false swarm: false -inherited_options: - option: debug value_type: bool default_value: "false" @@ -134,33 +132,8 @@ inherited_options: experimentalcli: false kubernetes: false swarm: false -examples: |- - ### Evaluate policies against an image and display the results - - ```console - $ docker scout policy dockerscoutpolicy/customers-api-service:0.0.1 - ``` - - ### Evaluate policies against an image for a specific organization - - ```console - $ docker scout policy dockerscoutpolicy/customers-api-service:0.0.1 --org dockerscoutpolicy - ``` - - ### Evaluate policies against an image with a specific platform - - ```console - $ docker scout policy dockerscoutpolicy/customers-api-service:0.0.1 --platform linux/amd64 - ``` - - ### Compare policy results for a repository in a specific environment - - ```console - $ docker scout policy dockerscoutpolicy/customers-api-service --to-env production - ``` deprecated: false experimental: false experimentalcli: true kubernetes: false -swarm: false - +swarm: false \ No newline at end of file From c065e9d3367068c624877db186162f9d35d23891 Mon Sep 17 00:00:00 2001 From: Koreloc23 Date: Sun, 9 Aug 2026 21:22:26 -0600 Subject: [PATCH 5/8] ci: add weekly security scan workflow --- .github/workflows/security_scan.yml | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/security_scan.yml diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml new file mode 100644 index 00000000..8a723eb0 --- /dev/null +++ b/.github/workflows/security_scan.yml @@ -0,0 +1,31 @@ +name: Weekly Security Scan + +on: + schedule: + - cron: '0 8 * * 1' + workflow_dispatch: + +jobs: + security: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install dependencies + run: pip install pytest pytest-html pyyaml + + - name: Run YAML schema tests + run: pytest tests/ --html=security-report.html --self-contained-html + + - name: Upload security report + if: always() + uses: actions/upload-artifact@v4 + with: + name: security-report + path: security-report.html \ No newline at end of file From bb614fc1adbe271f2ea4888b37dad4fe6bb41076 Mon Sep 17 00:00:00 2001 From: Koreloc23 Date: Sun, 9 Aug 2026 21:30:40 -0600 Subject: [PATCH 6/8] ci: add recommendations and watch steps to test workflow --- .github/workflows/release-branch.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/release-branch.yml b/.github/workflows/release-branch.yml index f2e1c31e..cc7cb92f 100644 --- a/.github/workflows/release-branch.yml +++ b/.github/workflows/release-branch.yml @@ -29,6 +29,15 @@ jobs: run: ./docker-scout quickview alpine:latest - name: TEST docker scout cves run: ./docker-scout cves docker/scout-demo-service:main + - name: TEST docker scout recommendations + run: ./docker-scout recommendations docker/scout-demo-service:main + - name: Watch repository after release + run: | + ./docker-scout watch \ + --org my-org \ + --repository registry.example.com/scout-demo-service \ + --tag latest \ + --dry-run - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx From c12142d0e64c9b1257d529a6b2378f28aecc751d Mon Sep 17 00:00:00 2001 From: Koreloc23 Date: Sun, 9 Aug 2026 21:32:56 -0600 Subject: [PATCH 7/8] test: trigger PR workflow --- README.md | 804 +++++++++++++++++++++++++++--------------------------- 1 file changed, 403 insertions(+), 401 deletions(-) diff --git a/README.md b/README.md index 2bd0ee7f..43815277 100644 --- a/README.md +++ b/README.md @@ -1,401 +1,403 @@ -- [Docker Scout](#docker-scout) -- [Usage](#usage) -- [CLI Plugin Installation](#cli-plugin-installation) -- [Run as container](#run-as-container) -- [CI integration](#ci-integration) -- [License](#license) - -# Docker Scout - -[Docker Scout](https://www.docker.com/products/docker-scout/) is a set of software supply chain features integrated into Docker's user interfaces and command line interface (CLI). These features offer comprehensive visibility into the structure and security of container images. -This repository contains installable binaries of the `docker scout` CLI plugin. - -## Usage - -The [CLI documentation is available in this repository](./docs/scout.md). - -See the [reference documentation](https://docs.docker.com/scout) to learn about Docker Scout including Docker Desktop and Docker Hub integrations. - -### Environment Variables - -The following environment variables are available to configure the Scout CLI: - -| Name | Format | Description | -| ---- | ------ | ----------- | -| `DOCKER_SCOUT_CACHE_FORMAT` | String | Format of the local image cache; can be `oci` or `tar` | -| `DOCKER_SCOUT_CACHE_DIR` | String | Directory where the local SBOM cache is stored | -| `DOCKER_SCOUT_NO_CACHE` | Boolean | Disable the local SBOM cache | -| `DOCKER_SCOUT_OFFLINE` | Boolean | Offline mode during SBOM indexing | -| `DOCKER_SCOUT_REGISTRY_TOKEN` | String | Registry Access token to authenticate when pulling images | -| `DOCKER_SCOUT_REGISTRY_USER` | String | Registry user name to authenticate when pulling images | -| `DOCKER_SCOUT_REGISTRY_PASSWORD` | String | Registry password/PAT to authenticate when pulling images | -| `DOCKER_SCOUT_HUB_USER` | String | Docker Hub user name to authenticate against the Docker Scout backend | -| `DOCKER_SCOUT_HUB_PASSWORD` | String | Docker Hub password/PAT to authenticate against the Docker Scout backend | -| `DOCKER_SCOUT_NEW_VERSION_WARN` | Boolean | Warn about new versions of the Docker Scout CLI | -| `DOCKER_SCOUT_EXPERIMENTAL_WARN` | Boolean | Warn about experimental features | -| `DOCKER_SCOUT_EXPERIMENTAL_POLICY_OUTPUT` | Boolean | Disable experimental policy output | - -You can found further information about environment variables [here](https://docs.docker.com/scout/how-tos/configure-cli/). - - -## CLI Plugin Installation - -### Docker Desktop - -`docker scout` CLI plugin is available by default on [Docker Desktop](https://docs.docker.com/desktop/) starting with version `4.17`. - -### Manual Installation - -To install it manually: - -- Download the `docker-scout` binary corresponding to your platform from the [latest](https://github.com/docker/scout-cli/releases/latest) or [other](https://github.com/docker/scout-cli/releases) releases. -- Uncompress it as - - `docker-scout` on _Linux_ and _macOS_ - - `docker-scout.exe` on _Windows_ -- Copy the binary to the `scout` directory - - `$HOME/.docker/scout` on _Linux_ and _macOS_ - - `%USERPROFILE%\.docker\scout` on _Windows_ -- Make it executable on _Linux_ and _macOS_ - - `chmod +x $HOME/.docker/scout/docker-scout` -- Authorize the binary to be executable on _macOS_ - - `xattr -d com.apple.quarantine $HOME/.docker/scout/docker-scout` -- Add the `scout` directory to your `.docker/config.json` as a plugin directory - - `$HOME/.docker/config.json` on _Linux_ and _macOS_ - - `%USERPROFILE%\.docker\config.json` on _Windows_ - - Add the `cliPluginsExtraDirs` property to the `config.json` file -``` -{ - ... - "cliPluginsExtraDirs": [ - "" - ], - ... -} -``` - -### Script Installation (macOS and Linux) - -To install, run the following command in your terminal: - -```shell -curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- -``` - -## Run as container - -A container image to run the Docker Scout CLI in containerized environments is available at [docker/scout-cli](https://hub.docker.com/r/docker/scout-cli). - -## CI Integration - -Docker Scout CLI can be used in CI environments. See below for the various ways to integrate the CLI into your CI pipelines. - -### GitHub Action - -An early prototype of running the Docker Scout CLI as part of a GitHub Action workflow is available at [docker/scout-action](https://github.com/docker/scout-action). - -The following GitHub Action workflow can be used as a template to integrate Docker Scout: - -```yaml -name: Docker - -on: - push: - tags: [ "*" ] - branches: - - 'main' - pull_request: - branches: [ "**" ] - -env: - # Use docker.io for Docker Hub if empty - REGISTRY: docker.io - IMAGE_NAME: ${{ github.repository }} - SHA: ${{ github.event.pull_request.head.sha || github.event.after }} - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - ref: ${{ env.SHA }} - - - name: Setup Docker buildx - uses: docker/setup-buildx-action@v2.5.0 - - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@v2.1.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PAT }} - - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v4.4.0 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - labels: | - org.opencontainers.image.revision=${{ env.SHA }} - tags: | - type=edge,branch=$repo.default_branch - type=semver,pattern=v{{version}} - type=sha,prefix=,suffix=,format=short - - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image - id: build-and-push - uses: docker/build-push-action@v4.0.0 - with: - context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Docker Scout - id: docker-scout - if: ${{ github.event_name == 'pull_request' }} - uses: docker/scout-action@dd36f5b0295baffa006aa6623371f226cc03e506 - with: - command: cves - image: ${{ steps.meta.outputs.tags }} - only-severities: critical,high - exit-code: true -``` - -### GitLab - -Use the following pipeline definition as a template to get Docker Scout integrated in GitLab CI: - -```yaml -docker-build: - image: docker:latest - stage: build - services: - - docker:dind - before_script: - - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY - - # Install curl and the Docker Scout CLI - - | - apk add --update curl - curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- - apk del curl - rm -rf /var/cache/apk/* - # Login to Docker Hub required for Docker Scout CLI - - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin - script: - - | - if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then - tag="" - echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'" - else - tag=":$CI_COMMIT_REF_SLUG" - echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag" - fi - - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" . - - - | - if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then - # Get a CVE report for the built image and fail the pipeline when critical or high CVEs are detected - docker scout cves "$CI_REGISTRY_IMAGE${tag}" --exit-code --only-severity critical,high - else - # Compare image from branch with latest image from the default branch and fail if new critical or high CVEs are detected - docker scout compare "$CI_REGISTRY_IMAGE${tag}" --to "$CI_REGISTRY_IMAGE:latest" --exit-on vulnerability,policy --only-severity critical,high --ignore-unchanged - fi - - - docker push "$CI_REGISTRY_IMAGE${tag}" - rules: - - if: $CI_COMMIT_BRANCH - exists: - - Dockerfile -``` - -### CircleCI - -Use the following pipeline definition as a template to get Docker Scout integrated in CircleCI project: - -```yaml -version: 2.1 - -jobs: - - build: - - docker: - - image: cimg/base:stable - - environment: - IMAGE_TAG: docker/scout-demo-service:latest - - steps: - # Checkout the repository files - - checkout - - # Set up a separate Docker environment to run `docker` commands in - - setup_remote_docker: - version: 20.10.24 - - # Install Docker Scout and login to Docker Hub - - run: - name: Install Docker Scout - command: | - env - curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- -b /home/circleci/bin - echo $DOCKER_HUB_PAT | docker login -u $DOCKER_HUB_USER --password-stdin - - # Build the Docker image - - run: - name: Build Docker image - command: docker build -t $IMAGE_TAG . - - # Run Docker Scout - - run: - name: Scan image for CVEs - command: | - docker-scout cves $IMAGE_TAG --exit-code --only-severity critical,high - -workflows: - build-docker-image: - jobs: - - build -``` - -### Microsoft Azure DevOps Pipelines - -Use the following pipeline definition as a template to get Docker Scout integrated in Azure DevOps Pipelines: - -```yaml -trigger: -- main - -resources: -- repo: self - -variables: - tag: '$(Build.BuildId)' - image: 'vonwig/nodejs-service' - -stages: -- stage: Build - displayName: Build image - jobs: - - job: Build - displayName: Build - pool: - vmImage: ubuntu-latest - steps: - - task: Docker@2 - displayName: Build an image - inputs: - command: build - dockerfile: '$(Build.SourcesDirectory)/Dockerfile' - repository: $(image) - tags: | - $(tag) - - task: CmdLine@2 - displayName: Find CVEs on image - inputs: - script: | - # Install the Docker Scout CLI - curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- - # Login to Docker Hub required for Docker Scout CLI - docker login -u $(DOCKER_HUB_USER) -p $(DOCKER_HUB_PAT) - # Get a CVE report for the built image and fail the pipeline when critical or high CVEs are detected - docker scout cves $(image):$(tag) --exit-code --only-severity critical,high -``` - -### Jenkins - -The following snippet can be added to a `Jenkinsfile` to install and analyze images: - -```groovy - stage('Analyze image') { - steps { - // Install Docker Scout - sh 'curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- -b /usr/local/bin' - - // Log into Docker Hub - sh 'echo $DOCKER_HUB_PAT | docker login -u $DOCKER_HUB_USER --password-stdin' - - // Analyze and fail on critical or high vulnerabilities - sh 'docker-scout cves $IMAGE_TAG --exit-code --only-severity critical,high' - } - } -``` - -This example assume two secrets to be available to authenticate against Docker Hub, called `DOCKER_HUB_USER` and `DOCKER_HUB_PAT`. - -### Bitbucket - -Use the following pipeline definition as a template to get Docker Scout integrated in Bitbucket Pipelines: - -```yaml -image: docker - -pipelines: - default: - - step: - name: Build - services: - - docker - caches: - - docker - script: - - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin $CI_REGISTRY - - # Install curl and the Docker Scout CLI - - | - export DOCKER_BUILDKIT=0 - apk add --update curl - curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- - apk del curl - rm -rf /var/cache/apk/* - # Login to Docker Hub required for Docker Scout CLI - - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin - - - | - export DEVELOPMENT_BRANCH="main" - if [[ "$BITBUCKET_BRANCH" == "$DEVELOPMENT_BRANCH" ]]; then # Bitbucket uses master by default, adjust if your default branch is different - tag=":latest" - echo "Running on default branch '$DEVELOPMENT_BRANCH': tag = 'latest'" - else - tag=":$BITBUCKET_COMMIT" - echo "Running on branch '$BITBUCKET_BRANCH': tag = $tag" - fi - - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" . - - - | - if [[ "$BITBUCKET_BRANCH" == "$DEVELOPMENT_BRANCH" ]]; then - # Get a CVE report for the built image and fail the pipeline when critical or high CVEs are detected - docker scout cves "$CI_REGISTRY_IMAGE${tag}" --exit-code --only-severity critical,high - else - # Compare image from branch with latest image from the default branch and fail if new critical or high CVEs are detected - docker scout compare "$CI_REGISTRY_IMAGE${tag}" --to "$CI_REGISTRY_IMAGE:latest" --exit-on vulnerability,policy --only-severity critical,high --ignore-unchanged - fi - - docker push "$CI_REGISTRY_IMAGE${tag}" - -definitions: - services: - docker: - memory: 2048 # Optional: Increase if needed -``` - -This example assumes two secrets to be available to authenticate against Docker Hub, called `DOCKER_HUB_USER` and `DOCKER_HUB_PAT`, also is necessary more two secrets called `CI_REGISTRY`, `CI_REGISTRY_IMAGE` about registry info. - -## License - -The Docker Scout CLI is licensed under the Terms and Conditions of the [Docker Subscription Service Agreement](https://www.docker.com/legal/docker-subscription-service-agreement/). +- [Docker Scout](#docker-scout) +- [Usage](#usage) +- [CLI Plugin Installation](#cli-plugin-installation) +- [Run as container](#run-as-container) +- [CI integration](#ci-integration) +- [License](#license) + +# Docker Scout + +[Docker Scout](https://www.docker.com/products/docker-scout/) is a set of software supply chain features integrated into Docker's user interfaces and command line interface (CLI). These features offer comprehensive visibility into the structure and security of container images. +This repository contains installable binaries of the `docker scout` CLI plugin. + +## Usage + +The [CLI documentation is available in this repository](./docs/scout.md). + +See the [reference documentation](https://docs.docker.com/scout) to learn about Docker Scout including Docker Desktop and Docker Hub integrations. + +### Environment Variables + +The following environment variables are available to configure the Scout CLI: + +| Name | Format | Description | +| ---- | ------ | ----------- | +| `DOCKER_SCOUT_CACHE_FORMAT` | String | Format of the local image cache; can be `oci` or `tar` | +| `DOCKER_SCOUT_CACHE_DIR` | String | Directory where the local SBOM cache is stored | +| `DOCKER_SCOUT_NO_CACHE` | Boolean | Disable the local SBOM cache | +| `DOCKER_SCOUT_OFFLINE` | Boolean | Offline mode during SBOM indexing | +| `DOCKER_SCOUT_REGISTRY_TOKEN` | String | Registry Access token to authenticate when pulling images | +| `DOCKER_SCOUT_REGISTRY_USER` | String | Registry user name to authenticate when pulling images | +| `DOCKER_SCOUT_REGISTRY_PASSWORD` | String | Registry password/PAT to authenticate when pulling images | +| `DOCKER_SCOUT_HUB_USER` | String | Docker Hub user name to authenticate against the Docker Scout backend | +| `DOCKER_SCOUT_HUB_PASSWORD` | String | Docker Hub password/PAT to authenticate against the Docker Scout backend | +| `DOCKER_SCOUT_NEW_VERSION_WARN` | Boolean | Warn about new versions of the Docker Scout CLI | +| `DOCKER_SCOUT_EXPERIMENTAL_WARN` | Boolean | Warn about experimental features | +| `DOCKER_SCOUT_EXPERIMENTAL_POLICY_OUTPUT` | Boolean | Disable experimental policy output | + +You can found further information about environment variables [here](https://docs.docker.com/scout/how-tos/configure-cli/). + + +## CLI Plugin Installation + +### Docker Desktop + +`docker scout` CLI plugin is available by default on [Docker Desktop](https://docs.docker.com/desktop/) starting with version `4.17`. + +### Manual Installation + +To install it manually: + +- Download the `docker-scout` binary corresponding to your platform from the [latest](https://github.com/docker/scout-cli/releases/latest) or [other](https://github.com/docker/scout-cli/releases) releases. +- Uncompress it as + - `docker-scout` on _Linux_ and _macOS_ + - `docker-scout.exe` on _Windows_ +- Copy the binary to the `scout` directory + - `$HOME/.docker/scout` on _Linux_ and _macOS_ + - `%USERPROFILE%\.docker\scout` on _Windows_ +- Make it executable on _Linux_ and _macOS_ + - `chmod +x $HOME/.docker/scout/docker-scout` +- Authorize the binary to be executable on _macOS_ + - `xattr -d com.apple.quarantine $HOME/.docker/scout/docker-scout` +- Add the `scout` directory to your `.docker/config.json` as a plugin directory + - `$HOME/.docker/config.json` on _Linux_ and _macOS_ + - `%USERPROFILE%\.docker\config.json` on _Windows_ + - Add the `cliPluginsExtraDirs` property to the `config.json` file +``` +{ + ... + "cliPluginsExtraDirs": [ + "" + ], + ... +} +``` + +### Script Installation (macOS and Linux) + +To install, run the following command in your terminal: + +```shell +curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- +``` + +## Run as container + +A container image to run the Docker Scout CLI in containerized environments is available at [docker/scout-cli](https://hub.docker.com/r/docker/scout-cli). + +## CI Integration + +Docker Scout CLI can be used in CI environments. See below for the various ways to integrate the CLI into your CI pipelines. + +### GitHub Action + +An early prototype of running the Docker Scout CLI as part of a GitHub Action workflow is available at [docker/scout-action](https://github.com/docker/scout-action). + +The following GitHub Action workflow can be used as a template to integrate Docker Scout: + +```yaml +name: Docker + +on: + push: + tags: [ "*" ] + branches: + - 'main' + pull_request: + branches: [ "**" ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: docker.io + IMAGE_NAME: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.event.after }} + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + ref: ${{ env.SHA }} + + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v2.5.0 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + uses: docker/login-action@v2.1.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PAT }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4.4.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + labels: | + org.opencontainers.image.revision=${{ env.SHA }} + tags: | + type=edge,branch=$repo.default_branch + type=semver,pattern=v{{version}} + type=sha,prefix=,suffix=,format=short + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v4.0.0 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Docker Scout + id: docker-scout + if: ${{ github.event_name == 'pull_request' }} + uses: docker/scout-action@dd36f5b0295baffa006aa6623371f226cc03e506 + with: + command: cves + image: ${{ steps.meta.outputs.tags }} + only-severities: critical,high + exit-code: true +``` + +### GitLab + +Use the following pipeline definition as a template to get Docker Scout integrated in GitLab CI: + +```yaml +docker-build: + image: docker:latest + stage: build + services: + - docker:dind + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + + # Install curl and the Docker Scout CLI + - | + apk add --update curl + curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- + apk del curl + rm -rf /var/cache/apk/* + # Login to Docker Hub required for Docker Scout CLI + - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin + script: + - | + if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then + tag="" + echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'" + else + tag=":$CI_COMMIT_REF_SLUG" + echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag" + fi + - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" . + + - | + if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then + # Get a CVE report for the built image and fail the pipeline when critical or high CVEs are detected + docker scout cves "$CI_REGISTRY_IMAGE${tag}" --exit-code --only-severity critical,high + else + # Compare image from branch with latest image from the default branch and fail if new critical or high CVEs are detected + docker scout compare "$CI_REGISTRY_IMAGE${tag}" --to "$CI_REGISTRY_IMAGE:latest" --exit-on vulnerability,policy --only-severity critical,high --ignore-unchanged + fi + + - docker push "$CI_REGISTRY_IMAGE${tag}" + rules: + - if: $CI_COMMIT_BRANCH + exists: + - Dockerfile +``` + +### CircleCI + +Use the following pipeline definition as a template to get Docker Scout integrated in CircleCI project: + +```yaml +version: 2.1 + +jobs: + + build: + + docker: + - image: cimg/base:stable + + environment: + IMAGE_TAG: docker/scout-demo-service:latest + + steps: + # Checkout the repository files + - checkout + + # Set up a separate Docker environment to run `docker` commands in + - setup_remote_docker: + version: 20.10.24 + + # Install Docker Scout and login to Docker Hub + - run: + name: Install Docker Scout + command: | + env + curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- -b /home/circleci/bin + echo $DOCKER_HUB_PAT | docker login -u $DOCKER_HUB_USER --password-stdin + + # Build the Docker image + - run: + name: Build Docker image + command: docker build -t $IMAGE_TAG . + + # Run Docker Scout + - run: + name: Scan image for CVEs + command: | + docker-scout cves $IMAGE_TAG --exit-code --only-severity critical,high + +workflows: + build-docker-image: + jobs: + - build +``` + +### Microsoft Azure DevOps Pipelines + +Use the following pipeline definition as a template to get Docker Scout integrated in Azure DevOps Pipelines: + +```yaml +trigger: +- main + +resources: +- repo: self + +variables: + tag: '$(Build.BuildId)' + image: 'vonwig/nodejs-service' + +stages: +- stage: Build + displayName: Build image + jobs: + - job: Build + displayName: Build + pool: + vmImage: ubuntu-latest + steps: + - task: Docker@2 + displayName: Build an image + inputs: + command: build + dockerfile: '$(Build.SourcesDirectory)/Dockerfile' + repository: $(image) + tags: | + $(tag) + - task: CmdLine@2 + displayName: Find CVEs on image + inputs: + script: | + # Install the Docker Scout CLI + curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- + # Login to Docker Hub required for Docker Scout CLI + docker login -u $(DOCKER_HUB_USER) -p $(DOCKER_HUB_PAT) + # Get a CVE report for the built image and fail the pipeline when critical or high CVEs are detected + docker scout cves $(image):$(tag) --exit-code --only-severity critical,high +``` + +### Jenkins + +The following snippet can be added to a `Jenkinsfile` to install and analyze images: + +```groovy + stage('Analyze image') { + steps { + // Install Docker Scout + sh 'curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- -b /usr/local/bin' + + // Log into Docker Hub + sh 'echo $DOCKER_HUB_PAT | docker login -u $DOCKER_HUB_USER --password-stdin' + + // Analyze and fail on critical or high vulnerabilities + sh 'docker-scout cves $IMAGE_TAG --exit-code --only-severity critical,high' + } + } +``` + +This example assume two secrets to be available to authenticate against Docker Hub, called `DOCKER_HUB_USER` and `DOCKER_HUB_PAT`. + +### Bitbucket + +Use the following pipeline definition as a template to get Docker Scout integrated in Bitbucket Pipelines: + +```yaml +image: docker + +pipelines: + default: + - step: + name: Build + services: + - docker + caches: + - docker + script: + - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin $CI_REGISTRY + + # Install curl and the Docker Scout CLI + - | + export DOCKER_BUILDKIT=0 + apk add --update curl + curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s -- + apk del curl + rm -rf /var/cache/apk/* + # Login to Docker Hub required for Docker Scout CLI + - echo "$DOCKER_HUB_PAT" | docker login --username "$DOCKER_HUB_USER" --password-stdin + + - | + export DEVELOPMENT_BRANCH="main" + if [[ "$BITBUCKET_BRANCH" == "$DEVELOPMENT_BRANCH" ]]; then # Bitbucket uses master by default, adjust if your default branch is different + tag=":latest" + echo "Running on default branch '$DEVELOPMENT_BRANCH': tag = 'latest'" + else + tag=":$BITBUCKET_COMMIT" + echo "Running on branch '$BITBUCKET_BRANCH': tag = $tag" + fi + - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" . + + - | + if [[ "$BITBUCKET_BRANCH" == "$DEVELOPMENT_BRANCH" ]]; then + # Get a CVE report for the built image and fail the pipeline when critical or high CVEs are detected + docker scout cves "$CI_REGISTRY_IMAGE${tag}" --exit-code --only-severity critical,high + else + # Compare image from branch with latest image from the default branch and fail if new critical or high CVEs are detected + docker scout compare "$CI_REGISTRY_IMAGE${tag}" --to "$CI_REGISTRY_IMAGE:latest" --exit-on vulnerability,policy --only-severity critical,high --ignore-unchanged + fi + - docker push "$CI_REGISTRY_IMAGE${tag}" + +definitions: + services: + docker: + memory: 2048 # Optional: Increase if needed +``` + +This example assumes two secrets to be available to authenticate against Docker Hub, called `DOCKER_HUB_USER` and `DOCKER_HUB_PAT`, also is necessary more two secrets called `CI_REGISTRY`, `CI_REGISTRY_IMAGE` about registry info. + +## License + +The Docker Scout CLI is licensed under the Terms and Conditions of the [Docker Subscription Service Agreement](https://www.docker.com/legal/docker-subscription-service-agreement/). +# Test PR + \ No newline at end of file From a80f4c84221a2132cacc8cf988cd100af985d5e9 Mon Sep 17 00:00:00 2001 From: Koreloc23 Date: Sun, 9 Aug 2026 22:03:17 -0600 Subject: [PATCH 8/8] test: trigger PR workflow again