Skip to content

Commit

Permalink
Add redhat prefix check
Browse files Browse the repository at this point in the history
Ported from:
https://github.com/openshift-helm-charts/development/blob/919146bc199e6366c40cf10d533d4669bd3def70/scripts/src/checkprcontent/checkpr.py#L211-L222

Signed-off-by: Matthias Goerens <mgoerens@redhat.com>

stash

Signed-off-by: Matthias Goerens <mgoerens@redhat.com>

Add owners-error-message GH output

Signed-off-by: Matthias Goerens <mgoerens@redhat.com>

Better craft pr-content-error-message

Signed-off-by: Matthias Goerens <mgoerens@redhat.com>

rework build

Fix build.yaml

add precheck to venv

fix import

fix api_url arg

Add bot_token env

first attempt fix submission env var; add owners_message debug

fix craft pr content error msg && ensure comments are added

fix condition

fix condition

pr artifcat should run always

fix submission env_var

bool env vars python

output vendor labels partners to partner

debug

remove debug and fix vendor output

fix condition for release

move github output

fix check auto merge condition

remove komish ping
  • Loading branch information
mgoerens committed Aug 8, 2024
1 parent f7f78e1 commit d86a0df
Show file tree
Hide file tree
Showing 9 changed files with 379 additions and 181 deletions.
180 changes: 125 additions & 55 deletions .github/workflows/build.yml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions scripts/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ console_scripts =
chart-repo-manager = chartrepomanager.chartrepomanager:main
chart-pr-review = chartprreview.chartprreview:main
check-pr-content = checkprcontent.checkpr:main
pre-check = precheck.precheck:main
pr-artifact = pullrequest.prartifact:main
pr-comment = pullrequest.prepare_pr_comment:main
sa-for-chart-testing = saforcharttesting.saforcharttesting:main
Expand Down
7 changes: 5 additions & 2 deletions scripts/src/chartprreview/chartprreview.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import semantic_version
import semver
import yaml
from environs import Env
from environs import Env, EnvValidationError

try:
from yaml import CLoader as Loader
Expand Down Expand Up @@ -516,7 +516,10 @@ def main():
generated_report_path = os.environ.get("GENERATED_REPORT_PATH")
generated_report_info_path = os.environ.get("REPORT_SUMMARY_PATH")
env = Env()
web_catalog_only = env.bool("WEB_CATALOG_ONLY", False)
try:
web_catalog_only = env.bool("WEB_CATALOG_ONLY", False)
except EnvValidationError:
web_catalog_only = False

submitted_report_path = os.path.join(
"charts", category, organization, chart, version, "report.yaml"
Expand Down
7 changes: 5 additions & 2 deletions scripts/src/chartrepomanager/chartrepomanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import urllib.parse

import yaml
from environs import Env
from environs import Env, EnvValidationError

try:
from yaml import CDumper as Dumper
Expand Down Expand Up @@ -448,7 +448,10 @@ def main():
)

env = Env()
web_catalog_only = env.bool("WEB_CATALOG_ONLY", False)
try:
web_catalog_only = env.bool("WEB_CATALOG_ONLY", False)
except EnvValidationError:
web_catalog_only = False
ocp_version_range = os.environ.get("OCP_VERSION_RANGE", "N/A")

print(f"[INFO] webCatalogOnly/providerDelivery is {web_catalog_only}")
Expand Down
93 changes: 0 additions & 93 deletions scripts/src/checkprcontent/checkpr.py
Original file line number Diff line number Diff line change
@@ -1,100 +1,7 @@
import argparse
import json
import os
import re
import sys

import requests
import semver
import yaml
from reporegex import matchers

try:
from yaml import CLoader as Loader
except ImportError:
from yaml import Loader

sys.path.append("../")
from owners import owners_file
from pullrequest import prartifact
from report import verifier_report
from tools import gitutils

ALLOW_CI_CHANGES = "allow/ci-changes"


def check_web_catalog_only(report_in_pr, num_files_in_pr, report_file_match):
print(f"[INFO] report in PR {report_in_pr}")
print(f"[INFO] num files in PR {num_files_in_pr}")

category, organization, chart, version = report_file_match.groups()

print(f"read owners file : {category}/{organization}/{chart}")
found_owners, owner_data = owners_file.get_owner_data(category, organization, chart)

if found_owners:
owner_web_catalog_only = owners_file.get_web_catalog_only(owner_data)
print(
f"[INFO] webCatalogOnly/providerDelivery from OWNERS : {owner_web_catalog_only}"
)
else:
msg = "[ERROR] OWNERS file was not found."
print(msg)
gitutils.add_output("owners-error-message", msg)
sys.exit(1)

