diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 874a34829ba..d5b026c0937 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -106,6 +106,7 @@ jobs: report_content: ${{ steps.check_report.outputs.report_content }} redhat_to_community: ${{ steps.check_report.outputs.redhat_to_community }} message_file: ${{ steps.pr_comment.outputs.message-file }} + message_text_base64: ${{ steps.encode_pr_comment.outputs.message-text-base64 }} web_catalog_only: ${{ steps.check_pr_content.outputs.web_catalog_only }} chart_entry_name: ${{ steps.check_pr_content.outputs.chart-entry-name }} release_tag: ${{ steps.check_pr_content.outputs.release_tag }} @@ -213,6 +214,7 @@ jobs: ../ve1/bin/get-verify-params --directory=pr --api-url=${{ github.event.pull_request._links.self.href }} - name: Install oc + id: install-oc if: ${{ steps.verify_requires.outputs.cluster_needed == 'true' }} uses: redhat-actions/openshift-tools-installer@v1 with: @@ -334,11 +336,20 @@ jobs: PR_CONTENT_ERROR_MESSAGE: ${{ steps.check_pr_content.outputs.pr-content-error-message }} OWNERS_ERROR_MESSAGE: ${{ steps.check_pr_content.outputs.owners-error-message }} COMMUNITY_MANUAL_REVIEW: ${{ steps.check_report.outputs.community_manual_review_required }} - OC_INSTALL_RESULT: ${{ steps.install-oc.conclusion }} + OC_INSTALL_RESULT: ${{ steps.install-oc.outcome }} VERIFIER_ERROR_MESSAGE: ${{ steps.check-verifier-result.outputs.verifier_error_message }} run: | ve1/bin/pr-comment ${{ steps.check_pr_content.outcome }} ${{ steps.run-verifier.outcome }} ${{ steps.check_report.conclusion }} + # Note(komish): This step is a temporary fix for the metrics step in the next job + # which expects the PR comment to exist at the specified filesystem location. + - name: Encode PR Comment for Metrics + id: encode_pr_comment + if: ${{ always() && needs.setup.outputs.run_build == 'true' }} + run: | + commentBase64=$(base64 --wrap=0 ${{ steps.pr_comment.outputs.message-file }}) + echo "message-text-base64=${commentBase64}" | tee -a $GITHUB_OUTPUT + - name: Comment on PR if: ${{ always() && needs.setup.outputs.run_build == 'true' }} uses: actions/github-script@v6 @@ -488,6 +499,16 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Note(komish): This step is a temporary workaround. Metrics requires the PR comment + # to be available, but it is written to the filesystem in the previous job. + # This can be removed once the metrics execution is restructured to have access to the PR + # comment, or pulled out of the release job entirely. + - name: Retrieve PR comment for metrics + if: ${{ always() && needs.setup.outputs.run_build == 'true' && env.GITHUB_REPOSITORY != 'openshift-helm-charts/sandbox' }} + run: | + mkdir -p $(dirname ${{ needs.chart-verifier.outputs.message_file }}) + echo ${{ needs.chart-verifier.outputs.message_text_base64 }} | base64 -d | tee ${{ needs.chart-verifier.outputs.message_file }} + - name: Add metrics if: ${{ always() && needs.setup.outputs.run_build == 'true' && env.GITHUB_REPOSITORY != 'openshift-helm-charts/sandbox' }} env: diff --git a/scripts/src/chartrepomanager/chartrepomanager.py b/scripts/src/chartrepomanager/chartrepomanager.py index 28569708b42..2668d9b2a18 100644 --- a/scripts/src/chartrepomanager/chartrepomanager.py +++ b/scripts/src/chartrepomanager/chartrepomanager.py @@ -1,3 +1,17 @@ +"""This file contains the logic for packaging and releasing the Helm chart in the +hosted Helm repository. It updates the index file and creates GitHub releases. + +A GitHub release (named after the chart+version that is being added) is created under +the following conditions: +* If the chart's source has been provided, it creates an archive (helm package) and + uploads it as a GitHub release. +* If a tarball has been provided, it uploads it directly as a GitHub release. +* No release is created if the user only provided a report file. + +The index entry corresponding to the Chart is either created or updated with the latest +version. +""" + import argparse import shutil import os @@ -29,6 +43,16 @@ def get_modified_charts(api_url): + """Get the category, organization, chart name, and new version corresponding to + the chart being added or modified by this PR. + + Args: + api_url (str): URL of the GitHub PR + + Returns: + (str, str, str, str): category, organization, chart, and version (e.g. partner, + hashicorp, vault, 1.4.0) + """ files = prartifact.get_modified_files(api_url) pattern = re.compile( matchers.submission_path_matcher(strict_categories=False) + r"/.*" @@ -59,6 +83,18 @@ def get_current_commit_sha(): def check_chart_source_or_tarball_exists(category, organization, chart, version): + """Check if the chart's source or chart's tarball is present + + 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) + + Returns: + (bool, bool): First boolean indicates the presence of the chart's source + Second boolean indicates the presence of the chart's tarball + """ src = os.path.join("charts", category, organization, chart, version, "src") if os.path.exists(src): return True, False @@ -73,13 +109,30 @@ def check_chart_source_or_tarball_exists(category, organization, chart, version) def check_report_exists(category, organization, chart, version): + """Check if a report was provided by the user + + 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) + + Returns: + (bool, str): a boolean set to True if the report.yaml file is present, and the + path to the report.yaml file. + """ report_path = os.path.join( "charts", category, organization, chart, version, "report.yaml" ) return os.path.exists(report_path), report_path -def generate_report(chart_file_name): +def generate_report(): + """Creates report file using the content generated by chart-pr-review + + Returns: + str: Path to the report.yaml file. + """ cwd = os.getcwd() report_content = urllib.parse.unquote(os.environ.get("REPORT_CONTENT")) print("[INFO] Report content:") @@ -91,6 +144,18 @@ def generate_report(chart_file_name): def prepare_chart_source_for_release(category, organization, chart, version): + """Create an archive file of the Chart for the GitHub release. + + When the PR contains the chart's source, we package it using "helm package" and + place the archive file in the ".cr-release-packages" directory. This directory will + contain all assets that should be uploaded as a GitHub Release. + + 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) + """ print( "[INFO] prepare chart source for release. %s, %s, %s, %s" % (category, organization, chart, version) @@ -110,11 +175,27 @@ def prepare_chart_source_for_release(category, organization, chart, version): def prepare_chart_tarball_for_release( category, organization, chart, version, signed_chart ): + """Move the provided tarball (and signing key if needed) to the release directory + + The tarball is moved to the ".cr-release-packages" directory. If the archive has + been signed with "helm package --sign", the provenance file is also included. + + 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) + signed_chart (bool): Set to True if the tarball chart is signed. + + Returns: + str: Path to the public key file used to sign the tarball + """ print( "[INFO] prepare chart tarball for release. %s, %s, %s, %s" % (category, organization, chart, version) ) chart_file_name = f"{chart}-{version}.tgz" + # Path to the provided tarball path = os.path.join( "charts", category, organization, chart, version, chart_file_name ) @@ -153,6 +234,13 @@ def get_key_file(category, organization, chart, version): def push_chart_release(repository, organization, commit_hash): + """Call chart-release to create the GitHub release. + + Args: + repository (str): Name of the Github repository + organization (str): Name of the organization (ex: hashicorp) + commit_hash (str): Hash of the HEAD commit + """ print( "[INFO]push chart release. %s, %s, %s " % (repository, organization, commit_hash) @@ -182,6 +270,14 @@ def push_chart_release(repository, organization, commit_hash): def create_worktree_for_index(branch): + """Create a detached worktree for the given branch + + Args: + branch (str): Name of the Git branch + + Returns: + str: Path to local detached worktree + """ dr = tempfile.mkdtemp(prefix="crm-") upstream = os.environ["GITHUB_SERVER_URL"] + "/" + os.environ["GITHUB_REPOSITORY"] out = subprocess.run( @@ -221,15 +317,19 @@ def create_worktree_for_index(branch): return dr -def create_index_from_chart( - indexdir, repository, branch, category, organization, chart, version, chart_url -): - print( - "[INFO] create index from chart. %s, %s, %s, %s, %s" - % (category, organization, chart, version, chart_url) - ) - path = os.path.join("charts", category, organization, chart, version) - chart_file_name = f"{chart}-{version}.tgz" +def create_index_from_chart(chart_file_name): + """Prepare the index entry for this chart + + Given that a chart tarball could be created (i.e. the user provided either the + chart's source or tarball), the content of Chart.yaml is used for the index entry. + + Args: + chart_file_name (str): Name of the chart's archive + + Returns: + dict: content of Chart.yaml, to be used as index entry. + """ + print("[INFO] create index from chart. %s" % (chart_file_name)) out = subprocess.run( [ "helm", @@ -247,6 +347,31 @@ def create_index_from_chart( def create_index_from_report(category, ocp_version_range, report_path): + """Prepare the index entry for this chart. + + In the case only a report was provided by the user, we need to craft an index entry + for this chart. + + To that end, this function performs the following actions: + * Get the list of annotations from report file + * Override / set additional annotations: + * Replaces the certifiedOpenShiftVersions annotation with the + testedOpenShiftVersion annotation. + * Adds supportedOpenShiftVersions if not already set. + * Adds (overrides) providerType. + * Use report.medatata.chart as a base for the index entry + * Merge (override) annotations into the index entry' annotations + * Add digest to index entry if known. + + Args: + category (str): Type of profile (community, partners, or redhat) + ocp_version_range (str): Range of supported OCP versions + report_path (str): Path to the report.yaml file + + Returns: + dict: Index entry for this chart + + """ print( "[INFO] create index from report. %s, %s, %s" % (category, ocp_version_range, report_path) @@ -263,7 +388,6 @@ def create_index_from_report(category, ocp_version_range, report_path): else: annotations["charts.openshift.io/providerType"] = category - chart_url = report_info.get_report_chart_url(report_path) chart_entry = report_info.get_report_chart(report_path) if "annotations" in chart_entry: annotations = chart_entry["annotations"] | annotations @@ -274,7 +398,7 @@ def create_index_from_report(category, ocp_version_range, report_path): if "package" in digests: chart_entry["digest"] = digests["package"] - return chart_entry, chart_url + return chart_entry def set_package_digest(chart_entry): @@ -318,7 +442,6 @@ def update_index_and_push( indexdir, repository, branch, - category, organization, chart, version, @@ -327,6 +450,23 @@ def update_index_and_push( pr_number, web_catalog_only, ): + """Update the Helm repository index file + + Args: + indexfile (str): Name of the index file to update (index.yaml or + unpublished-certified-charts.yaml) + indexdir (str): Path to the local worktree + repository (str): Name of the GitHub repository + branch (str): Name of the git branch + 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) + chart_url (str): URL of the Chart + chart_entry (dict): Index entry to add + pr_number (str): Git Pull Request ID + web_catalog_only (bool): Set to True if the provider has chosen the Web Catalog + Only option. + """ token = os.environ.get("GITHUB_TOKEN") print(f"Downloading {indexfile}") r = requests.get( @@ -462,6 +602,26 @@ def update_index_and_push( def update_chart_annotation( category, organization, chart_file_name, chart, ocp_version_range, report_path ): + """Untar the helm release that was placed under .cr-release-packages, update the + chart's annotations, and repackage the Helm release. + + In particular, following manipulations are performed on annotations: + * Gets the dict of annotations from the report file. + * Replaces the certifiedOpenShiftVersions annotation with the + testedOpenShiftVersion annotation. + * Adds supportedOpenShiftVersions if not already set. + * Adds (overrides) providerType. + * Adds provider if not already set. + * Merge (overrides) those annotations into the Chart's annotations. + + Args: + category (str): Type of profile (community, partners, or redhat) + organization (str): Name of the organization (ex: hashicorp) + chart_file_name (str): Name of the chart's archive + chart (str): Name of the chart (ex: vault) + ocp_version_range (str): Range of supported OCP versions + report_path (str): Path to the report.yaml file + """ print( "[INFO] Update chart annotation. %s, %s, %s, %s, %s" % (category, organization, chart_file_name, chart, ocp_version_range) @@ -610,7 +770,7 @@ def main(): shutil.copy(report_path, "report.yaml") else: print("[INFO] Generate report") - report_path = generate_report(chart_file_name) + report_path = generate_report() print("[INFO] Updating chart annotation") update_chart_annotation( @@ -624,16 +784,7 @@ def main(): chart_url = f"https://github.com/{args.repository}/releases/download/{organization}-{chart}-{version}/{chart_file_name}" print("[INFO] Helm package was released at %s" % chart_url) print("[INFO] Creating index from chart") - chart_entry = create_index_from_chart( - indexdir, - args.repository, - branch, - category, - organization, - chart, - version, - chart_url, - ) + chart_entry = create_index_from_chart(chart_file_name) else: report_path = os.path.join( "charts", category, organization, chart, version, "report.yaml" @@ -643,9 +794,8 @@ def main(): if signedchart.check_report_for_signed_chart(report_path): public_key_file = get_key_file(category, organization, chart, version) print("[INFO] Creating index from report") - chart_entry, chart_url = create_index_from_report( - category, ocp_version_range, report_path - ) + chart_url = report_info.get_report_chart_url(report_path) + chart_entry = create_index_from_report(category, ocp_version_range, report_path) if not web_catalog_only: current_dir = os.getcwd() @@ -661,7 +811,6 @@ def main(): indexdir, args.repository, branch, - category, organization, chart, version, diff --git a/scripts/src/pullrequest/prepare_pr_comment.py b/scripts/src/pullrequest/prepare_pr_comment.py index 6d690ddeae1..a94c669054f 100644 --- a/scripts/src/pullrequest/prepare_pr_comment.py +++ b/scripts/src/pullrequest/prepare_pr_comment.py @@ -28,8 +28,13 @@ def get_verifier_errors_comment(): def get_verifier_errors_trailer(): - return "Please create a valid report with the [chart-verifier](https://github.com/redhat-certification/chart-verifier) \ -and ensure all mandatory checks pass." + return " ".join( + [ + "Please create a valid report with the", + "[chart-verifier](https://github.com/redhat-certification/chart-verifier)", + "and ensure all mandatory checks pass.", + ] + ) def get_look_at_job_output_comment(): @@ -38,60 +43,63 @@ def get_look_at_job_output_comment(): def prepare_failure_comment(): - msg = f"""\ -{get_failure_comment()} -{get_look_at_job_output_comment()}""" + """assembles the comment for certification failures + + Will attempt to read a file with error messaging from the filesystem + and includes that information in its content. (e.g. ./pr/errors) + """ + msg = get_failure_comment() + msg = append_to(msg, get_look_at_job_output_comment()) if os.path.exists("./pr/errors"): errors = open("./pr/errors").read() - msg += f""" -{get_verifier_errors_comment()} - -{errors} - -{get_verifier_errors_trailer()} - -""" + msg = append_to(msg, get_verifier_errors_comment()) + msg = append_to(msg, errors) + msg = append_to(msg, get_verifier_errors_trailer()) gitutils.add_output("error-message", errors) else: gitutils.add_output("error-message", get_failure_comment()) return msg -def prepare_success_comment(): - msg = f"{get_success_coment()}.\n\n" - return msg +def prepare_pr_content_failure_comment(): + """Generate a message for PR Content Check Failures + This function reaches into the environment for known variables + that contain error messages. -def prepare_pr_content_failure_comment(): - msg = f"{get_content_failure_message()} \n" + Error messages are then passed into the GITHUB_OUTPUT. + + Returns a formatted string containing error message contents. + """ + msg = f"{get_content_failure_message()}" pr_content_error_msg = os.environ.get("PR_CONTENT_ERROR_MESSAGE", "") owners_error_msg = os.environ.get("OWNERS_ERROR_MESSAGE", "") if pr_content_error_msg: gitutils.add_output("error-message", pr_content_error_msg) - msg += f"{pr_content_error_msg}\n\n" + msg = append_to(msg, f"{pr_content_error_msg}") if owners_error_msg: gitutils.add_output("error-message", owners_error_msg) - msg += f"{owners_error_msg}\n\n" + msg = append_to(msg, f"{owners_error_msg}") return msg def prepare_run_verifier_failure_comment(): verifier_error_msg = os.environ.get("VERIFIER_ERROR_MESSAGE", "") gitutils.add_output("error-message", verifier_error_msg) - msg = f""" -{verifier_error_msg} - -{get_look_at_job_output_comment()} -""" + msg = verifier_error_msg + msg = append_to(msg, get_look_at_job_output_comment()) return msg def prepare_community_comment(): - msg = f"{get_community_review_message()}\n\n" + msg = f"{get_community_review_message()}" if os.path.exists("./pr/errors"): errors = open("./pr/errors").read() - msg += "However, please note that one or more errors were found while building and verifying your pull request:\n\n" - msg += f"{errors}\n\n" + msg = append_to( + msg, + "However, **please note** that one or more errors were found while building and verifying your pull request:", + ) + msg = append_to(msg, f"{errors}") return msg @@ -99,35 +107,97 @@ def prepare_generic_fail_comment(): msg = "" if os.path.exists("./pr/errors"): errors = open("./pr/errors").read() - msg += "One or more errors were found while building and verifying your pull request:\n\n" - msg += f"{errors}\n\n" + msg = append_to( + msg, + "One or more errors were found while building and verifying your pull request:", + ) + msg = append_to(msg, f"{errors}") else: - msg += "An unspecified error has occured while building and verifying your pull request.\n\n" + msg = append_to( + msg, + "An unspecified error has occured while building and verifying your pull request.", + ) return msg def prepare_oc_install_fail_comment(): - msg = "Unfortunately the certification process failed to install OpenShift and could not complete.\n\n" - msg += "This problem will be addressed by maintainers and no further action is required from the submitter at this time.\n\n" - return msg + return " ".join( + [ + "Unfortunately the certification process failed to install OpenShift Clientand could not complete.", + "This problem will be addressed by maintainers and no further action is required from the submitter at this time.", + ] + ) -def get_comment_header(issue_number): - msg = ( - f"Thank you for submitting PR #{issue_number} for Helm Chart Certification!\n\n" +def append_to(msg, new_content, use_horizontal_divider=False): + """Appends new_content to the msg. + + This utility function helps simplify the building of our PR comment + template. msg and new_content are joined with either a newline separator, or + a horizontal line (if the keyword argument is provided). + + It should be used in cases where the caller needs to join semi-related + ideas. Callers should instead use the join string method in cases where the + msg being constructed is a part of the same 'idea', or 'paragraph'. + + Args: + msg: The original message to which we should append new_content. + new_content: the new string to add. + use_horizontal_divider: Whether to divide the content + with a horizontal line (in markdown.) Horizontal lines are surrounded + in newlines to ensure that it does not inadvertently cause preceding + content to become a Header. + + Returns the msg containing the new content. + """ + divider_string = "" + if use_horizontal_divider: + divider_string = "\n---\n" + + return f""" +{msg} +{divider_string} +{new_content} +""".strip() # Remove surrounding whitespace, like that which is added by putting """ on a newline here. + + +def get_support_information(): + reqs_doc_link = "https://github.com/redhat-certification/chart-verifier/blob/main/docs/helm-chart-checks.md#types-of-helm-chart-checks" + support_link = "https://access.redhat.com/articles/6463941" + return "\n".join( + [ + "For information on the certification process see:", + f"- [Red Hat certification requirements and process for Kubernetes applications that are deployed using Helm charts.]({reqs_doc_link}).", + f"- For support, connect with our [Partner Acceleration Desk]({support_link}).", + ] ) - return msg -def get_comment_footer(vendor_label, chart_name): - msg = "\n---\n\n" - msg += "For information on the certification process see:\n" - msg += "- [Red Hat certification requirements and process for Kubernetes applications that are deployed using Helm charts.](https://redhat-connect.gitbook.io/partner-guide-for-red-hat-openshift-and-container/helm-chart-certification/overview).\n\n" - msg += "For support, connect with our [Technology Partner Success Desk](https://redhat-connect.gitbook.io/red-hat-partner-connect-general-guide/managing-your-account/getting-help/technology-partner-success-desk).\n\n" - msg += f'/metadata {{"vendor_label": "{vendor_label}", "chart_name": "{chart_name}"}}\n\n' +def metadata_label(vendor_label, chart_name): + """Returns the metadata context that must suffix PR comments.""" + return ( + f'/metadata {{"vendor_label": "{vendor_label}", "chart_name": "{chart_name}"}}' + ) + + +def task_table(task_tuples): + """returns a markdown table containing tasks and their outcome + + Args: + task_tuples: a list of two-length tuples where index 0 is the task + and index 1 is the outcome. These values should be short. + """ + sorted(task_tuples) + msg = "|task|outcome|" + "\n|---|---|" + for task_tuple in task_tuples: + msg += f"\n|{task_tuple[0]}|{task_tuple[1]}|" return msg +def overall_outcome(outcome): + return append_to("### Outcome:", f"**{outcome}**") + + def main(): pr_content_result = sys.argv[1] run_verifier_result = sys.argv[2] @@ -135,41 +205,81 @@ def main(): issue_number = open("./pr/NR").read().strip() vendor_label = open("./pr/vendor").read().strip() chart_name = open("./pr/chart").read().strip() - msg = get_comment_header(issue_number) - oc_install_result = os.environ.get("OC_INSTALL_RESULT", False) + + community_manual_review = os.environ.get("COMMUNITY_MANUAL_REVIEW", False) + oc_install_result = os.environ.get("OC_INSTALL_RESULT") + + msg = f"Thank you for submitting PR #{issue_number} for Helm Chart Certification!" + + # Assemble the detail separately to control order in which it is added to + # the overall output. + detail_message = "### Detail" + + outcome = "Failed" # Handle success explicitly if ( pr_content_result == "success" - and run_verifier_result == "success" + # run_verifier may not run if a report is not needed. + and run_verifier_result in ["success", "skipped"] and verify_result == "success" - and oc_install_result == "success" + # installation of oc may not run if a cluster is not needed. + and oc_install_result in ["success", "skipped"] ): - msg += prepare_success_comment() + outcome = "Passed" + detail_message = append_to(detail_message, get_success_coment()) gitutils.add_output("pr_passed", "true") else: # Handle various failure scenarios. if pr_content_result == "failure": - msg += prepare_pr_content_failure_comment() + detail_message = append_to( + detail_message, prepare_pr_content_failure_comment() + ) gitutils.add_output("pr_passed", "false") elif run_verifier_result == "failure": - msg += prepare_run_verifier_failure_comment() + detail_message = append_to( + detail_message, prepare_run_verifier_failure_comment() + ) gitutils.add_output("pr_passed", "false") elif verify_result == "failure": - community_manual_review = os.environ.get("COMMUNITY_MANUAL_REVIEW", False) if community_manual_review: - msg += prepare_community_comment() + outcome = "Pending Manual Review" + detail_message = append_to(detail_message, prepare_community_comment()) gitutils.add_output("pr_passed", "true") else: - msg += prepare_failure_comment() + detail_message = append_to(detail_message, prepare_failure_comment()) gitutils.add_output("pr_passed", "false") elif oc_install_result == "failure": - msg += prepare_oc_install_fail_comment() + detail_message = append_to( + detail_message, prepare_oc_install_fail_comment() + ) gitutils.add_output("pr_passed", "false") else: - msg += prepare_generic_fail_comment() + detail_message = append_to(detail_message, prepare_generic_fail_comment()) gitutils.add_output("pr_passed", "false") - msg += get_comment_footer(vendor_label, chart_name) + msg = append_to(msg, overall_outcome(outcome)) + msg = append_to(msg, detail_message) + if outcome != "Passed": + table = task_table( + [ + ("PR Content Check", pr_content_result), + ("Run Chart Verifier", run_verifier_result), + ("Result Verification", verify_result), + ("OpenShift Client Installation", oc_install_result), + ] + ) + msg = append_to(msg, "### Task Insights") + msg = append_to(msg, "Here are the outcomes of tasks driving this result.") + msg = append_to(msg, table) + + # All comments get helpful links and a metadata + msg = append_to(msg, get_support_information(), use_horizontal_divider=True) + msg = append_to(msg, metadata_label(vendor_label, chart_name)) + + # Print to the console so it's easily visible from CI. + print("*" * 30) + print(msg) + print("*" * 30) with open("./pr/comment", "w") as fd: fd.write(msg)