From fe6def4407a9327f4b31a0c33b34bde30d75414c Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Thu, 5 Oct 2023 15:29:47 +0530 Subject: [PATCH 01/24] Add SARIF parser --- .coveragerc | 3 + .deepsource.toml | 10 + .github/workflows/tox.yml | 24 ++ .gitignore | 3 + Dockerfile | 14 + mypy.ini | 3 + sarif-parser/README.md | 34 ++ sarif-parser/requirements-dev.txt | 1 + sarif-parser/requirements.txt | 1 + sarif-parser/setup.cfg | 49 +++ sarif-parser/setup.py | 3 + sarif-parser/src/sarif_parser/__init__.py | 91 ++++ sarif-parser/src/sarif_parser/__main__.py | 5 + sarif-parser/src/sarif_parser/cli.py | 57 +++ sarif-parser/src/sarif_parser/py.typed | 1 + sarif-parser/tests/cli_test.py | 127 ++++++ .../tests/sarif_files/kube-score.sarif | 387 ++++++++++++++++++ .../tests/sarif_files/kube-score.sarif.json | 208 ++++++++++ sarif-parser/tests/sarif_files/mypy.sarif | 56 +++ .../tests/sarif_files/mypy.sarif.json | 38 ++ sarif-parser/tests/sarif_parser_test.py | 44 ++ sarif-parser/tox.ini | 12 + 22 files changed, 1171 insertions(+) create mode 100644 .coveragerc create mode 100644 .github/workflows/tox.yml create mode 100644 Dockerfile create mode 100644 mypy.ini create mode 100644 sarif-parser/README.md create mode 100644 sarif-parser/requirements-dev.txt create mode 100644 sarif-parser/requirements.txt create mode 100644 sarif-parser/setup.cfg create mode 100644 sarif-parser/setup.py create mode 100644 sarif-parser/src/sarif_parser/__init__.py create mode 100644 sarif-parser/src/sarif_parser/__main__.py create mode 100644 sarif-parser/src/sarif_parser/cli.py create mode 100644 sarif-parser/src/sarif_parser/py.typed create mode 100644 sarif-parser/tests/cli_test.py create mode 100644 sarif-parser/tests/sarif_files/kube-score.sarif create mode 100644 sarif-parser/tests/sarif_files/kube-score.sarif.json create mode 100644 sarif-parser/tests/sarif_files/mypy.sarif create mode 100644 sarif-parser/tests/sarif_files/mypy.sarif.json create mode 100644 sarif-parser/tests/sarif_parser_test.py create mode 100644 sarif-parser/tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..e75c247a --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[report] +exclude_lines = + raise AssertionError diff --git a/.deepsource.toml b/.deepsource.toml index 23acd771..acaabf5a 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -7,6 +7,16 @@ name = "python" [analyzers.meta] runtime_version = "3.x.x" + type_checker = "mypy" + +[[analyzers]] +name = "test-coverage" + +[[analyzers]] +name = "secrets" + +[[analyzers]] +name = "docker" [[transformers]] name = "black" diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml new file mode 100644 index 00000000..f6eeb1b7 --- /dev/null +++ b/.github/workflows/tox.yml @@ -0,0 +1,24 @@ +name: Tox + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + name: Python tests + steps: + - name: Setup python + uses: actions/setup-python@v3 + with: + python-version: 3.11 + architecture: x64 + - name: Install dependencies + run: pip install tox + - name: Run tests and type checking + run: | + cd sarif-parser + tox diff --git a/.gitignore b/.gitignore index 675eaee1..e121797a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ __pycache__/ venv* .tox .coverage +**/*.egg-info +**/build +**/dist diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ff456c51 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.11-alpine + +RUN mkdir -p /home/runner /app /artifacts /toolbox \ + && chown -R 1000:3000 /home/runner /app /artifacts /toolbox \ + && chmod -R o-rwx /app /artifacts /toolbox \ + && adduser -D -u 1000 runner + +RUN apk add git grep + +ADD ./sarif-parser /toolbox/sarif-parser + +RUN pip install /toolbox/sarif-parser + +USER runner diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..a4baf064 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +strict = True +exclude = setup.py|venv*|build|dist diff --git a/sarif-parser/README.md b/sarif-parser/README.md new file mode 100644 index 00000000..302ef51c --- /dev/null +++ b/sarif-parser/README.md @@ -0,0 +1,34 @@ +# sarif-parser + +Parse SARIF reports and covert them to DeepSource issues. + +## Usage + +```bash +git clone https://github.com/DeepSourceCorp/community-analyzers +cd community-analyzers/sarif-parser +# to install the package +pip install . +# to convert a single sarif file to DeepSource JSON +sarif-parser path/to/sarif-file.json --output /dev/stdout +# to convert a folder containing ONLY sarif files, to DeepSource JSON. +# output defaults to /analysis_results.json +sarif-parser path/to/folder +``` + +## Local Development / Testing + +- Create and activate a virtual environment +- Run `pip install -r requirements-dev.txt` to do an editable install +- Run `pytest` to run tests, and ensure that the coverage report has no missing + lines. + +### The test suite + +The test suite expects you to create two files in `tests/sarif_files`, a SARIF +input file with `.sarif` extension, and the expected DeepSource output file +with the same name, but `.sarif.json` extension. + +## Type Checking + +Run `mypy .` diff --git a/sarif-parser/requirements-dev.txt b/sarif-parser/requirements-dev.txt new file mode 100644 index 00000000..b2f91bdb --- /dev/null +++ b/sarif-parser/requirements-dev.txt @@ -0,0 +1 @@ +-e .[dev] \ No newline at end of file diff --git a/sarif-parser/requirements.txt b/sarif-parser/requirements.txt new file mode 100644 index 00000000..945c9b46 --- /dev/null +++ b/sarif-parser/requirements.txt @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/sarif-parser/setup.cfg b/sarif-parser/setup.cfg new file mode 100644 index 00000000..fdc1bbef --- /dev/null +++ b/sarif-parser/setup.cfg @@ -0,0 +1,49 @@ +[metadata] +name = sarif-parser +version = 0.1.0 +description = Parse SARIF reports and covert them to DeepSource issues. +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/DeepSourceCorp/community-analyzers +author = Tushar Sadhwani +author_email = tushar@deepsource.io +license = MIT +license_file = LICENSE +classifiers = + License :: OSI Approved :: MIT License + Operating System :: OS Independent + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 + Programming Language :: Python :: Implementation :: CPython + Typing :: Typed + +[options] +packages = find: + +python_requires = >=3.8 +package_dir = =src + +[options.packages.find] +where = ./src + +[options.entry_points] +console_scripts = + sarif-parser=sarif_parser.cli:cli + +[options.extras_require] +dev = + black + mypy + pytest + pytest-cov + +[options.package_data] +sarif-parser = + py.typed + +[tool:pytest] +addopts = --cov --cov-report=term-missing diff --git a/sarif-parser/setup.py b/sarif-parser/setup.py new file mode 100644 index 00000000..60684932 --- /dev/null +++ b/sarif-parser/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() diff --git a/sarif-parser/src/sarif_parser/__init__.py b/sarif-parser/src/sarif_parser/__init__.py new file mode 100644 index 00000000..a5796c2b --- /dev/null +++ b/sarif-parser/src/sarif_parser/__init__.py @@ -0,0 +1,91 @@ +"""sarif-parser - Parse SARIF reports and covert them to DeepSource issues.""" +from __future__ import annotations + +import json +from typing import Any, TypedDict + + +class Issue(TypedDict): + issue_code: str + issue_text: str + location: IssueLocation + + +class IssueLocation(TypedDict): + path: str + position: IssuePosition + + +class IssuePosition(TypedDict): + begin: LineColumn + end: LineColumn + + +class LineColumn(TypedDict): + line: int + column: int + + +def parse( + sarif_data: dict[str, Any], + work_dir: str = "", + issue_map_path: str | None = None, +) -> list[Issue]: + """Parses a SARIF file and returns a list of DeepSource issues.""" + deepsource_issues: list[Issue] = [] + + if issue_map_path is not None: + with open(issue_map_path) as file: + issue_map = json.load(file) + else: + issue_map = {} + + for run in sarif_data["runs"]: + for issue in run["results"]: + assert len(issue["locations"]) == 1 + location = issue["locations"][0]["physicalLocation"] + issue_path = location["artifactLocation"]["uri"] + # remove file:// prefix if present + issue_path = issue_path.removeprefix("file://") + # remove work_dir prefix, if present + issue_path = issue_path.removeprefix(work_dir) + # remove leading "/" if any + issue_path = issue_path.removeprefix("/") + + start_line = location.get("contextRegion", {}).get( + "startLine" + ) or location.get("region", {}).get("startLine") + start_column = ( + location.get("contextRegion", {}).get("startColumn") + or location.get("region", {}).get("startColumn") + or 1 # columns are 1 indexed by default + ) + end_line = ( + location.get("contextRegion", {}).get("endLine") + or location.get("region", {}).get("endLine") + or start_line + ) + end_column = ( + location.get("contextRegion", {}).get("endColumn") + or location.get("region", {}).get("endColumn") + or start_column + ) + + issue_code = issue["ruleId"] + if issue_code in issue_map: + issue_code = issue_map[issue_code]["issue_code"] + + deepsource_issue = Issue( + issue_code=issue_code, + issue_text=issue["message"]["text"], + location=IssueLocation( + path=issue_path, + position=IssuePosition( + begin=LineColumn(line=start_line, column=start_column), + end=LineColumn(line=end_line, column=end_column), + ), + ), + ) + deepsource_issues.append(deepsource_issue) + + return DeepSourceIssues(issues=deepsource_issues) diff --git a/sarif-parser/src/sarif_parser/__main__.py b/sarif-parser/src/sarif_parser/__main__.py new file mode 100644 index 00000000..05656368 --- /dev/null +++ b/sarif-parser/src/sarif_parser/__main__.py @@ -0,0 +1,5 @@ +"""Support executing the CLI by doing `python -m sarif_parser`.""" +from sarif_parser.cli import cli + +if __name__ == "__main__": + cli() diff --git a/sarif-parser/src/sarif_parser/cli.py b/sarif-parser/src/sarif_parser/cli.py new file mode 100644 index 00000000..5043cf31 --- /dev/null +++ b/sarif-parser/src/sarif_parser/cli.py @@ -0,0 +1,57 @@ +"""CLI interface for sarif_parser.""" +import argparse +import json +import os.path + +import sarif_parser + +TOOLBOX_PATH = os.getenv("TOOLBOX_PATH", "/toolbox") +OUTPUT_PATH = os.path.join(TOOLBOX_PATH, "analysis_results.json") + + +class SarifParserArgs: + filepath: str + output: str + issue_map_path: str + + +def cli(argv: list[str] | None = None) -> None: + """CLI interface.""" + parser = argparse.ArgumentParser("sarif_parser") + parser.add_argument("filepath", help="Path to artifact containing SARIF data") + parser.add_argument("--issue-map-path", help="JSON file for replacing issue codes.") + parser.add_argument("--output", help="Path for writing analysis results.") + args = parser.parse_args(argv, namespace=SarifParserArgs) + + filepath = args.filepath + if not os.path.exists(filepath): + raise RuntimeError(f"{filepath} does not exist.") + + target_path = args.output + if target_path is None: + target_path = OUTPUT_PATH + + if os.path.isdir(filepath): + artifacts = [os.path.join(filepath, file) for file in os.listdir(filepath)] + else: + artifacts = [filepath] + + deepsource_issues = [] + for filepath in artifacts: + with open(filepath) as file: # skipcq: PTC-W6004 -- nothing sensitive here + artifact = json.load(file) + + sarif_data = json.loads(artifact["data"]) + work_dir = artifact["metadata"]["work_dir"] + issues = sarif_parser.parse(sarif_data, work_dir, args.issue_map_path) + deepsource_issues.extend(issues) + + issues_dict = { + "issues": deepsource_issues, + "metrics": [], + "errors": [], + "is_passed": True if not deepsource_issues else False, + "extra_data": {}, + } + with open(target_path, "w") as file: + json.dump(issues_dict, file) diff --git a/sarif-parser/src/sarif_parser/py.typed b/sarif-parser/src/sarif_parser/py.typed new file mode 100644 index 00000000..1c97f214 --- /dev/null +++ b/sarif-parser/src/sarif_parser/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. This package is fully type annotated. diff --git a/sarif-parser/tests/cli_test.py b/sarif-parser/tests/cli_test.py new file mode 100644 index 00000000..02a6a57c --- /dev/null +++ b/sarif-parser/tests/cli_test.py @@ -0,0 +1,127 @@ +import json +from pathlib import Path + +import pytest + +from sarif_parser.cli import cli + +sarif_json = """ +{ + "runs": [ + { + "results": [ + { + "message": { + "text": "Container has no configured security context" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "/workspace/git/myproject/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-security-context-user-group-id" + }, + { + "message": { + "text": "CPU limit is not set" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "/workspace/git/myproject/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-resources" + } + ] + } + ], + "version": "2.1.0", + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" +} +""" +expected_issues = [ + { + "issue_code": "KUBESCORE-W1001", + "issue_text": "Container has no configured security context", + "location": { + "path": "workspace/git/myproject/asd.yaml", + "position": { + "begin": {"line": 1, "column": 1}, + "end": {"line": 1, "column": 1}, + }, + }, + }, + { + "issue_code": "container-resources", + "issue_text": "CPU limit is not set", + "location": { + "path": "workspace/git/myproject/asd.yaml", + "position": { + "begin": {"line": 1, "column": 1}, + "end": {"line": 1, "column": 1}, + }, + }, + }, +] + +WORK_DIR = "/workspace/git/myproject" + + +@pytest.fixture +def artifact_path(tmp_path: Path) -> str: + artifact_data = {"data": sarif_json, "metadata": {"work_dir": "/"}} + + file_path = tmp_path / "artifact" + with file_path.open("w") as file: + json.dump(artifact_data, file) + + return file_path.as_posix() + + +def test_cli(artifact_path: str, capfd: pytest.CaptureFixture[str]) -> None: + cli([artifact_path, "--output=/dev/stdout"]) + out, err = capfd.readouterr() + assert err == "" + + expected_output = json.dumps( + { + "issues": expected_issues, + "metrics": [], + "errors": [], + "is_passed": False, + "extra_data": {}, + } + ) + assert out == expected_output diff --git a/sarif-parser/tests/sarif_files/kube-score.sarif b/sarif-parser/tests/sarif_files/kube-score.sarif new file mode 100644 index 00000000..fb731c7c --- /dev/null +++ b/sarif-parser/tests/sarif_files/kube-score.sarif @@ -0,0 +1,387 @@ +{ + "runs": [ + { + "tool": { + "driver": { + "name": "kube-score", + "rules": [ + { + "id": "container-security-context-user-group-id", + "name": "Container Security Context User Group ID" + }, + { + "id": "container-resources", + "name": "Container Resources" + }, + { + "id": "pod-networkpolicy", + "name": "Pod NetworkPolicy" + }, + { + "id": "container-image-pull-policy", + "name": "Container Image Pull Policy" + }, + { + "id": "container-security-context-readonlyrootfilesystem", + "name": "Container Security Context ReadOnlyRootFilesystem" + }, + { + "id": "container-ephemeral-storage-request-and-limit", + "name": "Container Ephemeral Storage Request and Limit" + }, + { + "id": "deployment-pod-selector-labels-match-template-metadata-labels", + "name": "Deployment Pod Selector labels match template metadata labels" + }, + { + "id": "deployment-has-poddisruptionbudget", + "name": "Deployment has PodDisruptionBudget" + }, + { + "id": "deployment-has-host-podantiaffinity", + "name": "Deployment has host PodAntiAffinity" + } + ] + } + }, + "conversion": { + "tool": { + "driver": {} + }, + "invocation": { + "endTimeUtc": "0001-01-01T00:00:00Z", + "workingDirectory": {} + } + }, + "properties": {}, + "results": [ + { + "message": { + "text": "Container has no configured security context" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-security-context-user-group-id" + }, + { + "message": { + "text": "CPU limit is not set" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-resources" + }, + { + "message": { + "text": "Memory limit is not set" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-resources" + }, + { + "message": { + "text": "CPU request is not set" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-resources" + }, + { + "message": { + "text": "Memory request is not set" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-resources" + }, + { + "message": { + "text": "The pod does not have a matching NetworkPolicy" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "pod-networkpolicy" + }, + { + "message": { + "text": "ImagePullPolicy is not set to Always" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-image-pull-policy" + }, + { + "message": { + "text": "Container has no configured security context" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-security-context-readonlyrootfilesystem" + }, + { + "message": { + "text": "Ephemeral Storage limit is not set" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "container-ephemeral-storage-request-and-limit" + }, + { + "message": { + "text": "Deployment selector labels not matching template metadata labels" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "deployment-pod-selector-labels-match-template-metadata-labels" + }, + { + "message": { + "text": "No matching PodDisruptionBudget was found" + }, + "level": "error", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "deployment-has-poddisruptionbudget" + }, + { + "message": { + "text": "Deployment does not have a host podAntiAffinity set" + }, + "level": "warning", + "locations": [ + { + "physicalLocation": { + "region": { + "snippet": {} + }, + "artifactLocation": { + "uri": "file:///artifacts/asd.yaml" + }, + "contextRegion": { + "snippet": {}, + "startLine": 1 + } + } + } + ], + "properties": { + "issue_confidence": "HIGH", + "issue_severity": "HIGH" + }, + "ruleId": "deployment-has-host-podantiaffinity" + } + ] + } + ], + "version": "2.1.0", + "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" +} diff --git a/sarif-parser/tests/sarif_files/kube-score.sarif.json b/sarif-parser/tests/sarif_files/kube-score.sarif.json new file mode 100644 index 00000000..4c6c66fe --- /dev/null +++ b/sarif-parser/tests/sarif_files/kube-score.sarif.json @@ -0,0 +1,208 @@ +{ + "issues": [ + { + "issue_code": "KUBESCORE-W1001", + "issue_text": "Container has no configured security context", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "container-resources", + "issue_text": "CPU limit is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "container-resources", + "issue_text": "Memory limit is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "container-resources", + "issue_text": "CPU request is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "container-resources", + "issue_text": "Memory request is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "pod-networkpolicy", + "issue_text": "The pod does not have a matching NetworkPolicy", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "container-image-pull-policy", + "issue_text": "ImagePullPolicy is not set to Always", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "container-security-context-readonlyrootfilesystem", + "issue_text": "Container has no configured security context", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "container-ephemeral-storage-request-and-limit", + "issue_text": "Ephemeral Storage limit is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "deployment-pod-selector-labels-match-template-metadata-labels", + "issue_text": "Deployment selector labels not matching template metadata labels", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "deployment-has-poddisruptionbudget", + "issue_text": "No matching PodDisruptionBudget was found", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + }, + { + "issue_code": "deployment-has-host-podantiaffinity", + "issue_text": "Deployment does not have a host podAntiAffinity set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 + } + } + } + } + ] +} diff --git a/sarif-parser/tests/sarif_files/mypy.sarif b/sarif-parser/tests/sarif_files/mypy.sarif new file mode 100644 index 00000000..657aaba9 --- /dev/null +++ b/sarif-parser/tests/sarif_files/mypy.sarif @@ -0,0 +1,56 @@ +{ + "version": "2.1.0", + "$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json", + "runs": [ + { + "tool": { + "driver": { + "name": "mypy", + "version": "0.910" + } + }, + "results": [ + { + "ruleId": "incompatible-types-in-assignment", + "level": "error", + "message": { + "text": "Incompatible types in assignment (expression has type \"str\", variable has type \"int\")" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "mytest.py" + }, + "region": { + "startLine": 2, + "startColumn": 4 + } + } + } + ] + }, + { + "ruleId": "name-not-defined", + "level": "error", + "message": { + "text": "Name \"z\" is not defined" + }, + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "uri": "mytest.py" + }, + "region": { + "startLine": 3, + "startColumn": 4 + } + } + } + ] + } + ] + } + ] +} diff --git a/sarif-parser/tests/sarif_files/mypy.sarif.json b/sarif-parser/tests/sarif_files/mypy.sarif.json new file mode 100644 index 00000000..50db2915 --- /dev/null +++ b/sarif-parser/tests/sarif_files/mypy.sarif.json @@ -0,0 +1,38 @@ +{ + "issues": [ + { + "issue_code": "incompatible-types-in-assignment", + "issue_text": "Incompatible types in assignment (expression has type \"str\", variable has type \"int\")", + "location": { + "path": "mytest.py", + "position": { + "begin": { + "line": 2, + "column": 4 + }, + "end": { + "line": 2, + "column": 4 + } + } + } + }, + { + "issue_code": "name-not-defined", + "issue_text": "Name \"z\" is not defined", + "location": { + "path": "mytest.py", + "position": { + "begin": { + "line": 3, + "column": 4 + }, + "end": { + "line": 3, + "column": 4 + } + } + } + } + ] +} diff --git a/sarif-parser/tests/sarif_parser_test.py b/sarif-parser/tests/sarif_parser_test.py new file mode 100644 index 00000000..d266c94e --- /dev/null +++ b/sarif-parser/tests/sarif_parser_test.py @@ -0,0 +1,44 @@ +import json +import os.path + +import pytest + +import sarif_parser + + +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: + """Generates all sarif_parser tests based on files in the sarif_files folder.""" + test_files_path = os.path.join(os.path.dirname(__file__), "sarif_files") + + test_params, test_names = [], [] + for file_name in os.listdir(test_files_path): + if not file_name.endswith(".sarif"): + continue + + deepsource_file_name = file_name + ".json" + deepsource_file_path = os.path.join(test_files_path, deepsource_file_name) + if not os.path.isfile(deepsource_file_path): + raise AssertionError( + f"Output file {deepsource_file_name!r} for {file_name!r} doesn't exist" + ) + + sarif_file_path = os.path.join(test_files_path, file_name) + test_params.append((sarif_file_path, deepsource_file_path)) + test_names.append(file_name) + + metafunc.parametrize( + ("sarif_file_path", "deepsource_file_path"), + test_params, + ids=test_names, + ) + + +def test_sarif_parser(sarif_file_path: str, deepsource_file_path: str) -> None: + """Tests parsing of various SARIF outputs into DeepSource issues.""" + with open(deepsource_file_path) as file: + expected_output = json.load(file) + + with open(sarif_file_path) as sarif_file: + sarif_data = json.load(sarif_file) + + assert sarif_parser.parse(sarif_data) == expected_output diff --git a/sarif-parser/tox.ini b/sarif-parser/tox.ini new file mode 100644 index 00000000..4a372106 --- /dev/null +++ b/sarif-parser/tox.ini @@ -0,0 +1,12 @@ +[tox] +envlist = py311,py311-mypy + + +[testenv] +deps = -rrequirements-dev.txt +commands = pytest + +[testenv:py311-mypy] +description = Type check with mypy +commands = + mypy . From 505b62bac64e4a7799b03a2dcce27dab2cda45e4 Mon Sep 17 00:00:00 2001 From: "enterprise-deepsource-icu[bot]" <140088518+enterprise-deepsource-icu[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:00:04 +0000 Subject: [PATCH 02/24] style: format code with isort and Black This commit fixes the style issues introduced in fe6def4 according to the output from isort and Black. Details: https://github.com/DeepSourceCorp/community-analyzers/pull/3 --- sarif-parser/tests/cli_test.py | 1 - sarif-parser/tests/sarif_parser_test.py | 1 - 2 files changed, 2 deletions(-) diff --git a/sarif-parser/tests/cli_test.py b/sarif-parser/tests/cli_test.py index 02a6a57c..9c503fdd 100644 --- a/sarif-parser/tests/cli_test.py +++ b/sarif-parser/tests/cli_test.py @@ -2,7 +2,6 @@ from pathlib import Path import pytest - from sarif_parser.cli import cli sarif_json = """ diff --git a/sarif-parser/tests/sarif_parser_test.py b/sarif-parser/tests/sarif_parser_test.py index d266c94e..af5f3fd0 100644 --- a/sarif-parser/tests/sarif_parser_test.py +++ b/sarif-parser/tests/sarif_parser_test.py @@ -2,7 +2,6 @@ import os.path import pytest - import sarif_parser From 5212304caa9a8f438a6b1fac0a1b25c54d715f9a Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:00:07 +0000 Subject: [PATCH 03/24] style: format code with Black and isort This commit fixes the style issues introduced in fe6def4 according to the output from Black and isort. Details: https://github.com/DeepSourceCorp/community-analyzers/pull/3 From 5d9f071793ea894904350c720480a8f9cda6c7cf Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Thu, 5 Oct 2023 16:05:11 +0530 Subject: [PATCH 04/24] bugfix --- sarif-parser/src/sarif_parser/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sarif-parser/src/sarif_parser/__init__.py b/sarif-parser/src/sarif_parser/__init__.py index a5796c2b..27226ed9 100644 --- a/sarif-parser/src/sarif_parser/__init__.py +++ b/sarif-parser/src/sarif_parser/__init__.py @@ -88,4 +88,4 @@ def parse( ) deepsource_issues.append(deepsource_issue) - return DeepSourceIssues(issues=deepsource_issues) + return deepsource_issues From 3b81fbe5d6cc10addd679a74ef918805063b645e Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Thu, 5 Oct 2023 16:10:20 +0530 Subject: [PATCH 05/24] add checkout action --- .github/workflows/tox.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index f6eeb1b7..f07c81d7 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -11,6 +11,10 @@ jobs: runs-on: ubuntu-latest name: Python tests steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 1 - name: Setup python uses: actions/setup-python@v3 with: From 23b8e9bb72e96d96188cb4f7b6a3896195ddc286 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Fri, 6 Oct 2023 18:04:08 +0530 Subject: [PATCH 06/24] Add run_community_analyzer.py and tests --- run_community_analyzer.py | 37 ++ sarif-parser/src/sarif_parser/__init__.py | 51 ++- sarif-parser/src/sarif_parser/cli.py | 41 +- sarif-parser/tests/cli_test.py | 21 +- .../tests/sarif_files/kube-score.sarif.json | 364 +++++++++--------- .../tests/sarif_files/mypy.sarif.json | 64 ++- tests/community_analyzer_test.py | 29 ++ tests/test_artifacts/kubelinter_1 | 6 + 8 files changed, 349 insertions(+), 264 deletions(-) create mode 100644 run_community_analyzer.py create mode 100644 tests/community_analyzer_test.py create mode 100644 tests/test_artifacts/kubelinter_1 diff --git a/run_community_analyzer.py b/run_community_analyzer.py new file mode 100644 index 00000000..d27734fb --- /dev/null +++ b/run_community_analyzer.py @@ -0,0 +1,37 @@ +import argparse +import os +import os.path + +from sarif_parser import run_sarif_parser + + +class CommunityAnalyzerArgs: + analyzer: str + + +def get_issue_map(analyzer_name: str) -> str: + """Returns the appropriate issue map filepath for the given analyzer.""" + analyzers_dir = os.path.join(os.path.dirname(__file__), "analyzers") + return os.path.join(analyzers_dir, analyzer_name, "utils", "issue_map.json") + + +def main(argv: list[str] | None = None) -> None: + toolbox_path = os.getenv("TOOLBOX_PATH", "/toolbox") + output_path = os.path.join(toolbox_path, "analysis_results.json") + artifacts_path = os.getenv("ARTIFACTS_PATH", "/artifacts") + + parser = argparse.ArgumentParser("sarif_parser") + parser.add_argument( + "--analyzer", + help="Which community analyzer to run. Example: 'kube-linter'", + required=True, + ) + args = parser.parse_args(argv, namespace=CommunityAnalyzerArgs) + + analyzer_name = args.analyzer + issue_map_path = get_issue_map(analyzer_name) + run_sarif_parser(artifacts_path, output_path, issue_map_path) + + +if __name__ == "__main__": + main() diff --git a/sarif-parser/src/sarif_parser/__init__.py b/sarif-parser/src/sarif_parser/__init__.py index 27226ed9..8a16df38 100644 --- a/sarif-parser/src/sarif_parser/__init__.py +++ b/sarif-parser/src/sarif_parser/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations import json +import os.path from typing import Any, TypedDict @@ -29,17 +30,13 @@ class LineColumn(TypedDict): def parse( sarif_data: dict[str, Any], work_dir: str = "", - issue_map_path: str | None = None, + issue_map: dict[str, Any] | None = None, ) -> list[Issue]: """Parses a SARIF file and returns a list of DeepSource issues.""" - deepsource_issues: list[Issue] = [] - - if issue_map_path is not None: - with open(issue_map_path) as file: - issue_map = json.load(file) - else: + if issue_map is None: issue_map = {} + deepsource_issues: list[Issue] = [] for run in sarif_data["runs"]: for issue in run["results"]: assert len(issue["locations"]) == 1 @@ -89,3 +86,43 @@ def parse( deepsource_issues.append(deepsource_issue) return deepsource_issues + + +def run_sarif_parser(filepath: str, output_path: str, issue_map_path: str) -> None: + """Parse SARIF files from given filepath, and save JSON output in output path.""" + # Get list of sarif files + if not os.path.exists(filepath): + raise RuntimeError(f"{filepath} does not exist.") + + if os.path.isdir(filepath): + artifacts = [os.path.join(filepath, file) for file in os.listdir(filepath)] + else: + artifacts = [filepath] + + # Prepare mapping from SARIF rule IDs to DeepSource issue codes + if issue_map_path is not None: + with open(issue_map_path) as file: + issue_map = json.load(file) + else: + issue_map = None + + # Run parser + deepsource_issues = [] + for artifact in artifacts: + with open(artifact) as file: # skipcq: PTC-W6004 -- nothing sensitive here + artifact = json.load(file) + + sarif_data = json.loads(artifact["data"]) + work_dir = artifact["metadata"]["work_dir"] + issues = parse(sarif_data, work_dir, issue_map) + deepsource_issues.extend(issues) + + issues_dict = { + "issues": deepsource_issues, + "metrics": [], + "errors": [], + "is_passed": True if not deepsource_issues else False, + "extra_data": {}, + } + with open(output_path, "w") as file: + json.dump(issues_dict, file) diff --git a/sarif-parser/src/sarif_parser/cli.py b/sarif-parser/src/sarif_parser/cli.py index 5043cf31..876c0b7b 100644 --- a/sarif-parser/src/sarif_parser/cli.py +++ b/sarif-parser/src/sarif_parser/cli.py @@ -1,12 +1,7 @@ """CLI interface for sarif_parser.""" import argparse -import json -import os.path -import sarif_parser - -TOOLBOX_PATH = os.getenv("TOOLBOX_PATH", "/toolbox") -OUTPUT_PATH = os.path.join(TOOLBOX_PATH, "analysis_results.json") +from sarif_parser import run_sarif_parser class SarifParserArgs: @@ -22,36 +17,4 @@ def cli(argv: list[str] | None = None) -> None: parser.add_argument("--issue-map-path", help="JSON file for replacing issue codes.") parser.add_argument("--output", help="Path for writing analysis results.") args = parser.parse_args(argv, namespace=SarifParserArgs) - - filepath = args.filepath - if not os.path.exists(filepath): - raise RuntimeError(f"{filepath} does not exist.") - - target_path = args.output - if target_path is None: - target_path = OUTPUT_PATH - - if os.path.isdir(filepath): - artifacts = [os.path.join(filepath, file) for file in os.listdir(filepath)] - else: - artifacts = [filepath] - - deepsource_issues = [] - for filepath in artifacts: - with open(filepath) as file: # skipcq: PTC-W6004 -- nothing sensitive here - artifact = json.load(file) - - sarif_data = json.loads(artifact["data"]) - work_dir = artifact["metadata"]["work_dir"] - issues = sarif_parser.parse(sarif_data, work_dir, args.issue_map_path) - deepsource_issues.extend(issues) - - issues_dict = { - "issues": deepsource_issues, - "metrics": [], - "errors": [], - "is_passed": True if not deepsource_issues else False, - "extra_data": {}, - } - with open(target_path, "w") as file: - json.dump(issues_dict, file) + run_sarif_parser(args.filepath, args.output, args.issue_map_path) diff --git a/sarif-parser/tests/cli_test.py b/sarif-parser/tests/cli_test.py index 9c503fdd..f8555528 100644 --- a/sarif-parser/tests/cli_test.py +++ b/sarif-parser/tests/cli_test.py @@ -109,8 +109,25 @@ def artifact_path(tmp_path: Path) -> str: return file_path.as_posix() -def test_cli(artifact_path: str, capfd: pytest.CaptureFixture[str]) -> None: - cli([artifact_path, "--output=/dev/stdout"]) +@pytest.fixture +def issue_map_path(tmp_path: Path) -> str: + issue_map = { + "container-security-context-user-group-id": {"issue_code": "KUBESCORE-W1001"} + } + + file_path = tmp_path / "issue_map.json" + with file_path.open("w") as file: + json.dump(issue_map, file) + + return file_path.as_posix() + + +def test_cli( + artifact_path: str, + issue_map_path: str, + capfd: pytest.CaptureFixture[str], +) -> None: + cli([artifact_path, f"--issue-map-path={issue_map_path}", "--output=/dev/stdout"]) out, err = capfd.readouterr() assert err == "" diff --git a/sarif-parser/tests/sarif_files/kube-score.sarif.json b/sarif-parser/tests/sarif_files/kube-score.sarif.json index 4c6c66fe..90cb6961 100644 --- a/sarif-parser/tests/sarif_files/kube-score.sarif.json +++ b/sarif-parser/tests/sarif_files/kube-score.sarif.json @@ -1,208 +1,206 @@ -{ - "issues": [ - { - "issue_code": "KUBESCORE-W1001", - "issue_text": "Container has no configured security context", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } +[ + { + "issue_code": "container-security-context-user-group-id", + "issue_text": "Container has no configured security context", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "container-resources", - "issue_text": "CPU limit is not set", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "container-resources", + "issue_text": "CPU limit is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "container-resources", - "issue_text": "Memory limit is not set", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "container-resources", + "issue_text": "Memory limit is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "container-resources", - "issue_text": "CPU request is not set", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "container-resources", + "issue_text": "CPU request is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "container-resources", - "issue_text": "Memory request is not set", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "container-resources", + "issue_text": "Memory request is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "pod-networkpolicy", - "issue_text": "The pod does not have a matching NetworkPolicy", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "pod-networkpolicy", + "issue_text": "The pod does not have a matching NetworkPolicy", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "container-image-pull-policy", - "issue_text": "ImagePullPolicy is not set to Always", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "container-image-pull-policy", + "issue_text": "ImagePullPolicy is not set to Always", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "container-security-context-readonlyrootfilesystem", - "issue_text": "Container has no configured security context", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "container-security-context-readonlyrootfilesystem", + "issue_text": "Container has no configured security context", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "container-ephemeral-storage-request-and-limit", - "issue_text": "Ephemeral Storage limit is not set", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "container-ephemeral-storage-request-and-limit", + "issue_text": "Ephemeral Storage limit is not set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "deployment-pod-selector-labels-match-template-metadata-labels", - "issue_text": "Deployment selector labels not matching template metadata labels", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "deployment-pod-selector-labels-match-template-metadata-labels", + "issue_text": "Deployment selector labels not matching template metadata labels", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "deployment-has-poddisruptionbudget", - "issue_text": "No matching PodDisruptionBudget was found", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "deployment-has-poddisruptionbudget", + "issue_text": "No matching PodDisruptionBudget was found", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } - }, - { - "issue_code": "deployment-has-host-podantiaffinity", - "issue_text": "Deployment does not have a host podAntiAffinity set", - "location": { - "path": "artifacts/asd.yaml", - "position": { - "begin": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 1 - } + } + }, + { + "issue_code": "deployment-has-host-podantiaffinity", + "issue_text": "Deployment does not have a host podAntiAffinity set", + "location": { + "path": "artifacts/asd.yaml", + "position": { + "begin": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 1 } } } - ] -} + } +] diff --git a/sarif-parser/tests/sarif_files/mypy.sarif.json b/sarif-parser/tests/sarif_files/mypy.sarif.json index 50db2915..f3725fea 100644 --- a/sarif-parser/tests/sarif_files/mypy.sarif.json +++ b/sarif-parser/tests/sarif_files/mypy.sarif.json @@ -1,38 +1,36 @@ -{ - "issues": [ - { - "issue_code": "incompatible-types-in-assignment", - "issue_text": "Incompatible types in assignment (expression has type \"str\", variable has type \"int\")", - "location": { - "path": "mytest.py", - "position": { - "begin": { - "line": 2, - "column": 4 - }, - "end": { - "line": 2, - "column": 4 - } +[ + { + "issue_code": "incompatible-types-in-assignment", + "issue_text": "Incompatible types in assignment (expression has type \"str\", variable has type \"int\")", + "location": { + "path": "mytest.py", + "position": { + "begin": { + "line": 2, + "column": 4 + }, + "end": { + "line": 2, + "column": 4 } } - }, - { - "issue_code": "name-not-defined", - "issue_text": "Name \"z\" is not defined", - "location": { - "path": "mytest.py", - "position": { - "begin": { - "line": 3, - "column": 4 - }, - "end": { - "line": 3, - "column": 4 - } + } + }, + { + "issue_code": "name-not-defined", + "issue_text": "Name \"z\" is not defined", + "location": { + "path": "mytest.py", + "position": { + "begin": { + "line": 3, + "column": 4 + }, + "end": { + "line": 3, + "column": 4 } } } - ] -} + } +] diff --git a/tests/community_analyzer_test.py b/tests/community_analyzer_test.py new file mode 100644 index 00000000..14bc7b56 --- /dev/null +++ b/tests/community_analyzer_test.py @@ -0,0 +1,29 @@ +import json +import os +from pathlib import Path + +import run_community_analyzer + + +def test_community_analyzer(tmp_path: Path) -> None: + toolbox_path = tmp_path.as_posix() + # TODO: Add a kubelinter output in the test folder + artifacts_path = os.path.join(os.path.dirname(__file__), "test_artifacts") + + os.environ["TOOLBOX_PATH"] = toolbox_path + os.environ["ARTIFACTS_PATH"] = artifacts_path + run_community_analyzer.main(["--analyzer=kube-linter"]) + + analysis_results = tmp_path / "analysis_results.json" + assert analysis_results.exists() + + with open(analysis_results) as file: + result = json.load(file) + + assert result == { + "issues": [], # TODO + "errors": [], + "metrics": [], + "is_passed": True, + "extra_data": {}, + } diff --git a/tests/test_artifacts/kubelinter_1 b/tests/test_artifacts/kubelinter_1 new file mode 100644 index 00000000..9012761f --- /dev/null +++ b/tests/test_artifacts/kubelinter_1 @@ -0,0 +1,6 @@ +{ + "data": "{\"runs\": []}", + "metadata": { + "work_dir": "/workspaces/test/my_projet" + } +} From 9e9e2d0a8835212f84c9f198084f92af548111c4 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Fri, 6 Oct 2023 18:06:55 +0530 Subject: [PATCH 07/24] Move tox and mypy scripts out --- .github/workflows/tox.yml | 1 - mypy.ini | 2 +- sarif-parser/src/sarif_parser/__init__.py | 4 ++-- sarif-parser/tox.ini => tox.ini | 0 4 files changed, 3 insertions(+), 4 deletions(-) rename sarif-parser/tox.ini => tox.ini (100%) diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index f07c81d7..4cfac62e 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -24,5 +24,4 @@ jobs: run: pip install tox - name: Run tests and type checking run: | - cd sarif-parser tox diff --git a/mypy.ini b/mypy.ini index a4baf064..a471bc63 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,3 +1,3 @@ [mypy] strict = True -exclude = setup.py|venv*|build|dist +exclude = setup.py|utils|venv*|build|dist diff --git a/sarif-parser/src/sarif_parser/__init__.py b/sarif-parser/src/sarif_parser/__init__.py index 8a16df38..d2a1d4a2 100644 --- a/sarif-parser/src/sarif_parser/__init__.py +++ b/sarif-parser/src/sarif_parser/__init__.py @@ -108,8 +108,8 @@ def run_sarif_parser(filepath: str, output_path: str, issue_map_path: str) -> No # Run parser deepsource_issues = [] - for artifact in artifacts: - with open(artifact) as file: # skipcq: PTC-W6004 -- nothing sensitive here + for artifact_path in artifacts: + with open(artifact_path) as file: # skipcq: PTC-W6004 -- nothing sensitive here artifact = json.load(file) sarif_data = json.loads(artifact["data"]) diff --git a/sarif-parser/tox.ini b/tox.ini similarity index 100% rename from sarif-parser/tox.ini rename to tox.ini From 362ed39ab4c1e819757c79d49bd5a4ab70440289 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Fri, 6 Oct 2023 18:19:05 +0530 Subject: [PATCH 08/24] Update README and fix tox --- README.md | 35 +++++++++++++++++++++++++------ requirements-dev.txt | 1 + requirements.txt | 1 + sarif-parser/README.md | 19 +---------------- sarif-parser/requirements-dev.txt | 1 - sarif-parser/requirements.txt | 1 - tox.ini | 1 + 7 files changed, 33 insertions(+), 26 deletions(-) create mode 100644 requirements-dev.txt create mode 100644 requirements.txt delete mode 100644 sarif-parser/requirements-dev.txt delete mode 100644 sarif-parser/requirements.txt diff --git a/README.md b/README.md index addcb91d..328507a1 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,38 @@ Hub of all open-sourced third-party static analyzers supported by DeepSource. ## Supported Analyzers -|Analyzer name|Latest version|Language / Technology| -|:-----------|:------------|:-------------------| -|[facebook/infer](https://github.com/facebook/infer)|v1.1.0|Java, C++, Objective-C| -|[Azure/bicep](https://github.com/Azure/bicep)|v0.20.4|Azure Resource Manager| -|[stackrox/kube-linter](https://github.com/stackrox/kube-linter)|0.6.4|Kubernetes, Helm| +| Analyzer name | Latest version | Language / Technology | +| :-------------------------------------------------------------- | :------------- | :--------------------- | +| [facebook/infer](https://github.com/facebook/infer) | v1.1.0 | Java, C++, Objective-C | +| [Azure/bicep](https://github.com/Azure/bicep) | v0.20.4 | Azure Resource Manager | +| [stackrox/kube-linter](https://github.com/stackrox/kube-linter) | 0.6.4 | Kubernetes, Helm | --- + ## Development Guide -... + +### Running tests + +- Create and activate a virtual environment +- Run `pip install -r requirements-dev.txt` to do an editable install +- Run `pytest` to run tests, and ensure that the coverage report has no missing + lines. + +### The test suite + +There are minimal tests for the `run_community_analyzer.py` wrapper in +`tests/community_analyzer_test.py` that do sanity checks, to ensure that the +issue map is being respected, etc. + +For the SARIF parser itself, the test suite expects you to create two files in +`sarif-parser/tests/sarif_files`, a SARIF input file with `.sarif` extension, +and the expected DeepSource output file with the same name, but `.sarif.json` +extension. + +### Type Checking + +Run `mypy .` ## Maintenance Guide + ... diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..f456cd69 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +-e sarif-parser[dev] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..023bee3b --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +sarif-parser \ No newline at end of file diff --git a/sarif-parser/README.md b/sarif-parser/README.md index 302ef51c..06945112 100644 --- a/sarif-parser/README.md +++ b/sarif-parser/README.md @@ -9,26 +9,9 @@ git clone https://github.com/DeepSourceCorp/community-analyzers cd community-analyzers/sarif-parser # to install the package pip install . -# to convert a single sarif file to DeepSource JSON +# to convert a single sarif file to DeepSource JSON, and output to terminal sarif-parser path/to/sarif-file.json --output /dev/stdout # to convert a folder containing ONLY sarif files, to DeepSource JSON. # output defaults to /analysis_results.json sarif-parser path/to/folder ``` - -## Local Development / Testing - -- Create and activate a virtual environment -- Run `pip install -r requirements-dev.txt` to do an editable install -- Run `pytest` to run tests, and ensure that the coverage report has no missing - lines. - -### The test suite - -The test suite expects you to create two files in `tests/sarif_files`, a SARIF -input file with `.sarif` extension, and the expected DeepSource output file -with the same name, but `.sarif.json` extension. - -## Type Checking - -Run `mypy .` diff --git a/sarif-parser/requirements-dev.txt b/sarif-parser/requirements-dev.txt deleted file mode 100644 index b2f91bdb..00000000 --- a/sarif-parser/requirements-dev.txt +++ /dev/null @@ -1 +0,0 @@ --e .[dev] \ No newline at end of file diff --git a/sarif-parser/requirements.txt b/sarif-parser/requirements.txt deleted file mode 100644 index 945c9b46..00000000 --- a/sarif-parser/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/tox.ini b/tox.ini index 4a372106..0222ebca 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = py311,py311-mypy [testenv] deps = -rrequirements-dev.txt +setenv = PYTHONPATH=. commands = pytest [testenv:py311-mypy] From e0e0f78fb9fa23605a49d0b0422468aaef7b224c Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> Date: Fri, 6 Oct 2023 18:26:05 +0530 Subject: [PATCH 09/24] Update tests glob Signed-off-by: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> --- .deepsource.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.deepsource.toml b/.deepsource.toml index acaabf5a..3e3816c5 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -1,6 +1,6 @@ version = 1 -test_patterns = ["tests/**"] +test_patterns = ["**/tests/**"] [[analyzers]] name = "python" From 50c0b555882cdb87b6a58cf0331d072b50a445df Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Fri, 6 Oct 2023 18:30:35 +0530 Subject: [PATCH 10/24] Fix DeepSource issues --- Dockerfile | 6 +++--- sarif-parser/src/sarif_parser/__init__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index ff456c51..e19e1a61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,10 +5,10 @@ RUN mkdir -p /home/runner /app /artifacts /toolbox \ && chmod -R o-rwx /app /artifacts /toolbox \ && adduser -D -u 1000 runner -RUN apk add git grep +RUN apk add --no-cache git grep -ADD ./sarif-parser /toolbox/sarif-parser +COPY ./sarif-parser /toolbox/sarif-parser -RUN pip install /toolbox/sarif-parser +RUN pip install --no-cache /toolbox/sarif-parser USER runner diff --git a/sarif-parser/src/sarif_parser/__init__.py b/sarif-parser/src/sarif_parser/__init__.py index d2a1d4a2..2d4de73a 100644 --- a/sarif-parser/src/sarif_parser/__init__.py +++ b/sarif-parser/src/sarif_parser/__init__.py @@ -121,7 +121,7 @@ def run_sarif_parser(filepath: str, output_path: str, issue_map_path: str) -> No "issues": deepsource_issues, "metrics": [], "errors": [], - "is_passed": True if not deepsource_issues else False, + "is_passed": len(deepsource_issues) == 0, "extra_data": {}, } with open(output_path, "w") as file: From e0e6f11f4028d4dbf22abd481abea0b775acea52 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Mon, 9 Oct 2023 17:46:36 +0530 Subject: [PATCH 11/24] Add kubelinter test --- tests/community_analyzer_test.py | 170 +++++++++++++++++++++++++++++- tests/test_artifacts/kubelinter_1 | 2 +- 2 files changed, 169 insertions(+), 3 deletions(-) diff --git a/tests/community_analyzer_test.py b/tests/community_analyzer_test.py index 14bc7b56..9e36f8ca 100644 --- a/tests/community_analyzer_test.py +++ b/tests/community_analyzer_test.py @@ -21,9 +21,175 @@ def test_community_analyzer(tmp_path: Path) -> None: result = json.load(file) assert result == { - "issues": [], # TODO + "is_passed": False, + "issues": [ + { + "issue_code": "KUBELIN-W1029", + "issue_text": 'container "kube-scheduler" does not have a read-only root file system\nobject: /test-release-kube-scheduler apps/v1, Kind=Deployment', + "location": { + "path": "charts/kube-scheduler/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1042", + "issue_text": 'container "kube-scheduler" is not set to runAsNonRoot\nobject: /test-release-kube-scheduler apps/v1, Kind=Deployment', + "location": { + "path": "charts/kube-scheduler/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1015", + "issue_text": 'environment variable TASK_IMAGE_PULL_SECRET_NAME in container "runner" found\nobject: /test-release-runner apps/v1, Kind=Deployment', + "location": { + "path": "charts/runner/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1015", + "issue_text": 'environment variable TASK_ARTIFACT_SECRET_NAME in container "runner" found\nobject: /test-release-runner apps/v1, Kind=Deployment', + "location": { + "path": "charts/runner/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1029", + "issue_text": 'container "runner" does not have a read-only root file system\nobject: /test-release-runner apps/v1, Kind=Deployment', + "location": { + "path": "charts/runner/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1042", + "issue_text": 'container "runner" is not set to runAsNonRoot\nobject: /test-release-runner apps/v1, Kind=Deployment', + "location": { + "path": "charts/runner/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1029", + "issue_text": 'container "runner-rqlite" does not have a read-only root file system\nobject: /test-release-runner-rqlite apps/v1, Kind=StatefulSet', + "location": { + "path": "charts/runner/templates/rqlite-statefulset.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1042", + "issue_text": 'container "runner-rqlite" is not set to runAsNonRoot\nobject: /test-release-runner-rqlite apps/v1, Kind=StatefulSet', + "location": { + "path": "charts/runner/templates/rqlite-statefulset.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1022", + "issue_text": 'The container "wget" is using an invalid container image, "busybox". Please use images that are not blocked by the `BlockList` criteria : [".*:(latest)$" "^[^:]*$" "(.*/[^:]+)$"]\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1029", + "issue_text": 'container "wget" does not have a read-only root file system\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1042", + "issue_text": 'container "wget" is not set to runAsNonRoot\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1047", + "issue_text": 'container "wget" has cpu request 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1047", + "issue_text": 'container "wget" has cpu limit 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1048", + "issue_text": 'container "wget" has memory request 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + { + "issue_code": "KUBELIN-W1048", + "issue_text": 'container "wget" has memory limit 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, + }, + }, + }, + ], "errors": [], "metrics": [], - "is_passed": True, "extra_data": {}, } diff --git a/tests/test_artifacts/kubelinter_1 b/tests/test_artifacts/kubelinter_1 index 9012761f..260535c0 100644 --- a/tests/test_artifacts/kubelinter_1 +++ b/tests/test_artifacts/kubelinter_1 @@ -1,5 +1,5 @@ { - "data": "{\"runs\": []}", + "data": "{\"version\": \"2.1.0\", \"$schema\": \"https://json.schemastore.org/sarif-2.1.0-rtm.5.json\", \"runs\": [{\"tool\": {\"driver\": {\"informationUri\": \"https://github.com/stackrox/kube-linter\", \"name\": \"kube-linter\", \"rules\": [{\"id\": \"dangling-service\", \"shortDescription\": {\"text\": \"Indicates when services do not have any associated deployments.\"}, \"fullDescription\": {\"text\": \"Confirm that your service's selector correctly matches the labels on one of your deployments.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=dangling-services\", \"help\": {\"text\": \"Check: dangling-service\\nDescription: Indicates when services do not have any associated deployments.\\nRemediation: Confirm that your service's selector correctly matches the labels on one of your deployments.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=dangling-services\"}}, {\"id\": \"deprecated-service-account-field\", \"shortDescription\": {\"text\": \"Indicates when deployments use the deprecated serviceAccount field.\"}, \"fullDescription\": {\"text\": \"Use the serviceAccountName field instead. If you must specify serviceAccount, ensure values for serviceAccount and serviceAccountName match.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=deprecated-service-account-field\", \"help\": {\"text\": \"Check: deprecated-service-account-field\\nDescription: Indicates when deployments use the deprecated serviceAccount field.\\nRemediation: Use the serviceAccountName field instead. If you must specify serviceAccount, ensure values for serviceAccount and serviceAccountName match.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=deprecated-service-account-field\"}}, {\"id\": \"docker-sock\", \"shortDescription\": {\"text\": \"Alert on deployments with docker.sock mounted in containers. \"}, \"fullDescription\": {\"text\": \"Ensure the Docker socket is not mounted inside any containers by removing the associated Volume and VolumeMount in deployment yaml specification. If the Docker socket is mounted inside a container it could allow processes running within the container to execute Docker commands which would effectively allow for full control of the host.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=host-mounts\", \"help\": {\"text\": \"Check: docker-sock\\nDescription: Alert on deployments with docker.sock mounted in containers. \\nRemediation: Ensure the Docker socket is not mounted inside any containers by removing the associated Volume and VolumeMount in deployment yaml specification. If the Docker socket is mounted inside a container it could allow processes running within the container to execute Docker commands which would effectively allow for full control of the host.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=host-mounts\"}}, {\"id\": \"drop-net-raw-capability\", \"shortDescription\": {\"text\": \"Indicates when containers do not drop NET_RAW capability\"}, \"fullDescription\": {\"text\": \"NET_RAW makes it so that an application within the container is able to craft raw packets, use raw sockets, and bind to any address. Remove this capability in the containers under containers security contexts.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=verify-container-capabilities\", \"help\": {\"text\": \"Check: drop-net-raw-capability\\nDescription: Indicates when containers do not drop NET_RAW capability\\nRemediation: NET_RAW makes it so that an application within the container is able to craft raw packets, use raw sockets, and bind to any address. Remove this capability in the containers under containers security contexts.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=verify-container-capabilities\"}}, {\"id\": \"duplicate-env-var\", \"shortDescription\": {\"text\": \"Check that duplicate named env vars aren't passed to a deployment like.\"}, \"fullDescription\": {\"text\": \"Confirm that your DeploymentLike doesn't have duplicate env vars names.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=duplicate-environment-variables\", \"help\": {\"text\": \"Check: duplicate-env-var\\nDescription: Check that duplicate named env vars aren't passed to a deployment like.\\nRemediation: Confirm that your DeploymentLike doesn't have duplicate env vars names.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=duplicate-environment-variables\"}}, {\"id\": \"env-var-secret\", \"shortDescription\": {\"text\": \"Indicates when objects use a secret in an environment variable.\"}, \"fullDescription\": {\"text\": \"Do not use raw secrets in environment variables. Instead, either mount the secret as a file or use a secretKeyRef. Refer to https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets for details.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=environment-variables\", \"help\": {\"text\": \"Check: env-var-secret\\nDescription: Indicates when objects use a secret in an environment variable.\\nRemediation: Do not use raw secrets in environment variables. Instead, either mount the secret as a file or use a secretKeyRef. Refer to https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets for details.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=environment-variables\"}}, {\"id\": \"host-ipc\", \"shortDescription\": {\"text\": \"Alert on pods/deployment-likes with sharing host's IPC namespace\"}, \"fullDescription\": {\"text\": \"Ensure the host's IPC namespace is not shared.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=host-ipc\", \"help\": {\"text\": \"Check: host-ipc\\nDescription: Alert on pods/deployment-likes with sharing host's IPC namespace\\nRemediation: Ensure the host's IPC namespace is not shared.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=host-ipc\"}}, {\"id\": \"host-network\", \"shortDescription\": {\"text\": \"Alert on pods/deployment-likes with sharing host's network namespace\"}, \"fullDescription\": {\"text\": \"Ensure the host's network namespace is not shared.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=host-network\", \"help\": {\"text\": \"Check: host-network\\nDescription: Alert on pods/deployment-likes with sharing host's network namespace\\nRemediation: Ensure the host's network namespace is not shared.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=host-network\"}}, {\"id\": \"host-pid\", \"shortDescription\": {\"text\": \"Alert on pods/deployment-likes with sharing host's process namespace\"}, \"fullDescription\": {\"text\": \"Ensure the host's process namespace is not shared.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=host-pid\", \"help\": {\"text\": \"Check: host-pid\\nDescription: Alert on pods/deployment-likes with sharing host's process namespace\\nRemediation: Ensure the host's process namespace is not shared.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=host-pid\"}}, {\"id\": \"invalid-target-ports\", \"shortDescription\": {\"text\": \"Indicates when deployments or services are using port names that are violating specifications.\"}, \"fullDescription\": {\"text\": \"Ensure that port naming is in conjunction with the specification. For more information, please look at the Kubernetes Service specification on this page: https://kubernetes.io/docs/reference/_print/#ServiceSpec. And additional information about IANA Service naming can be found on the following page: https://www.rfc-editor.org/rfc/rfc6335.html#section-5.1.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=target-port\", \"help\": {\"text\": \"Check: invalid-target-ports\\nDescription: Indicates when deployments or services are using port names that are violating specifications.\\nRemediation: Ensure that port naming is in conjunction with the specification. For more information, please look at the Kubernetes Service specification on this page: https://kubernetes.io/docs/reference/_print/#ServiceSpec. And additional information about IANA Service naming can be found on the following page: https://www.rfc-editor.org/rfc/rfc6335.html#section-5.1.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=target-port\"}}, {\"id\": \"latest-tag\", \"shortDescription\": {\"text\": \"Indicates when a deployment-like object is running a container with an invalid container image\"}, \"fullDescription\": {\"text\": \"Use a container image with a specific tag other than latest.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=latest-tag\", \"help\": {\"text\": \"Check: latest-tag\\nDescription: Indicates when a deployment-like object is running a container with an invalid container image\\nRemediation: Use a container image with a specific tag other than latest.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=latest-tag\"}}, {\"id\": \"mismatching-selector\", \"shortDescription\": {\"text\": \"Indicates when deployment selectors fail to match the pod template labels.\"}, \"fullDescription\": {\"text\": \"Confirm that your deployment selector correctly matches the labels in its pod template.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=mismatching-selector\", \"help\": {\"text\": \"Check: mismatching-selector\\nDescription: Indicates when deployment selectors fail to match the pod template labels.\\nRemediation: Confirm that your deployment selector correctly matches the labels in its pod template.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=mismatching-selector\"}}, {\"id\": \"no-anti-affinity\", \"shortDescription\": {\"text\": \"Indicates when deployments with multiple replicas fail to specify inter-pod anti-affinity, to ensure that the orchestrator attempts to schedule replicas on different nodes.\"}, \"fullDescription\": {\"text\": \"Specify anti-affinity in your pod specification to ensure that the orchestrator attempts to schedule replicas on different nodes. Using podAntiAffinity, specify a labelSelector that matches pods for the deployment, and set the topologyKey to kubernetes.io/hostname. Refer to https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity for details.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=anti-affinity-not-specified\", \"help\": {\"text\": \"Check: no-anti-affinity\\nDescription: Indicates when deployments with multiple replicas fail to specify inter-pod anti-affinity, to ensure that the orchestrator attempts to schedule replicas on different nodes.\\nRemediation: Specify anti-affinity in your pod specification to ensure that the orchestrator attempts to schedule replicas on different nodes. Using podAntiAffinity, specify a labelSelector that matches pods for the deployment, and set the topologyKey to kubernetes.io/hostname. Refer to https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity for details.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=anti-affinity-not-specified\"}}, {\"id\": \"no-extensions-v1beta\", \"shortDescription\": {\"text\": \"Indicates when objects use deprecated API versions under extensions/v1beta.\"}, \"fullDescription\": {\"text\": \"Migrate using the apps/v1 API versions for the objects. Refer to https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/ for details.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=disallowed-api-objects\", \"help\": {\"text\": \"Check: no-extensions-v1beta\\nDescription: Indicates when objects use deprecated API versions under extensions/v1beta.\\nRemediation: Migrate using the apps/v1 API versions for the objects. Refer to https://kubernetes.io/blog/2019/07/18/api-deprecations-in-1-16/ for details.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=disallowed-api-objects\"}}, {\"id\": \"no-read-only-root-fs\", \"shortDescription\": {\"text\": \"Indicates when containers are running without a read-only root filesystem.\"}, \"fullDescription\": {\"text\": \"Set readOnlyRootFilesystem to true in the container securityContext.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=read-only-root-filesystems\", \"help\": {\"text\": \"Check: no-read-only-root-fs\\nDescription: Indicates when containers are running without a read-only root filesystem.\\nRemediation: Set readOnlyRootFilesystem to true in the container securityContext.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=read-only-root-filesystems\"}}, {\"id\": \"non-existent-service-account\", \"shortDescription\": {\"text\": \"Indicates when pods reference a service account that is not found.\"}, \"fullDescription\": {\"text\": \"Create the missing service account, or refer to an existing service account.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=non-existent-service-account\", \"help\": {\"text\": \"Check: non-existent-service-account\\nDescription: Indicates when pods reference a service account that is not found.\\nRemediation: Create the missing service account, or refer to an existing service account.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=non-existent-service-account\"}}, {\"id\": \"pdb-max-unavailable\", \"shortDescription\": {\"text\": \"Indicates when a PodDisruptionBudget has a maxUnavailable value that will always prevent disruptions of pods created by related deployment-like objects.\"}, \"fullDescription\": {\"text\": \"Change the PodDisruptionBudget to have maxUnavailable set to a value greater than 0. Refer to https://kubernetes.io/docs/tasks/run-application/configure-pdb/ for more information.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=no-pod-disruptions-allowed---maxunavailable\", \"help\": {\"text\": \"Check: pdb-max-unavailable\\nDescription: Indicates when a PodDisruptionBudget has a maxUnavailable value that will always prevent disruptions of pods created by related deployment-like objects.\\nRemediation: Change the PodDisruptionBudget to have maxUnavailable set to a value greater than 0. Refer to https://kubernetes.io/docs/tasks/run-application/configure-pdb/ for more information.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=no-pod-disruptions-allowed---maxunavailable\"}}, {\"id\": \"pdb-min-available\", \"shortDescription\": {\"text\": \"Indicates when a PodDisruptionBudget sets a minAvailable value that will always prevent disruptions of pods created by related deployment-like objects.\"}, \"fullDescription\": {\"text\": \"Change the PodDisruptionBudget to have minAvailable set to a number lower than the number of replicas in the related deployment-like objects. Refer to https://kubernetes.io/docs/tasks/run-application/configure-pdb/ for more information.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=no-pod-disruptions-allowed---minavailable\", \"help\": {\"text\": \"Check: pdb-min-available\\nDescription: Indicates when a PodDisruptionBudget sets a minAvailable value that will always prevent disruptions of pods created by related deployment-like objects.\\nRemediation: Change the PodDisruptionBudget to have minAvailable set to a number lower than the number of replicas in the related deployment-like objects. Refer to https://kubernetes.io/docs/tasks/run-application/configure-pdb/ for more information.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=no-pod-disruptions-allowed---minavailable\"}}, {\"id\": \"privilege-escalation-container\", \"shortDescription\": {\"text\": \"Alert on containers of allowing privilege escalation that could gain more privileges than its parent process.\"}, \"fullDescription\": {\"text\": \"Ensure containers do not allow privilege escalation by setting allowPrivilegeEscalation=false, privileged=false and removing CAP_SYS_ADMIN capability. See https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for more details.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=privilege-escalation-on-containers\", \"help\": {\"text\": \"Check: privilege-escalation-container\\nDescription: Alert on containers of allowing privilege escalation that could gain more privileges than its parent process.\\nRemediation: Ensure containers do not allow privilege escalation by setting allowPrivilegeEscalation=false, privileged=false and removing CAP_SYS_ADMIN capability. See https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for more details.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=privilege-escalation-on-containers\"}}, {\"id\": \"privileged-container\", \"shortDescription\": {\"text\": \"Indicates when deployments have containers running in privileged mode.\"}, \"fullDescription\": {\"text\": \"Do not run your container as privileged unless it is required.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=privileged-containers\", \"help\": {\"text\": \"Check: privileged-container\\nDescription: Indicates when deployments have containers running in privileged mode.\\nRemediation: Do not run your container as privileged unless it is required.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=privileged-containers\"}}, {\"id\": \"run-as-non-root\", \"shortDescription\": {\"text\": \"Indicates when containers are not set to runAsNonRoot.\"}, \"fullDescription\": {\"text\": \"Set runAsUser to a non-zero number and runAsNonRoot to true in your pod or container securityContext. Refer to https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for details.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=run-as-non-root-user\", \"help\": {\"text\": \"Check: run-as-non-root\\nDescription: Indicates when containers are not set to runAsNonRoot.\\nRemediation: Set runAsUser to a non-zero number and runAsNonRoot to true in your pod or container securityContext. Refer to https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for details.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=run-as-non-root-user\"}}, {\"id\": \"sensitive-host-mounts\", \"shortDescription\": {\"text\": \"Alert on deployments with sensitive host system directories mounted in containers\"}, \"fullDescription\": {\"text\": \"Ensure sensitive host system directories are not mounted in containers by removing those Volumes and VolumeMounts.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=host-mounts\", \"help\": {\"text\": \"Check: sensitive-host-mounts\\nDescription: Alert on deployments with sensitive host system directories mounted in containers\\nRemediation: Ensure sensitive host system directories are not mounted in containers by removing those Volumes and VolumeMounts.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=host-mounts\"}}, {\"id\": \"ssh-port\", \"shortDescription\": {\"text\": \"Indicates when deployments expose port 22, which is commonly reserved for SSH access.\"}, \"fullDescription\": {\"text\": \"Ensure that non-SSH services are not using port 22. Confirm that any actual SSH servers have been vetted.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=ports\", \"help\": {\"text\": \"Check: ssh-port\\nDescription: Indicates when deployments expose port 22, which is commonly reserved for SSH access.\\nRemediation: Ensure that non-SSH services are not using port 22. Confirm that any actual SSH servers have been vetted.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=ports\"}}, {\"id\": \"unsafe-sysctls\", \"shortDescription\": {\"text\": \"Alert on deployments specifying unsafe sysctls that may lead to severe problems like wrong behavior of containers\"}, \"fullDescription\": {\"text\": \"Ensure container does not allow unsafe allocation of system resources by removing unsafe sysctls configurations. For more details see https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ https://docs.docker.com/engine/reference/commandline/run/#configure-namespaced-kernel-parameters-sysctls-at-runtime.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=unsafe-sysctls\", \"help\": {\"text\": \"Check: unsafe-sysctls\\nDescription: Alert on deployments specifying unsafe sysctls that may lead to severe problems like wrong behavior of containers\\nRemediation: Ensure container does not allow unsafe allocation of system resources by removing unsafe sysctls configurations. For more details see https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/ https://docs.docker.com/engine/reference/commandline/run/#configure-namespaced-kernel-parameters-sysctls-at-runtime.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=unsafe-sysctls\"}}, {\"id\": \"unset-cpu-requirements\", \"shortDescription\": {\"text\": \"Indicates when containers do not have CPU requests and limits set.\"}, \"fullDescription\": {\"text\": \"Set CPU requests and limits for your container based on its requirements. Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=cpu-requirements\", \"help\": {\"text\": \"Check: unset-cpu-requirements\\nDescription: Indicates when containers do not have CPU requests and limits set.\\nRemediation: Set CPU requests and limits for your container based on its requirements. Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=cpu-requirements\"}}, {\"id\": \"unset-memory-requirements\", \"shortDescription\": {\"text\": \"Indicates when containers do not have memory requests and limits set.\"}, \"fullDescription\": {\"text\": \"Set memory requests and limits for your container based on its requirements. Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details.\"}, \"helpUri\": \"https://docs.kubelinter.io/#/generated/templates?id=memory-requirements\", \"help\": {\"text\": \"Check: unset-memory-requirements\\nDescription: Indicates when containers do not have memory requests and limits set.\\nRemediation: Set memory requests and limits for your container based on its requirements. Refer to https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for details.\\nTemplate: https://docs.kubelinter.io/#/generated/templates?id=memory-requirements\"}}], \"version\": \"v0.6.4-0-g4f0b1e01fd\"}}, \"invocations\": [{\"endTimeUtc\": \"2023-10-09T12:07:15.338088365Z\", \"executionSuccessful\": false, \"workingDirectory\": {\"uri\": \"file:///home/runner/work/helm-charts/helm-charts\"}}], \"results\": [{\"ruleId\": \"no-read-only-root-fs\", \"ruleIndex\": 14, \"message\": {\"text\": \"container \\\"kube-scheduler\\\" does not have a read-only root file system\\nobject: /test-release-kube-scheduler apps/v1, Kind=Deployment\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/kube-scheduler/templates/deployment.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-kube-scheduler\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"apps\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"apps/v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Deployment\", \"fullyQualifiedName\": \"apps/v1, Kind=Deployment\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"run-as-non-root\", \"ruleIndex\": 20, \"message\": {\"text\": \"container \\\"kube-scheduler\\\" is not set to runAsNonRoot\\nobject: /test-release-kube-scheduler apps/v1, Kind=Deployment\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/kube-scheduler/templates/deployment.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-kube-scheduler\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"apps\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"apps/v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Deployment\", \"fullyQualifiedName\": \"apps/v1, Kind=Deployment\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"env-var-secret\", \"ruleIndex\": 5, \"message\": {\"text\": \"environment variable TASK_IMAGE_PULL_SECRET_NAME in container \\\"runner\\\" found\\nobject: /test-release-runner apps/v1, Kind=Deployment\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/deployment.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"apps\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"apps/v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Deployment\", \"fullyQualifiedName\": \"apps/v1, Kind=Deployment\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"env-var-secret\", \"ruleIndex\": 5, \"message\": {\"text\": \"environment variable TASK_ARTIFACT_SECRET_NAME in container \\\"runner\\\" found\\nobject: /test-release-runner apps/v1, Kind=Deployment\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/deployment.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"apps\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"apps/v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Deployment\", \"fullyQualifiedName\": \"apps/v1, Kind=Deployment\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"no-read-only-root-fs\", \"ruleIndex\": 14, \"message\": {\"text\": \"container \\\"runner\\\" does not have a read-only root file system\\nobject: /test-release-runner apps/v1, Kind=Deployment\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/deployment.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"apps\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"apps/v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Deployment\", \"fullyQualifiedName\": \"apps/v1, Kind=Deployment\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"run-as-non-root\", \"ruleIndex\": 20, \"message\": {\"text\": \"container \\\"runner\\\" is not set to runAsNonRoot\\nobject: /test-release-runner apps/v1, Kind=Deployment\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/deployment.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"apps\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"apps/v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Deployment\", \"fullyQualifiedName\": \"apps/v1, Kind=Deployment\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"no-read-only-root-fs\", \"ruleIndex\": 14, \"message\": {\"text\": \"container \\\"runner-rqlite\\\" does not have a read-only root file system\\nobject: /test-release-runner-rqlite apps/v1, Kind=StatefulSet\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/rqlite-statefulset.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner-rqlite\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"apps\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"apps/v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"StatefulSet\", \"fullyQualifiedName\": \"apps/v1, Kind=StatefulSet\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"run-as-non-root\", \"ruleIndex\": 20, \"message\": {\"text\": \"container \\\"runner-rqlite\\\" is not set to runAsNonRoot\\nobject: /test-release-runner-rqlite apps/v1, Kind=StatefulSet\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/rqlite-statefulset.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner-rqlite\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"apps\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"apps/v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"StatefulSet\", \"fullyQualifiedName\": \"apps/v1, Kind=StatefulSet\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"latest-tag\", \"ruleIndex\": 10, \"message\": {\"text\": \"The container \\\"wget\\\" is using an invalid container image, \\\"busybox\\\". Please use images that are not blocked by the `BlockList` criteria : [\\\".*:(latest)$\\\" \\\"^[^:]*$\\\" \\\"(.*/[^:]+)$\\\"]\\nobject: /test-release-runner-test-connection /v1, Kind=Pod\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/tests/test-connection.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner-test-connection\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Pod\", \"fullyQualifiedName\": \"/v1, Kind=Pod\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"no-read-only-root-fs\", \"ruleIndex\": 14, \"message\": {\"text\": \"container \\\"wget\\\" does not have a read-only root file system\\nobject: /test-release-runner-test-connection /v1, Kind=Pod\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/tests/test-connection.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner-test-connection\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Pod\", \"fullyQualifiedName\": \"/v1, Kind=Pod\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"run-as-non-root\", \"ruleIndex\": 20, \"message\": {\"text\": \"container \\\"wget\\\" is not set to runAsNonRoot\\nobject: /test-release-runner-test-connection /v1, Kind=Pod\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/tests/test-connection.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner-test-connection\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Pod\", \"fullyQualifiedName\": \"/v1, Kind=Pod\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"unset-cpu-requirements\", \"ruleIndex\": 24, \"message\": {\"text\": \"container \\\"wget\\\" has cpu request 0\\nobject: /test-release-runner-test-connection /v1, Kind=Pod\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/tests/test-connection.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner-test-connection\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Pod\", \"fullyQualifiedName\": \"/v1, Kind=Pod\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"unset-cpu-requirements\", \"ruleIndex\": 24, \"message\": {\"text\": \"container \\\"wget\\\" has cpu limit 0\\nobject: /test-release-runner-test-connection /v1, Kind=Pod\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/tests/test-connection.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner-test-connection\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Pod\", \"fullyQualifiedName\": \"/v1, Kind=Pod\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"unset-memory-requirements\", \"ruleIndex\": 25, \"message\": {\"text\": \"container \\\"wget\\\" has memory request 0\\nobject: /test-release-runner-test-connection /v1, Kind=Pod\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/tests/test-connection.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner-test-connection\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Pod\", \"fullyQualifiedName\": \"/v1, Kind=Pod\", \"kind\": \"GVK/Kind\"}]}]}, {\"ruleId\": \"unset-memory-requirements\", \"ruleIndex\": 25, \"message\": {\"text\": \"container \\\"wget\\\" has memory limit 0\\nobject: /test-release-runner-test-connection /v1, Kind=Pod\"}, \"locations\": [{\"physicalLocation\": {\"artifactLocation\": {\"uri\": \"charts/runner/templates/tests/test-connection.yaml\"}, \"region\": {\"startLine\": 1}}, \"logicalLocations\": [{\"name\": \"test-release-runner-test-connection\", \"kind\": \"Object Name\"}, {\"name\": \"\", \"kind\": \"Object Namespace\"}, {\"name\": \"\", \"kind\": \"GVK/Group\"}, {\"name\": \"v1\", \"fullyQualifiedName\": \"v1\", \"kind\": \"GVK/Version\"}, {\"name\": \"Pod\", \"fullyQualifiedName\": \"/v1, Kind=Pod\", \"kind\": \"GVK/Kind\"}]}]}]}]}", "metadata": { "work_dir": "/workspaces/test/my_projet" } From 1aa5fcc7306c30927e78383af642aac70475ddd2 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:32:05 +0530 Subject: [PATCH 12/24] Update tox.yml Signed-off-by: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> --- .github/workflows/tox.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 4cfac62e..8d3ee001 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -5,6 +5,8 @@ on: branches: [master] pull_request: branches: [master] +env: + DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }} jobs: build: @@ -15,13 +17,25 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 1 + - name: Setup python uses: actions/setup-python@v3 with: python-version: 3.11 architecture: x64 + - name: Install dependencies run: pip install tox + - name: Run tests and type checking + run: tox + + - name: Set up python3.10 + uses: actions/setup-python@v2 + with: + python-version: '3.10' + + - name: Report test coverage to DeepSource run: | - tox + curl https://deepsource.io/cli | sh + ./bin/deepsource report --analyzer test-coverage --key python --value-file ./coverage.xml From 453ae64259b79c907c652f0f001502fa6685f481 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:32:36 +0530 Subject: [PATCH 13/24] Remove duplicate setup python step Signed-off-by: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> --- .github/workflows/tox.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 8d3ee001..2d8f7a1b 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -30,11 +30,6 @@ jobs: - name: Run tests and type checking run: tox - - name: Set up python3.10 - uses: actions/setup-python@v2 - with: - python-version: '3.10' - - name: Report test coverage to DeepSource run: | curl https://deepsource.io/cli | sh From 2c37bb12fb97555b6caf2485d4e07287e2d259cb Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:45:16 +0530 Subject: [PATCH 14/24] Update tox.ini Signed-off-by: Tushar Sadhwani <86737547+tushar-deepsource@users.noreply.github.com> --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0222ebca..9c418ba0 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = py311,py311-mypy [testenv] deps = -rrequirements-dev.txt setenv = PYTHONPATH=. -commands = pytest +commands = pytest --cov=./ --cov-report=xml [testenv:py311-mypy] description = Type check with mypy From 8458fff3e877ac395b9b14c7f57a00a56c7dc852 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 10 Oct 2023 15:00:05 +0530 Subject: [PATCH 15/24] Add more tests --- sarif-parser/src/sarif_parser/__init__.py | 6 +- sarif-parser/src/sarif_parser/cli.py | 2 +- sarif-parser/tests/cli_test.py | 55 +++- tests/community_analyzer_test.py | 342 ++++++++++++---------- 4 files changed, 239 insertions(+), 166 deletions(-) diff --git a/sarif-parser/src/sarif_parser/__init__.py b/sarif-parser/src/sarif_parser/__init__.py index 2d4de73a..db8eefd9 100644 --- a/sarif-parser/src/sarif_parser/__init__.py +++ b/sarif-parser/src/sarif_parser/__init__.py @@ -88,7 +88,11 @@ def parse( return deepsource_issues -def run_sarif_parser(filepath: str, output_path: str, issue_map_path: str) -> None: +def run_sarif_parser( + filepath: str, + output_path: str, + issue_map_path: str | None, +) -> None: """Parse SARIF files from given filepath, and save JSON output in output path.""" # Get list of sarif files if not os.path.exists(filepath): diff --git a/sarif-parser/src/sarif_parser/cli.py b/sarif-parser/src/sarif_parser/cli.py index 876c0b7b..cff64775 100644 --- a/sarif-parser/src/sarif_parser/cli.py +++ b/sarif-parser/src/sarif_parser/cli.py @@ -7,7 +7,7 @@ class SarifParserArgs: filepath: str output: str - issue_map_path: str + issue_map_path: str | None def cli(argv: list[str] | None = None) -> None: diff --git a/sarif-parser/tests/cli_test.py b/sarif-parser/tests/cli_test.py index f8555528..94025f06 100644 --- a/sarif-parser/tests/cli_test.py +++ b/sarif-parser/tests/cli_test.py @@ -70,7 +70,35 @@ "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" } """ +issue_map = { + "container-security-context-user-group-id": {"issue_code": "KUBESCORE-W1001"} +} + expected_issues = [ + { + "issue_code": "container-security-context-user-group-id", + "issue_text": "Container has no configured security context", + "location": { + "path": "workspace/git/myproject/asd.yaml", + "position": { + "begin": {"line": 1, "column": 1}, + "end": {"line": 1, "column": 1}, + }, + }, + }, + { + "issue_code": "container-resources", + "issue_text": "CPU limit is not set", + "location": { + "path": "workspace/git/myproject/asd.yaml", + "position": { + "begin": {"line": 1, "column": 1}, + "end": {"line": 1, "column": 1}, + }, + }, + }, +] +expected_issues_with_issue_map = [ { "issue_code": "KUBESCORE-W1001", "issue_text": "Container has no configured security context", @@ -111,10 +139,6 @@ def artifact_path(tmp_path: Path) -> str: @pytest.fixture def issue_map_path(tmp_path: Path) -> str: - issue_map = { - "container-security-context-user-group-id": {"issue_code": "KUBESCORE-W1001"} - } - file_path = tmp_path / "issue_map.json" with file_path.open("w") as file: json.dump(issue_map, file) @@ -127,7 +151,7 @@ def test_cli( issue_map_path: str, capfd: pytest.CaptureFixture[str], ) -> None: - cli([artifact_path, f"--issue-map-path={issue_map_path}", "--output=/dev/stdout"]) + cli([artifact_path, "--output=/dev/stdout"]) out, err = capfd.readouterr() assert err == "" @@ -141,3 +165,24 @@ def test_cli( } ) assert out == expected_output + + +def test_cli_with_issue_map( + artifact_path: str, + issue_map_path: str, + capfd: pytest.CaptureFixture[str], +) -> None: + cli([artifact_path, f"--issue-map-path={issue_map_path}", "--output=/dev/stdout"]) + out, err = capfd.readouterr() + assert err == "" + + expected_output = json.dumps( + { + "issues": expected_issues_with_issue_map, + "metrics": [], + "errors": [], + "is_passed": False, + "extra_data": {}, + } + ) + assert out == expected_output diff --git a/tests/community_analyzer_test.py b/tests/community_analyzer_test.py index 9e36f8ca..a40235da 100644 --- a/tests/community_analyzer_test.py +++ b/tests/community_analyzer_test.py @@ -1,195 +1,219 @@ import json import os from pathlib import Path +import subprocess +import sys import run_community_analyzer - -def test_community_analyzer(tmp_path: Path) -> None: - toolbox_path = tmp_path.as_posix() - # TODO: Add a kubelinter output in the test folder - artifacts_path = os.path.join(os.path.dirname(__file__), "test_artifacts") - - os.environ["TOOLBOX_PATH"] = toolbox_path - os.environ["ARTIFACTS_PATH"] = artifacts_path - run_community_analyzer.main(["--analyzer=kube-linter"]) - - analysis_results = tmp_path / "analysis_results.json" - assert analysis_results.exists() - - with open(analysis_results) as file: - result = json.load(file) - - assert result == { - "is_passed": False, - "issues": [ - { - "issue_code": "KUBELIN-W1029", - "issue_text": 'container "kube-scheduler" does not have a read-only root file system\nobject: /test-release-kube-scheduler apps/v1, Kind=Deployment', - "location": { - "path": "charts/kube-scheduler/templates/deployment.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, +expected_result = { + "is_passed": False, + "issues": [ + { + "issue_code": "KUBELIN-W1029", + "issue_text": 'container "kube-scheduler" does not have a read-only root file system\nobject: /test-release-kube-scheduler apps/v1, Kind=Deployment', + "location": { + "path": "charts/kube-scheduler/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1042", - "issue_text": 'container "kube-scheduler" is not set to runAsNonRoot\nobject: /test-release-kube-scheduler apps/v1, Kind=Deployment', - "location": { - "path": "charts/kube-scheduler/templates/deployment.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1042", + "issue_text": 'container "kube-scheduler" is not set to runAsNonRoot\nobject: /test-release-kube-scheduler apps/v1, Kind=Deployment', + "location": { + "path": "charts/kube-scheduler/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1015", - "issue_text": 'environment variable TASK_IMAGE_PULL_SECRET_NAME in container "runner" found\nobject: /test-release-runner apps/v1, Kind=Deployment', - "location": { - "path": "charts/runner/templates/deployment.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1015", + "issue_text": 'environment variable TASK_IMAGE_PULL_SECRET_NAME in container "runner" found\nobject: /test-release-runner apps/v1, Kind=Deployment', + "location": { + "path": "charts/runner/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1015", - "issue_text": 'environment variable TASK_ARTIFACT_SECRET_NAME in container "runner" found\nobject: /test-release-runner apps/v1, Kind=Deployment', - "location": { - "path": "charts/runner/templates/deployment.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1015", + "issue_text": 'environment variable TASK_ARTIFACT_SECRET_NAME in container "runner" found\nobject: /test-release-runner apps/v1, Kind=Deployment', + "location": { + "path": "charts/runner/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1029", - "issue_text": 'container "runner" does not have a read-only root file system\nobject: /test-release-runner apps/v1, Kind=Deployment', - "location": { - "path": "charts/runner/templates/deployment.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1029", + "issue_text": 'container "runner" does not have a read-only root file system\nobject: /test-release-runner apps/v1, Kind=Deployment', + "location": { + "path": "charts/runner/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1042", - "issue_text": 'container "runner" is not set to runAsNonRoot\nobject: /test-release-runner apps/v1, Kind=Deployment', - "location": { - "path": "charts/runner/templates/deployment.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1042", + "issue_text": 'container "runner" is not set to runAsNonRoot\nobject: /test-release-runner apps/v1, Kind=Deployment', + "location": { + "path": "charts/runner/templates/deployment.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1029", - "issue_text": 'container "runner-rqlite" does not have a read-only root file system\nobject: /test-release-runner-rqlite apps/v1, Kind=StatefulSet', - "location": { - "path": "charts/runner/templates/rqlite-statefulset.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1029", + "issue_text": 'container "runner-rqlite" does not have a read-only root file system\nobject: /test-release-runner-rqlite apps/v1, Kind=StatefulSet', + "location": { + "path": "charts/runner/templates/rqlite-statefulset.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1042", - "issue_text": 'container "runner-rqlite" is not set to runAsNonRoot\nobject: /test-release-runner-rqlite apps/v1, Kind=StatefulSet', - "location": { - "path": "charts/runner/templates/rqlite-statefulset.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1042", + "issue_text": 'container "runner-rqlite" is not set to runAsNonRoot\nobject: /test-release-runner-rqlite apps/v1, Kind=StatefulSet', + "location": { + "path": "charts/runner/templates/rqlite-statefulset.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1022", - "issue_text": 'The container "wget" is using an invalid container image, "busybox". Please use images that are not blocked by the `BlockList` criteria : [".*:(latest)$" "^[^:]*$" "(.*/[^:]+)$"]\nobject: /test-release-runner-test-connection /v1, Kind=Pod', - "location": { - "path": "charts/runner/templates/tests/test-connection.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1022", + "issue_text": 'The container "wget" is using an invalid container image, "busybox". Please use images that are not blocked by the `BlockList` criteria : [".*:(latest)$" "^[^:]*$" "(.*/[^:]+)$"]\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1029", - "issue_text": 'container "wget" does not have a read-only root file system\nobject: /test-release-runner-test-connection /v1, Kind=Pod', - "location": { - "path": "charts/runner/templates/tests/test-connection.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1029", + "issue_text": 'container "wget" does not have a read-only root file system\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1042", - "issue_text": 'container "wget" is not set to runAsNonRoot\nobject: /test-release-runner-test-connection /v1, Kind=Pod', - "location": { - "path": "charts/runner/templates/tests/test-connection.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1042", + "issue_text": 'container "wget" is not set to runAsNonRoot\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1047", - "issue_text": 'container "wget" has cpu request 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', - "location": { - "path": "charts/runner/templates/tests/test-connection.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1047", + "issue_text": 'container "wget" has cpu request 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1047", - "issue_text": 'container "wget" has cpu limit 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', - "location": { - "path": "charts/runner/templates/tests/test-connection.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1047", + "issue_text": 'container "wget" has cpu limit 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1048", - "issue_text": 'container "wget" has memory request 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', - "location": { - "path": "charts/runner/templates/tests/test-connection.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1048", + "issue_text": 'container "wget" has memory request 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - { - "issue_code": "KUBELIN-W1048", - "issue_text": 'container "wget" has memory limit 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', - "location": { - "path": "charts/runner/templates/tests/test-connection.yaml", - "position": { - "begin": {"column": 1, "line": 1}, - "end": {"column": 1, "line": 1}, - }, + }, + { + "issue_code": "KUBELIN-W1048", + "issue_text": 'container "wget" has memory limit 0\nobject: /test-release-runner-test-connection /v1, Kind=Pod', + "location": { + "path": "charts/runner/templates/tests/test-connection.yaml", + "position": { + "begin": {"column": 1, "line": 1}, + "end": {"column": 1, "line": 1}, }, }, - ], - "errors": [], - "metrics": [], - "extra_data": {}, - } + }, + ], + "errors": [], + "metrics": [], + "extra_data": {}, +} + + +def test_community_analyzer(tmp_path: Path) -> None: + toolbox_path = tmp_path.as_posix() + artifacts_path = os.path.join(os.path.dirname(__file__), "test_artifacts") + + os.environ["TOOLBOX_PATH"] = toolbox_path + os.environ["ARTIFACTS_PATH"] = artifacts_path + run_community_analyzer.main(["--analyzer=kube-linter"]) + + analysis_results = tmp_path / "analysis_results.json" + assert analysis_results.exists() + + with open(analysis_results) as file: + result = json.load(file) + + assert result == expected_result + + +def test_cli(tmp_path: Path) -> None: + toolbox_path = tmp_path.as_posix() + artifacts_path = os.path.join(os.path.dirname(__file__), "test_artifacts") + + subprocess.check_call( + [sys.executable, "run_community_analyzer.py", "--analyzer=kube-linter"], + env={ + "TOOLBOX_PATH": toolbox_path, + "ARTIFACTS_PATH": artifacts_path, + }, + ) + + analysis_results = tmp_path / "analysis_results.json" + assert analysis_results.exists() + + with open(analysis_results) as file: + result = json.load(file) + + assert result == expected_result From 1520982c0d78dd4ad8a9dd97cccada2c45f60922 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 10 Oct 2023 15:02:45 +0530 Subject: [PATCH 16/24] Docstrings --- run_community_analyzer.py | 1 + sarif-parser/tests/cli_test.py | 10 +++++----- tests/community_analyzer_test.py | 2 ++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/run_community_analyzer.py b/run_community_analyzer.py index d27734fb..09dac70d 100644 --- a/run_community_analyzer.py +++ b/run_community_analyzer.py @@ -16,6 +16,7 @@ def get_issue_map(analyzer_name: str) -> str: def main(argv: list[str] | None = None) -> None: + """Runs the CLI.""" toolbox_path = os.getenv("TOOLBOX_PATH", "/toolbox") output_path = os.path.join(toolbox_path, "analysis_results.json") artifacts_path = os.getenv("ARTIFACTS_PATH", "/artifacts") diff --git a/sarif-parser/tests/cli_test.py b/sarif-parser/tests/cli_test.py index 94025f06..ba373528 100644 --- a/sarif-parser/tests/cli_test.py +++ b/sarif-parser/tests/cli_test.py @@ -128,6 +128,7 @@ @pytest.fixture def artifact_path(tmp_path: Path) -> str: + """Creates the uploaded artifact containing SARIF data.""" artifact_data = {"data": sarif_json, "metadata": {"work_dir": "/"}} file_path = tmp_path / "artifact" @@ -139,6 +140,7 @@ def artifact_path(tmp_path: Path) -> str: @pytest.fixture def issue_map_path(tmp_path: Path) -> str: + """Creates `issue_map.json` file.""" file_path = tmp_path / "issue_map.json" with file_path.open("w") as file: json.dump(issue_map, file) @@ -146,11 +148,8 @@ def issue_map_path(tmp_path: Path) -> str: return file_path.as_posix() -def test_cli( - artifact_path: str, - issue_map_path: str, - capfd: pytest.CaptureFixture[str], -) -> None: +def test_cli(artifact_path: str, capfd: pytest.CaptureFixture[str]) -> None: + """Tests `sarif-parser` CLI.""" cli([artifact_path, "--output=/dev/stdout"]) out, err = capfd.readouterr() assert err == "" @@ -172,6 +171,7 @@ def test_cli_with_issue_map( issue_map_path: str, capfd: pytest.CaptureFixture[str], ) -> None: + """Tests `sarif-parser` CLI, with issue code substitution.""" cli([artifact_path, f"--issue-map-path={issue_map_path}", "--output=/dev/stdout"]) out, err = capfd.readouterr() assert err == "" diff --git a/tests/community_analyzer_test.py b/tests/community_analyzer_test.py index a40235da..c9d00dc3 100644 --- a/tests/community_analyzer_test.py +++ b/tests/community_analyzer_test.py @@ -182,6 +182,7 @@ def test_community_analyzer(tmp_path: Path) -> None: + """Test for `run_community_analyzer.main()`, to test `issue_map.json` parsing.""" toolbox_path = tmp_path.as_posix() artifacts_path = os.path.join(os.path.dirname(__file__), "test_artifacts") @@ -199,6 +200,7 @@ def test_community_analyzer(tmp_path: Path) -> None: def test_cli(tmp_path: Path) -> None: + """Test for the CLI command, to test `issue_map.json` parsing.""" toolbox_path = tmp_path.as_posix() artifacts_path = os.path.join(os.path.dirname(__file__), "test_artifacts") From 14cce7cef47e97a3390b224ddb2a71f2ae3400a2 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:33:09 +0000 Subject: [PATCH 17/24] style: format code with Black and isort This commit fixes the style issues introduced in 1520982 according to the output from Black and isort. Details: https://github.com/DeepSourceCorp/community-analyzers/pull/3 --- tests/community_analyzer_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/community_analyzer_test.py b/tests/community_analyzer_test.py index c9d00dc3..df1f4df3 100644 --- a/tests/community_analyzer_test.py +++ b/tests/community_analyzer_test.py @@ -1,8 +1,8 @@ import json import os -from pathlib import Path import subprocess import sys +from pathlib import Path import run_community_analyzer From 0e3fad385cda2e0537c8203de4ff5d5c80293579 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 10 Oct 2023 15:09:39 +0530 Subject: [PATCH 18/24] Add CLI test and better error type --- run_community_analyzer.py | 2 +- sarif-parser/src/sarif_parser/__init__.py | 2 +- sarif-parser/tests/cli_test.py | 6 ++++++ tests/community_analyzer_test.py | 22 ---------------------- 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/run_community_analyzer.py b/run_community_analyzer.py index 09dac70d..c3dee510 100644 --- a/run_community_analyzer.py +++ b/run_community_analyzer.py @@ -35,4 +35,4 @@ def main(argv: list[str] | None = None) -> None: if __name__ == "__main__": - main() + main() # pragma: no cover diff --git a/sarif-parser/src/sarif_parser/__init__.py b/sarif-parser/src/sarif_parser/__init__.py index db8eefd9..a55b3670 100644 --- a/sarif-parser/src/sarif_parser/__init__.py +++ b/sarif-parser/src/sarif_parser/__init__.py @@ -96,7 +96,7 @@ def run_sarif_parser( """Parse SARIF files from given filepath, and save JSON output in output path.""" # Get list of sarif files if not os.path.exists(filepath): - raise RuntimeError(f"{filepath} does not exist.") + raise FileNotFoundError(f"{filepath} does not exist.") if os.path.isdir(filepath): artifacts = [os.path.join(filepath, file) for file in os.listdir(filepath)] diff --git a/sarif-parser/tests/cli_test.py b/sarif-parser/tests/cli_test.py index ba373528..dd926af0 100644 --- a/sarif-parser/tests/cli_test.py +++ b/sarif-parser/tests/cli_test.py @@ -186,3 +186,9 @@ def test_cli_with_issue_map( } ) assert out == expected_output + + +def test_cli_file_not_found() -> None: + """Tests `sarif-parser` CLI, with issue code substitution.""" + with pytest.raises(FileNotFoundError): + cli(["/tmp/doesnotexist.py"]) diff --git a/tests/community_analyzer_test.py b/tests/community_analyzer_test.py index df1f4df3..c00ca2ff 100644 --- a/tests/community_analyzer_test.py +++ b/tests/community_analyzer_test.py @@ -197,25 +197,3 @@ def test_community_analyzer(tmp_path: Path) -> None: result = json.load(file) assert result == expected_result - - -def test_cli(tmp_path: Path) -> None: - """Test for the CLI command, to test `issue_map.json` parsing.""" - toolbox_path = tmp_path.as_posix() - artifacts_path = os.path.join(os.path.dirname(__file__), "test_artifacts") - - subprocess.check_call( - [sys.executable, "run_community_analyzer.py", "--analyzer=kube-linter"], - env={ - "TOOLBOX_PATH": toolbox_path, - "ARTIFACTS_PATH": artifacts_path, - }, - ) - - analysis_results = tmp_path / "analysis_results.json" - assert analysis_results.exists() - - with open(analysis_results) as file: - result = json.load(file) - - assert result == expected_result From 1940221a429960401bae199a4acf4366f84255d7 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 10 Oct 2023 15:10:24 +0530 Subject: [PATCH 19/24] no-cache-dir --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e19e1a61..b3fedf8e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,6 @@ RUN apk add --no-cache git grep COPY ./sarif-parser /toolbox/sarif-parser -RUN pip install --no-cache /toolbox/sarif-parser +RUN pip install --no-cache-dir /toolbox/sarif-parser USER runner From cce5fe2ef95b2722e44cdd4c27fa4cd19c2a91c8 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 10 Oct 2023 15:12:16 +0530 Subject: [PATCH 20/24] remove unused imports --- tests/community_analyzer_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/community_analyzer_test.py b/tests/community_analyzer_test.py index c00ca2ff..caa45409 100644 --- a/tests/community_analyzer_test.py +++ b/tests/community_analyzer_test.py @@ -1,7 +1,5 @@ import json import os -import subprocess -import sys from pathlib import Path import run_community_analyzer From 9755dec4adf4e848deb8ad35caa709511dd791de Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 10 Oct 2023 15:12:50 +0530 Subject: [PATCH 21/24] add pragma to exclude_lines --- .coveragerc | 1 + 1 file changed, 1 insertion(+) diff --git a/.coveragerc b/.coveragerc index e75c247a..36e43910 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,3 +1,4 @@ [report] exclude_lines = raise AssertionError + # pragma: no cover \ No newline at end of file From 050f72aef9129dd32f0572591add0473e8e4933d Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 10 Oct 2023 15:12:58 +0530 Subject: [PATCH 22/24] newline at the end --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 36e43910..58bb7f85 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,4 @@ [report] exclude_lines = raise AssertionError - # pragma: no cover \ No newline at end of file + # pragma: no cover From 3f8201b792c73547dd43be8f95ef8640de02ae58 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 10 Oct 2023 15:18:09 +0530 Subject: [PATCH 23/24] fix rcfile --- .coveragerc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.coveragerc b/.coveragerc index 58bb7f85..1b006382 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,4 @@ [report] exclude_lines = raise AssertionError - # pragma: no cover + pragma: no cover From 7ca9433ad67e1b492b959846af947bbc352bcfe7 Mon Sep 17 00:00:00 2001 From: Tushar Sadhwani Date: Tue, 10 Oct 2023 15:19:16 +0530 Subject: [PATCH 24/24] Use action --- .github/workflows/tox.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 2d8f7a1b..62dcefd3 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -5,8 +5,6 @@ on: branches: [master] pull_request: branches: [master] -env: - DEEPSOURCE_DSN: ${{ secrets.DEEPSOURCE_DSN }} jobs: build: @@ -31,6 +29,8 @@ jobs: run: tox - name: Report test coverage to DeepSource - run: | - curl https://deepsource.io/cli | sh - ./bin/deepsource report --analyzer test-coverage --key python --value-file ./coverage.xml + uses: deepsourcelabs/test-coverage-action@master + with: + key: python + coverage-file: ./coverage.xml + dsn: ${{ secrets.DEEPSOURCE_DSN }}