if report_in_pr:
report_file_path = os.path.join(
"pr-branch", "charts", category, organization, chart, version, "report.yaml"
)
print(f"read report file : {report_file_path}")
found_report, report_data = verifier_report.get_report_data(report_file_path)

if found_report:
report_web_catalog_only = verifier_report.get_web_catalog_only(report_data)
print(
f"[INFO] webCatalogOnly/providerDelivery from report : {report_web_catalog_only}"
)
else:
msg = f"[ERROR] Failed tp open report: {report_file_path}."
print(msg)
gitutils.add_output("pr-content-error-message", msg)
sys.exit(1)

web_catalog_only = False
if report_in_pr and num_files_in_pr > 1:
if report_web_catalog_only or owner_web_catalog_only:
msg = "[ERROR] The web catalog distribution method requires the pull request to be report only."
print(msg)
gitutils.add_output("pr-content-error-message", msg)
sys.exit(1)
elif report_in_pr:
if report_web_catalog_only and owner_web_catalog_only:
if verifier_report.get_package_digest(report_data):
web_catalog_only = True
else:
msg = "[ERROR] The web catalog distribution method requires a package digest in the report."
print(msg)
gitutils.add_output("pr-content-error-message", msg)
sys.exit(1)
elif report_web_catalog_only:
msg = "[ERROR] Report indicates web catalog only but the distribution method set for the chart is not web catalog only."
print(msg)
gitutils.add_output("pr-content-error-message", msg)
sys.exit(1)
elif owner_web_catalog_only:
msg = "[ERROR] The web catalog distribution method is set for the chart but is not set in the report."
print(msg)
gitutils.add_output("pr-content-error-message", msg)
sys.exit(1)

if web_catalog_only:
print("[INFO] webCatalogOnly/providerDelivery is a go")
gitutils.add_output("web_catalog_only", "True")
else:
gitutils.add_output("web_catalog_only", "False")
print("[INFO] webCatalogOnly/providerDelivery is a no-go")


