Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the versions check #906

Merged
merged 1 commit into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions c2cciutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def get_master_branch(repo: List[str]) -> Tuple[str, bool]:
return master_branch, success


def get_config(branch: Optional[str] = None) -> c2cciutils.configuration.Configuration:
def get_config() -> c2cciutils.configuration.Configuration:
"""
Get the configuration, with project and auto detections.
"""
Expand All @@ -89,7 +89,7 @@ def get_config(branch: Optional[str] = None) -> c2cciutils.configuration.Configu

repository = get_repository()
repo = repository.split("/")
master_branch, credentials = get_master_branch(repo)
master_branch, _ = get_master_branch(repo)

merge(
{
Expand All @@ -106,7 +106,6 @@ def get_config(branch: Optional[str] = None) -> c2cciutils.configuration.Configu
config,
)

based_on_master = get_based_on_master(repo, branch, master_branch, config) if credentials else False
has_docker_files = bool(
subprocess.run(
["git", "ls-files", "*/Dockerfile*", "Dockerfile*"], stdout=subprocess.PIPE, check=True
Expand Down Expand Up @@ -162,14 +161,6 @@ def get_config(branch: Optional[str] = None) -> c2cciutils.configuration.Configu
"gitattribute": True,
"eof": True,
"workflows": True,
"versions": {
"extra_versions": [master_branch],
"backport_labels": True,
"audit": True,
"branches": True,
}
if based_on_master
else False,
"black": True,
"isort": True,
"codespell": True,
Expand Down
240 changes: 1 addition & 239 deletions c2cciutils/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
import sys
from argparse import Namespace
from io import StringIO
from typing import Any, Dict, List, Optional, Set
from typing import Any, Dict

import requests
import ruamel.yaml
import yaml
from ruamel.yaml.comments import CommentedMap
Expand Down Expand Up @@ -249,243 +248,6 @@ def workflows(
return success


def versions(
config: c2cciutils.configuration.ChecksVersionsConfig,
full_config: c2cciutils.configuration.Configuration,
args: Namespace,
) -> bool:
"""
Verify various GitHub / CI tools versions or branches configurations.
Versions from audit workflow, protected branches and backport labels
match with versions from `SECURITY.md` file.
The columns `Version` and `Supported Until` should be present.
The `Supported Until` should contains dates formatted as `dd/mm/yyyy`, or `Unsupported`
(we ignore those lines), or `Best effort`, or `To be defined`.
config is like:
extra_versions: # versions that are not in the `SECURITY.md` but should still be consided
audit: # if `True` check that the audit workflow run on the right branches
backport_labels: # if `True` check the required backport labels exists
branches: # if `True` check that the required branches exists
Arguments:
config: The check section config
full_config: All the CI config
args: The parsed command arguments
"""

del args

# If the `SECURITY.md` file is not present the check is disabled.
if not os.path.exists("SECURITY.md"):
c2cciutils.error(
"versions", "The file 'SECURITY.md' does not exists", "SECURITY.md", error_type="warning"
)
return True

with open("SECURITY.md", encoding="utf-8") as open_file:
security = c2cciutils.security.Security(open_file.read())

for col in ("Version", "Supported Until"):
if col not in security.headers:
c2cciutils.error(
"versions",
f"The file 'SECURITY.md' does not have the column required '{col}'",
"SECURITY.md",
)
return False

version_index = security.headers.index("Version")
date_index = security.headers.index("Supported Until")

success = True
all_versions = set(config.get("extra_versions", []))

for row in security.data:
str_date = row[date_index]
if str_date != "Unsupported":
all_versions.add(row[version_index])

if config.get("audit", False):
if not _versions_audit(all_versions, full_config):
success = False
if config.get("backport_labels", False):
if not _versions_backport_labels(all_versions, full_config):
success = False
if config.get("branches", False):
if not _versions_branches(all_versions, full_config):
success = False

return success


def _get_branch_matrix(
job: Dict[str, Any], branch_to_version_re: List[c2cciutils.VersionTransform]
) -> List[str]:
"""
Get the branches from a `strategy` `matrix`, and return the corresponding version.
Arguments:
job: The job from the GitHub workflow
branch_to_version_re: The transform configuration
"""

matrix = job.get("strategy", {}).get("matrix", {})
if "include" in matrix:
branch = []
for include in matrix["include"]:
if "branch" in include:
branch.append(include["branch"])
else:
branch = matrix.get("branch", [])
return [c2cciutils.get_value(*c2cciutils.match(av, branch_to_version_re)) for av in branch]


def _versions_audit(all_versions: Set[str], full_config: c2cciutils.configuration.Configuration) -> bool:
"""
Check the audit branches match with the versions from the Security.md.
Arguments:
all_versions: All the required versions
full_config: All the CI configuration
"""
success = True
filename = ".github/workflows/audit.yaml"
if not os.path.exists(filename):
c2cciutils.error(
"versions",
f"The file '{filename}' does not exists",
filename,
)
success = False
else:
with open(filename, encoding="utf-8") as open_file:
workflow = yaml.load(open_file, Loader=yaml.SafeLoader)

branch_to_version_re = c2cciutils.compile_re(full_config["version"].get("branch_to_version_re", []))

for name, job in workflow.get("jobs").items():
audit_versions = _get_branch_matrix(job, branch_to_version_re)

if all_versions != set(audit_versions):
c2cciutils.error(
"versions",
f"The workflow '{filename}', job '{name}' does not have a branch matrix with the "
"right list of versions "
f"[{', '.join(sorted(audit_versions))}] != [{', '.join(sorted(all_versions))}]",
)
success = False
return success


def _versions_backport_labels(
all_versions: Set[str], full_config: c2cciutils.configuration.Configuration
) -> bool:
"""
Check the backport labels match with the version from the Security.md.
Arguments:
all_versions: All the required versions
full_config: All the CI configuration
"""
success = True
try:
label_versions = set()

sys.stdout.flush()
sys.stderr.flush()
labels_response = requests.get(
f"https://api.github.com/repos/{c2cciutils.get_repository()}/labels",
headers=c2cciutils.add_authorization_header({"Accept": "application/vnd.github.v3+json"}),
timeout=int(os.environ.get("C2CCIUTILS_TIMEOUT", "30")),
)
labels_response.raise_for_status()

label_re = c2cciutils.compile_re(full_config["version"].get("branch_to_version_re", []), "backport ")
for json_label in labels_response.json():
match = c2cciutils.match(json_label["name"], label_re)
if match[0] is not None:
label_versions.add(c2cciutils.get_value(*match))

if all_versions != label_versions:
c2cciutils.error(
"versions backport labels",
"The backport labels do not have the right list of versions "
f"[{', '.join(sorted(label_versions))}] != [{', '.join(sorted(all_versions))}]",
)
success = False
except FileNotFoundError as exception:
c2cciutils.error(
"versions backport labels",
f"Unable to get credentials to run the check: {exception}",
error_type="warning",
)

return success


def _versions_branches(all_versions: Set[str], full_config: c2cciutils.configuration.Configuration) -> bool:
"""
Check the branches match with the versions from the Security.md.
Arguments:
all_versions: All the required versions
full_config: All the CI configuration
"""
success = True
try:
branch_versions: Set[str] = set()

sys.stdout.flush()
sys.stderr.flush()
url: Optional[str] = f"https://api.github.com/repos/{c2cciutils.get_repository()}/branches"
while url:
branches_response = requests.get(
url,
params={"protected": "true"},
headers=c2cciutils.add_authorization_header({"Accept": "application/vnd.github.v3+json"}),
timeout=int(os.environ.get("C2CCIUTILS_TIMEOUT", "30")),
)
branches_response.raise_for_status()
url = None
try:
links = requests.utils.parse_header_links(branches_response.headers.get("Link", ""))
if isinstance(links, list):
next_links = [link["url"] for link in links if link["rel"] == "next"]
if len(next_links) >= 1:
url = next_links[0]
except Exception as exception: # pylint: disable=broad-except
c2cciutils.error(
"versions branches",
f"error on reading Link header '{branches_response.headers.get('Link')}': {exception}",
error_type="warning",
)

branch_re = c2cciutils.compile_re(full_config["version"].get("branch_to_version_re", []))
for branch in branches_response.json():
match = c2cciutils.match(branch["name"], branch_re)
if match[0] is not None:
branch_versions.add(c2cciutils.get_value(*match))

if len([v for v in all_versions if v not in branch_versions]) > 0:
c2cciutils.error(
"versions branches",
"The version from the protected branches does not correspond with "
f"expected versions [{', '.join(sorted(branch_versions))}] != "
f"[{', '.join(sorted(all_versions))}]",
)
success = False
except FileNotFoundError as exception:
c2cciutils.error(
"versions branches",
f"Unable to get credentials to run the check: {exception}",
error_type="warning",
)

return success


def black(
config: c2cciutils.configuration.ChecksBlackConfig,
full_config: c2cciutils.configuration.Configuration,
Expand Down
32 changes: 0 additions & 32 deletions c2cciutils/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,37 +272,6 @@
"description": "The print the configuration including the auto-generated parts",
"type": "boolean"
},
"checks_versions": {
"title": "checks versions",
"description": "The version check configuration",
"oneOf": [
{
"title": "checks versions config",
"description": "The version check configuration",
"type": "object",
"properties": {
"audit": {
"description": "Check the versions in the audit workflow",
"type": "boolean"
},
"backport_labels": {
"description": "Check the versions of the backport labels",
"type": "boolean"
},
"branches": {
"description": "Check the versions of the protected branches",
"type": "boolean"
},
"extra_versions": {
"description": "Versions that are not in the `SECURITY.md` but should still be considered",
"type": "array",
"items": { "type": "string" }
}
}
},
{ "const": false }
]
},
"checks_workflows": {
"title": "checks workflows",
"description": "The workflows checks configuration",
Expand Down Expand Up @@ -773,7 +742,6 @@
"gitattribute": { "$ref": "#/definitions/checks_gitattribute" },
"isort": { "$ref": "#/definitions/checks_isort" },
"print_config": { "$ref": "#/definitions/checks_print_config" },
"versions": { "$ref": "#/definitions/checks_versions" },
"workflows": { "$ref": "#/definitions/checks_workflows" },
"snyk": { "$ref": "#/definitions/checks_snyk" },
"snyk_code": { "$ref": "#/definitions/checks_snyk_code" },
Expand Down
3 changes: 1 addition & 2 deletions c2cciutils/scripts/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ def main() -> None:
parser.add_argument("--fix", action="store_true", help="fix black and isort issues")
parser.add_argument("--stop", action="store_true", help="stop on first error")
parser.add_argument("--check", help="runs only the specified check")
parser.add_argument("--branch", help="The branch to check, not defined means autodetect")

args = parser.parse_args()

full_config = c2cciutils.get_config(args.branch)
full_config = c2cciutils.get_config()
config = full_config.get("checks", {})
success = True
for key, conf in config.items():
Expand Down