def get_file_match_compiled_patterns():
"""Return a tuple of patterns, where the first can be used to match any file in a chart PR
Expand Down
116 changes: 116 additions & 0 deletions scripts/src/precheck/precheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import argparse
import json
import sys

from precheck import submission, serializer
from tools import gitutils


def write_submission_to_file(s: submission.Submission, artifact_path: str):
data = serializer.SubmissionEncoder().encode(s)

with open(artifact_path, "w") as f:
f.write(data)


def read_submission_from_file(articact_path: str):
with open(articact_path, "r") as f:
s = json.load(f, cls=serializer.SubmissionDecoder)

return s


def craft_pr_content_error_msg(s: submission.Submission):
# Checks that this PR is a valid "Chart certification" PR
is_valid, msg = s.is_valid_certification_submission(ignore_owners=True)
if not is_valid:
return msg

# Parse the modified files and determine if it is a "web_catalog_only" certification
try:
s.parse_web_catalog_only()
except submission.SubmissionError as e:
return str(e)

if s.is_web_catalog_only:
if not s.is_valid_web_catalog_only():
msg = "nope"
return msg

return ""


def craft_owners_error_msg(s):
is_valid, msg = s.is_valid_owners_submission()
if is_valid:
msg = "[INFO] OWNERS file changes require manual review by maintainers."
return msg


def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"-u",
"--api_url",
dest="api_url",
type=str,
required=True,
help="API URL for the pull request",
)
parser.add_argument(
"-o",
"--output",
dest="output",
type=str,
required=True,
help="Path to artifact file to write Submission json representation",
)
parser.add_argument(
"-c",
"--chart_submission",
dest="check_chart_submission",
default=False,
action="store_true",
help="Signify that the PR referenced by api_url is expected to be a certification submission",
)

args = parser.parse_args()
try:
s = submission.Submission(args.api_url)
except submission.SubmissionError as e:
print(str(e))
gitutils.add_output("pr-content-error-message", str(e))
sys.exit(10)

### TODO: add check index

owners_error_msg = ""
if s.modified_owners:
# If the PR contains an OWNER file, craft a error message to be added as a comment in the PR
owners_error_msg = craft_owners_error_msg(s)
print(owners_error_msg)
gitutils.add_output("owners-error-message", owners_error_msg)

pr_content_error_msg = ""
if args.check_chart_submission:
pr_content_error_msg = craft_pr_content_error_msg(s)
if pr_content_error_msg:
print(pr_content_error_msg)
gitutils.add_output("pr-content-error-message", pr_content_error_msg)

gitutils.add_output("chart_entry_name", s.chart.name)
gitutils.add_output("release_tag", s.chart.get_release_tag())
gitutils.add_output("web_catalog_only", s.is_web_catalog_only)
gitutils.add_output("category", s.chart.get_vendor_label())

if owners_error_msg or pr_content_error_msg:
print(
f"exit with owners_error_msg={owners_error_msg}; pr_content_error_msg={pr_content_error_msg}"
)
sys.exit(20)

write_submission_to_file(s, args.output)


if __name__ == "__main__":
main()
75 changes: 59 additions & 16 deletions scripts/src/precheck/submission.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,29 @@ class Chart:
name: str = None
version: str = None

def register_chart_info(self, category, organization, name, version):
def register_chart_info(
self, category: str, organization: str, name: str, version: str = None
):
"""Initialize the chart's category, organization, name and version
Providing a version is not mandatory. In case of a Submission that only contains an OWNERS
file, a version is not present.
This function ensures that once set, the chart's information are not modified, as a PR must
only relate to a unique chart.
Args:
category (str): Type of profile (community, partners, or redhat)
organization (str): Name of the organization (ex: hashicorp)
chart (str): Name of the chart (ex: vault)
version (str): The version of the chart (ex: 1.4.0)
Raises:
DuplicateChartError if the caller attempts to modify the chart's information
ChartError if the redhat prefix is incorrectly set
VersionError if the provided version is not semver compatible
"""
if (
(self.category and self.category != category)
or (self.organization and self.organization != organization)
Expand All @@ -78,11 +100,16 @@ def register_chart_info(self, category, organization, name, version):
msg = "[ERROR] A PR must contain only one chart. Current PR includes files for multiple charts."
raise DuplicateChartError(msg)

if not semver.VersionInfo.is_valid(version):
msg = (
f"[ERROR] Helm chart version is not a valid semantic version: {version}"
)
raise VersionError(msg)
# Red Hat charts must carry the Red Hat prefix.
if organization == "redhat":
if not name.startswith("redhat-"):
msg = f"[ERROR] Charts provided by Red Hat must have their name begin with the redhat- prefix. I.e. redhat-{name}"
raise ChartError(msg)

# Non Red Hat charts must not carry the Red Hat prefix.
if organization != "redhat" and name.startswith("redhat-"):
msg = f"[ERROR] The redhat- prefix is reserved for charts provided by Red Hat. Your chart: {name}"
raise ChartError(msg)

# Red Hat charts must carry the Red Hat prefix.
if organization == "redhat":
Expand All @@ -98,11 +125,22 @@ def register_chart_info(self, category, organization, name, version):
self.category = category
self.organization = organization
self.name = name
self.version = version

if version:
if not semver.VersionInfo.is_valid(version):
msg = f"[ERROR] Helm chart version is not a valid semantic version: {version}"
raise VersionError(msg)

self.version = version

def get_owners_path(self):
return f"charts/{self.category}/{self.organization}/{self.name}/OWNERS"

def get_vendor_label(self):
if self.category == "partners":
return "partner"
return self.category

def get_release_tag(self):
return f"{self.organization}-{self.name}-{self.version}"

Expand Down Expand Up @@ -332,7 +370,7 @@ def set_tarball(self, file_path, tarball_match):
else:
self.modified_unknown.append(file_path)

def is_valid_certification_submission(self):
def is_valid_certification_submission(self, ignore_owners: bool = False):
"""Check wether the files in this Submission are valid to attempt to certify a Chart
We expect the user to provide either:
Expand All @@ -347,7 +385,7 @@ def is_valid_certification_submission(self):
Returns True in all other cases
"""
if self.modified_owners:
if self.modified_owners and not ignore_owners:
return False, "[ERROR] Send OWNERS file by itself in a separate PR."

if self.modified_unknown:
Expand All @@ -363,22 +401,27 @@ def is_valid_certification_submission(self):
return False, ""

def is_valid_owners_submission(self):
"""Check wether the file in this Submission are valid for an OWNERS PR
"""Check wether the files in this Submission are valid for an OWNERS PR
Returns True if the PR only modified files is an OWNERS file.
A valid OWNERS PR contains only the OWNERS file, and is not submitted by a partner
Returns False in all other cases.
"""
if (self.chart.category == "partners") and self.modified_owners:
# The PR contains an OWNERS file for a parnter
msg = "[ERROR] OWNERS file should never be set directly by partners. See certification docs."
return False, msg

if len(self.modified_owners) == 1 and len(self.modified_files) == 1:
# Happy path: PR contains a single modified files that is an OWNERS, and is not for a partner
return True, ""

msg = ""
if self.modified_owners:
# At least one OWNERS file, with other files (modified_files > 1)
msg = "[ERROR] Send OWNERS file by itself in a separate PR."
else:
msg = "No OWNERS file provided"
return False, msg

return False, msg
# No OWNERS have been provided
return False, "No OWNERS file provided"

def parse_web_catalog_only(self, repo_path=""):
"""Set the web_catalog_only attribute
Expand Down
Loading

0 comments on commit d86a0df

Please sign in to comment.