diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d9ffaed3fa..26b1a6cd1e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,6 +30,8 @@ jobs: - name: Check for CI changes id: check_ci_changes + env: + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} run: | # check if workflow testing should run. echo "[INFO] check if PR contains only workflow changes and user is authorized" @@ -40,6 +42,7 @@ jobs: if: ${{ steps.check_ci_changes.outputs.run-tests != true }} env: BOT_NAME: ${{ secrets.BOT_NAME }} + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} run: | # check if PR was created as part of release processing ./ve1/bin/release-checker --api-url=${{ github.event.pull_request._links.self.href }} \ @@ -70,7 +73,7 @@ jobs: elif [ "${CHART_PR_FOR_RELEASE}" == "true" ]; then echo "The PR is part of release processing for the charts repository - do not continue." else - echo "::set-output name=run-build::true" + echo "run-build=true" >> $GITHUB_OUTPUT fi - name: Set Environment @@ -79,12 +82,12 @@ jobs: #set environemnt based on repository if [ $GITHUB_REPOSITORY == "openshift-helm-charts/charts" ]; then echo "Use latest verifier image" - echo "::set-output name=insecure_skip_tls_verify::false" - echo "::set-output name=verifier-action-image::latest" + echo "insecure_skip_tls_verify=false" >> $GITHUB_OUTPUT + echo "verifier-action-image=latest" >> $GITHUB_OUTPUT else echo "Use dev verifier image" - echo "::set-output name=insecure_skip_tls_verify::true" - echo "::set-output name=verifier-action-image::0.1.0" + echo "insecure_skip_tls_verify=true" >> $GITHUB_OUTPUT + echo "verifier-action-image=0.1.0" >> $GITHUB_OUTPUT fi - name: Checkout @@ -101,6 +104,7 @@ jobs: continue-on-error: true env: GITHUB_REF: ${{ github.ref }} + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} run: | INDEX_BRANCH=$(if [ "${GITHUB_REF}" = "refs/heads/main" ]; then echo "refs/heads/gh-pages"; else echo "${GITHUB_REF}-gh-pages"; fi) ./ve1/bin/check-pr-content --index-branch=${INDEX_BRANCH} --repository=${{ github.repository }} --api-url=${{ github.event.pull_request._links.self.href }} @@ -164,6 +168,8 @@ jobs: - name: determine verify requirements if: ${{ steps.check_build_required.outputs.run-build == 'true' }} id: verify_requires + env: + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} working-directory: ./pr-branch run: | ../ve1/bin/get-verify-params --directory=pr --api-url=${{ github.event.pull_request._links.self.href }} @@ -180,7 +186,7 @@ jobs: run: | #calculate cluster params API_SERVER=$( echo -n ${{ secrets.API_SERVER }} | base64 -d) - echo "::set-output name=API_SERVER::${API_SERVER}" + echo "API_SERVER=${API_SERVER}" >> $GITHUB_OUTPUT - uses: redhat-actions/oc-login@v1 id: oc_login @@ -195,9 +201,10 @@ jobs: if: ${{ steps.verify_requires.outputs.cluster_needed == 'true' }} env: API_SERVER: ${{ steps.login-params.outputs.API_SERVER }} + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} run: | ve1/bin/sa-for-chart-testing --create charts-${{ github.event.number }} --token token.txt --server ${API_SERVER} - echo "::set-output name=delete_namespace::true" + echo "delete_namespace=true" >> $GITHUB_OUTPUT echo $KUBECONFIG - uses: redhat-actions/chart-verifier@v1.1 @@ -214,14 +221,15 @@ jobs: if: ${{ always() && steps.run-verifier.outcome == 'failure' }} run: | error_message="The chart verifier returned an error when trying to obtain a verification report for the chart." - echo "::set-output name=verifier_error_message::$error_message" + echo "verifier_error_message=$error_message" >> $GITHUB_OUTPUT - name: Check Report id: check_report if: ${{ steps.check_build_required.outputs.run-build == 'true' }} env: + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} VENDOR_TYPE: ${{ steps.check_pr_content.outputs.category }} - PROVIDER_DELIVERY: ${{ steps.check_pr_content.outputs.providerDelivery }} + WEB_CATALOG_ONLY: ${{ steps.check_pr_content.outputs.webCatalogOnly }} REPORT_GENERATED: ${{ steps.verify_requires.outputs.report_needed }} GENERATED_REPORT_PATH: ${{ steps.run-verifier.outputs.report_file }} REPORT_SUMMARY_PATH: ${{ steps.run-verifier.outputs.report_info_file }} @@ -241,6 +249,8 @@ jobs: ve1/bin/sa-for-chart-testing --delete charts-${{ github.event.number }} - name: Save PR artifact + env: + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} if: ${{ always() && steps.check_build_required.outputs.run-build == 'true' }} run: | ve1/bin/pr-artifact --directory=./pr --pr-number=${{ github.event.number }} --api-url=${{ github.event.pull_request._links.self.href }} @@ -249,6 +259,7 @@ jobs: id: pr_comment if: ${{ always() && steps.check_build_required.outputs.run-build == 'true' }} env: + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} 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 }} @@ -309,6 +320,8 @@ jobs: - name: Check for PR merge if: ${{ steps.check_build_required.outputs.run-build == 'true' }} + env: + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} run: | ./ve1/bin/check-auto-merge --api-url=${{ github.event.pull_request._links.self.href }} @@ -327,13 +340,14 @@ jobs: - name: Release Charts if: ${{ steps.check_build_required.outputs.run-build == 'true' }} env: + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} GITHUB_REF: ${{ github.ref }} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} REPORT_CONTENT: ${{steps.check_report.outputs.report_content}} CHART_ENTRY_NAME: ${{ steps.check_pr_content.outputs.chart-entry-name }} CHART_NAME_WITH_VERSION: ${{ steps.check_pr_content.outputs.chart-name-with-version }} REDHAT_TO_COMMUNITY: ${{ steps.check_report.outputs.redhat_to_community }} - PROVIDER_DELIVERY: ${{ steps.check_pr_content.outputs.providerDelivery }} + WEB_CATALOG_ONLY: ${{ steps.check_pr_content.outputs.webCatalogOnly }} id: release-charts run: | tar zxvf ./scripts/dependencies/helm-chart-releaser/chart-releaser_1.2.0_linux_amd64.tar.gz @@ -358,9 +372,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Add metrics - if: always() && steps.check_build_required.outputs.run-build == 'true' + if: ${{ always() && steps.check_build_required.outputs.run-build == 'true' && env.GITHUB_REPOSITORY != 'openshift-helm-charts/sandbox' }} env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} run: | if [ $GITHUB_REPOSITORY == "openshift-helm-charts/charts" ]; then WRITE_KEY=${{ secrets.SEGMENT_WRITE_KEY }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b16d02b61b..d1465a2737 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,6 +53,8 @@ jobs: - name: Check Request id: check_request + env: + BOT_TOKEN: ${{ secrets.BOT_TOKEN }} run: | # check if workflow testing should run. echo "Request type: '$GITHUB_EVENT_NAME'" diff --git a/.github/workflows/token.yml b/.github/workflows/token.yml index f37758862c..34a42f0835 100644 --- a/.github/workflows/token.yml +++ b/.github/workflows/token.yml @@ -19,7 +19,7 @@ jobs: - name: Get Date id: get-date run: | - echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")" + echo "date=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_OUTPUT shell: bash - uses: actions/cache@v2 diff --git a/.github/workflows/version_check.yml b/.github/workflows/version_check.yml index 33c788def7..b3b9db738f 100644 --- a/.github/workflows/version_check.yml +++ b/.github/workflows/version_check.yml @@ -39,22 +39,22 @@ jobs: echo "dry-run : ${{ github.event.inputs.dry-run }}" echo "update-version : ${{ github.event.inputs.update-version }}" if [ $GITHUB_EVENT_NAME == 'workflow_dispatch' ]; then - echo '::set-output name=run-job::true' + echo "run-job=true" >> $GITHUB_OUTPUT if [ "${{ github.event.inputs.dry-run }}" == "true" ]; then if [[ "${{ github.event.inputs.update-version }}" == "true" && $GITHUB_REPOSITORY != "openshift-helm-charts/charts" ]]; then - echo '::set-output name=check-version::true' + echo "check-version=true" >> $GITHUB_OUTPUT else - echo '::set-output name=check-version::false' + echo "check-version=false" >> $GITHUB_OUTPUT fi else - echo '::set-output name=check-version::true' + echo "check-version=true" >> $GITHUB_OUTPUT fi elif [ $GITHUB_REPOSITORY == "openshift-helm-charts/charts" ]; then - echo '::set-output name=run-job::true' - echo '::set-output name=check-version::true' + echo "run-job=true" >> $GITHUB_OUTPUT + echo "check-version=true" >> $GITHUB_OUTPUT else - echo '::set-output name=run-job::false' - echo '::set-output name=check-version::false' + echo "run-job=false" >> $GITHUB_OUTPUT + echo "check-version=false" >> $GITHUB_OUTPUT fi @@ -78,7 +78,7 @@ jobs: OCP_VERSION=$(./oc version -o json | jq '.openshiftVersion') OCP_VERSION=$(sed -e 's/^"//' -e 's/"$//' <<< $OCP_VERSION) printf "[INFO] Current OCP Version: %s\n" ${OCP_VERSION} - echo "::set-output name=curr_ocp_version::${OCP_VERSION}" + echo "curr_ocp_version=${OCP_VERSION}" >> $GITHUB_OUTPUT shell: bash - name: Checkout software-version branch @@ -100,30 +100,30 @@ jobs: run: | set -euo pipefail if [ "${{ steps.check_repo.outputs.run-job }}" != "true" ]; then - echo "::set-output name=run_tests::false" - echo "::set-output name=update-version::false" + echo "run_tests=false" >> $GITHUB_OUTPUT + echo "update-version=false" >> $GITHUB_OUTPUT elif [ "${{ steps.check_repo.outputs.check-version }}" == "true" ]; then if [ "${{ steps.get_curr_ocp_version.outputs.curr_ocp_version }}" == "${{ steps.get_prev_ocp_version.outputs.result }}" ]; then # No change in the OpenShift versions. printf "OpenShift version has not changed since last run: '%s' -> '%s'\n" "${{ steps.get_prev_ocp_version.outputs.result }}" "${{ steps.get_curr_ocp_version.outputs.curr_ocp_version }}" - echo "::set-output name=update-version::false" + echo "update-version=false" >> $GITHUB_OUTPUT if [ "${{ github.event.inputs.dry-run }}" == "true" ]; then echo "Openshift version has not changed but run anyaway as dry-run is set" - echo "::set-output name=run_tests::true" + echo "run_tests=true" >> $GITHUB_OUTPUT else echo "Openshift version has not changed do not run tests" - echo "::set-output name=run_tests::false" + echo "run_tests=false" >> $GITHUB_OUTPUT fi else printf "OpenShift version has changed since last run: '%s' -> '%s'\n" "${{ steps.get_prev_ocp_version.outputs.result }}" "${{ steps.get_curr_ocp_version.outputs.curr_ocp_version }}" - echo "::set-output name=run_tests::true" - echo "::set-output name=update-version::true" + echo "run_tests=true" >> $GITHUB_OUTPUT + echo "update-version=true" >> $GITHUB_OUTPUT fi else # Run whether open shift version has changed or not echo "Run tests - version check skipped" - echo "::set-output name=update-version::false" - echo "::set-output name=run_tests::true" + echo "update-version=false" >> $GITHUB_OUTPUT + echo "run_tests=true" >> $GITHUB_OUTPUT fi shell: bash @@ -215,16 +215,30 @@ jobs: printf "[INFO] Software Version: '%s'\n" "${{ env.SOFTWARE_VERSION }}" ve1/bin/behave tests/functional/behave_features/ --tags=version-change --logging-level=INFO --no-capture --no-color - - name: Send message to slack channel - id: notify - if: always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' + - name: Send message to helm_dev slack channel + id: notify_dev + if: ${{ always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' && steps.run-schedule-tests.conclusion != 'success' }} uses: archive/github-actions-slack@v2.0.0 with: slack-bot-user-oauth-access-token: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }} slack-channel: C02979BDUPL - slack-text: ${{ steps.run-schedule-tests.conclusion }}! Nightly run after an OpenShift version update to ${{ steps.get_curr_ocp_version.outputs.curr_ocp_version }} was detected. See '${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}' + slack-text: Failure! Nightly run after an OpenShift version update to ${{ steps.get_curr_ocp_version.outputs.curr_ocp_version }} was detected. See '${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}' + + - name: Result from "Send Message to helm_dev slack channel" + if: ${{ always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' && steps.run-schedule-tests.conclusion != 'success' }} + run: echo "The result was ${{ steps.notify_dev.outputs.slack-result }}" + + - name: Send message to helm_notify slack channel + id: notify + if: ${{ always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' && steps.run-schedule-tests.conclusion == 'success' }} + uses: archive/github-actions-slack@v2.0.0 + with: + slack-bot-user-oauth-access-token: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }} + slack-channel: C04K1ARMH8A + slack-text: Success! Nightly run after an OpenShift version update to ${{ steps.get_curr_ocp_version.outputs.curr_ocp_version }} was detected. See '${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}' - - name: Result from "Send Message to slack channel" + - name: Result from "Send Message to helm_notify slack channel" + if: ${{ always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' && steps.run-schedule-tests.conclusion == 'success' }} run: echo "The result was ${{ steps.notify.outputs.slack-result }}" @@ -242,22 +256,22 @@ jobs: echo "dry-run : ${{ github.event.inputs.dry-run }}" echo "update-version : ${{ github.event.inputs.update-version }}" if [ $GITHUB_EVENT_NAME == 'workflow_dispatch' ]; then - echo '::set-output name=run-job::true' + echo "run-job=true" >> $GITHUB_OUTPUT if [ "${{ github.event.inputs.dry-run }}" == "true" ]; then if [[ "${{ github.event.inputs.update-version }}" == "true" && $GITHUB_REPOSITORY != "openshift-helm-charts/charts" ]]; then - echo '::set-output name=check-version::true' + echo "check-version=true" >> $GITHUB_OUTPUT else - echo '::set-output name=check-version::false' + echo "check-version=false" >> $GITHUB_OUTPUT fi else - echo '::set-output name=check-version::true' + echo "check-version=true" >> $GITHUB_OUTPUT fi elif [ $GITHUB_REPOSITORY == "openshift-helm-charts/charts" ]; then - echo '::set-output name=run-job::true' - echo '::set-output name=check-version::true' + echo "run-job=true" >> $GITHUB_OUTPUT + echo "check-version=true" >> $GITHUB_OUTPUT else - echo '::set-output name=run-job::false' - echo '::set-output name=check-version::false' + echo "run-job=false" >> $GITHUB_OUTPUT + echo "check-version=false" >> $GITHUB_OUTPUT fi - name: Get current Chart Verifier version @@ -267,7 +281,7 @@ jobs: QUAY_API='https://quay.io/api/v1/repository/redhat-certification/chart-verifier/tag/' CV_DIGEST=$(curl ${QUAY_API} | jq '[.tags[] | select(.name == "latest")] | .[0].manifest_digest') printf "[INFO] Current Chart Verifier digest: %s\n" ${CV_DIGEST} - echo "::set-output name=current_cv_digest::${CV_DIGEST}" + echo "current_cv_digest=${CV_DIGEST}" >> $GITHUB_OUTPUT shell: bash - name: Checkout software-version branch @@ -289,31 +303,31 @@ jobs: run: | set -euo pipefail if [ "${{ steps.check_repo.outputs.run-job }}" != "true" ]; then - echo "::set-output name=run_tests::false" - echo "::set-output name=update-version::false" + echo "run_tests=false" >> $GITHUB_OUTPUT + echo "update-version=false" >> $GITHUB_OUTPUT elif [ "${{ steps.check_repo.outputs.check-version }}" == "true" ]; then if [ "${{ steps.get_curr_cv_version.outputs.current_cv_digest }}" == "${{ steps.get_prev_cv_digest.outputs.result }}" ]; then # No change in the Chart Verifier image - do not run tests if a scheduled run or dry-run is not set printf "Chart Verifier has not changed since last run: '%s' -> '%s'\n" "${{ steps.get_prev_cv_digest.outputs.result }}" "${{ steps.get_curr_cv_version.outputs.current_cv_digest }}" - echo "::set-output name=update-version::false" + echo "update-version=false" >> $GITHUB_OUTPUT if [ "${{ github.event.inputs.dry-run }}" == "true" ]; then echo "Chart Verifier image has not changed but run anyaway as dry-run is set" - echo "::set-output name=run_tests::true" + echo "run_tests=true" >> $GITHUB_OUTPUT else echo "Chart Verifier image has not changed do not run tests" - echo "::set-output name=run_tests::false" + echo "run_tests=false" >> $GITHUB_OUTPUT fi else # New Chart Verifier image is found printf "Chart Verifier has changed since last run: '%s' -> '%s'\n" "${{ steps.get_prev_cv_digest.outputs.result }}" "${{ steps.get_curr_cv_version.outputs.current_cv_digest }}" - echo "::set-output name=run_tests::true" - echo "::set-output name=update-version::true" + echo "run_tests=true" >> $GITHUB_OUTPUT + echo "update-version=true" >> $GITHUB_OUTPUT fi else # Run whether Chart Verifier image has changed or not echo "Run tests - version check skipped" - echo "::set-output name=update-version::false" - echo "::set-output name=run_tests::true" + echo "update-version=false" >> $GITHUB_OUTPUT + echo "run_tests=true" >> $GITHUB_OUTPUT fi shell: bash @@ -408,19 +422,29 @@ jobs: printf "[INFO] Software Version: '%s'\n" "${{ env.SOFTWARE_VERSION }}" ve1/bin/behave tests/functional/behave_features/ --tags=version-change --logging-level=INFO --no-capture --no-color - - name: Send message to slack channel - id: notify - if: always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' + - name: Send message to helm_dev slack channel + id: notify_dev + if: ${{ always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' && steps.run-schedule-tests.conclusion != 'success' }} uses: archive/github-actions-slack@v2.0.0 - env: - SOFTWARE_NAME: "chart-verifier" - SOFTWARE_VERSION: ${{ steps.get_curr_cv_version.outputs.current_cv_digest }} with: slack-bot-user-oauth-access-token: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }} slack-channel: C02979BDUPL - slack-text: ${{ steps.run-schedule-tests.conclusion }}! Nightly run after a chart-verifier version update to ${{ steps.get_curr_cv_version.outputs.current_cv_digest }} was detected. See '${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}' + slack-text: Failure! Nightly run after a chart-verifier version update to ${{ steps.get_curr_cv_version.outputs.current_cv_digest }} was detected. See '${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}' + + - name: Result from "Send Message to helm_dev slack channel" + if: ${{ always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' && steps.run-schedule-tests.conclusion != 'success' }} + run: echo "The result was ${{ steps.notify_dev.outputs.slack-result }}" + + - name: Send message to helm_notify slack channel + id: notify + if: ${{ always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' && steps.run-schedule-tests.conclusion == 'success' }} + uses: archive/github-actions-slack@v2.0.0 + with: + slack-bot-user-oauth-access-token: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }} + slack-channel: C04K1ARMH8A + slack-text: Success! Nightly run after a chart-verifier version update to ${{ steps.get_curr_cv_version.outputs.current_cv_digest }} was detected. See '${{github.server_url}}/${{github.repository}}/actions/runs/${{github.run_id}}' - - name: Result from "Send Message to slack channel" - if: always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' + - name: Result from "Send Message to helm_notify slack channel" + if: ${{ always() && github.event_name == 'schedule' && steps.check_test.outputs.run_tests == 'true' && steps.run-schedule-tests.conclusion == 'success' }} run: echo "The result was ${{ steps.notify.outputs.slack-result }}" diff --git a/scripts/src/chartprreview/chartprreview.py b/scripts/src/chartprreview/chartprreview.py index be17211ea5..fb821be4b6 100644 --- a/scripts/src/chartprreview/chartprreview.py +++ b/scripts/src/chartprreview/chartprreview.py @@ -22,6 +22,8 @@ from report import report_info from report import verifier_report from signedchart import signedchart +from pullrequest import prartifact +from tools import gitutils def write_error_log(directory, *msg): os.makedirs(directory, exist_ok=True) @@ -39,20 +41,12 @@ def get_vendor_type(directory): sys.exit(1) return vendor_type -def get_labels(api_url): - # api_url https://api.github.com/repos///pulls/1 - headers = {'Accept': 'application/vnd.github.v3+json'} - r = requests.get(api_url, headers=headers) - return r.json()["labels"] - def get_modified_charts(directory, api_url): print("[INFO] Get modified charts. %s" %directory) - files_api_url = f'{api_url}/files' - headers = {'Accept': 'application/vnd.github.v3+json'} - r = requests.get(files_api_url, headers=headers) + files = prartifact.get_modified_files(api_url) pattern = re.compile(r"charts/(\w+)/([\w-]+)/([\w-]+)/([\w\.-]+)/.*") - for f in r.json(): - m = pattern.match(f["filename"]) + for file_path in files: + m = pattern.match(file_path) if m: category, organization, chart, version = m.groups() return category, organization, chart, version @@ -218,7 +212,7 @@ def check_report_success(directory, api_url, report_path, report_info_path, vers print("[INFO] Full report: ") print(data) quoted_data = data.replace("%", "%25").replace("\n", "%0A").replace("\r", "%0D") - print(f"::set-output name=report_content::{quoted_data}") + gitutils.add_output("report_content",quoted_data) chart = report_info.get_report_chart(report_path=report_path,report_info_path=report_info_path) report_version = chart["version"] @@ -260,8 +254,7 @@ def check_report_success(directory, api_url, report_path, report_info_path, vers report = report_info.get_report_results(report_path=report_path,report_info_path=report_info_path,profile_type=vendor_type) - labels = get_labels(api_url) - label_names = [l["name"] for l in labels] + label_names = prartifact.get_labels(api_url) failed = report["failed"] passed = report["passed"] @@ -276,17 +269,17 @@ def check_report_success(directory, api_url, report_path, report_info_path, vers msgs.append(f" - {m}") write_error_log(directory, *msgs) if vendor_type == "redhat": - print(f"::set-output name=redhat_to_community::True") + gitutils.add_output("redhat_to_community","True") if vendor_type != "redhat" and "force-publish" not in label_names: if vendor_type == "community": # requires manual review and approval - print(f"::set-output name=community_manual_review_required::True") + gitutils.add_output("community_manual_review_required","True") sys.exit(1) if vendor_type == "community" and "force-publish" not in label_names: # requires manual review and approval print("[INFO] Community submission requires manual approval.") - print(f"::set-output name=community_manual_review_required::True") + gitutils.add_output("community_manual_review_required","True") sys.exit(1) if failures_in_report or vendor_type == "community": @@ -367,7 +360,7 @@ def main(): generated_report_path = os.environ.get("GENERATED_REPORT_PATH") generated_report_info_path = os.environ.get("REPORT_SUMMARY_PATH") env = Env() - provider_delivery = env.bool("PROVIDER_DELIVERY",False) + web_catalog_only = env.bool("WEB_CATALOG_ONLY",False) if os.path.exists(submitted_report_path): print("[INFO] Report exists: ", submitted_report_path) @@ -376,7 +369,7 @@ def main(): report_info_path = "" if report_generated and report_generated == "True": match_checksum(args.directory,generated_report_info_path, category, organization, chart, version) - elif not provider_delivery: + elif not web_catalog_only: check_url(args.directory, report_path) else: print("[INFO] Report does not exist: ", submitted_report_path) diff --git a/scripts/src/chartrepomanager/chartrepomanager.py b/scripts/src/chartrepomanager/chartrepomanager.py index 813c334b7c..afe644cd7f 100644 --- a/scripts/src/chartrepomanager/chartrepomanager.py +++ b/scripts/src/chartrepomanager/chartrepomanager.py @@ -2,6 +2,7 @@ import shutil import os import sys +import json import re import subprocess import tempfile @@ -23,14 +24,14 @@ from report import report_info from chartrepomanager import indexannotations from signedchart import signedchart +from pullrequest import prartifact +from tools import gitutils def get_modified_charts(api_url): - files_api_url = f'{api_url}/files' - headers = {'Accept': 'application/vnd.github.v3+json'} - r = requests.get(files_api_url, headers=headers) + files = prartifact.get_modified_files(api_url) pattern = re.compile(r"charts/(\w+)/([\w-]+)/([\w-]+)/([\w\.-]+)/.*") - for f in r.json(): - m = pattern.match(f["filename"]) + for file_path in files: + m = pattern.match(file_path) if m: category, organization, chart, version = m.groups() return category, organization, chart, version @@ -140,7 +141,7 @@ def create_worktree_for_index(branch): err = out.stderr.decode("utf-8") if err.strip(): print("Adding upstream remote failed:", err, "branch", branch, "upstream", upstream) - out = subprocess.run(["git", "fetch", "upstream"], capture_output=True) + out = subprocess.run(["git", "fetch", "upstream",branch], capture_output=True) print(out.stdout.decode("utf-8")) err = out.stderr.decode("utf-8") if err.strip(): @@ -197,14 +198,22 @@ def set_package_digest(chart_entry): url = chart_entry["urls"][0] head = requests.head(url, allow_redirects=True) + print(f"[DEBUG]: tgz url : {url}") + print(f"[DEBUG]: response code from head request: {head.status_code}") + target_digest = "" if head.status_code == 200: response = requests.get(url, allow_redirects=True) + print(f"[DEBUG]: response code get request: {response.status_code}") target_digest = hashlib.sha256(response.content).hexdigest() + print(f"[DEBUG]: calculated digest : {target_digest}") + pkg_digest = "" if "digest" in chart_entry: pkg_digest = chart_entry["digest"] + print(f"[DEBUG]: digest in report : {pkg_digest}" ) + if target_digest: if not pkg_digest: @@ -218,12 +227,13 @@ def set_package_digest(chart_entry): raise Exception("Was unable to compute SHA256 digest, please ensure chart url points to a chart package.") -def update_index_and_push(indexfile,indexdir, repository, branch, category, organization, chart, version, chart_url, chart_entry, pr_number, provider_delivery): +def update_index_and_push(indexfile, indexdir, repository, branch, category, organization, chart, version, chart_url, chart_entry, pr_number, web_catalog_only): token = os.environ.get("GITHUB_TOKEN") print(f"Downloading {indexfile}") r = requests.get(f'https://raw.githubusercontent.com/{repository}/{branch}/{indexfile}') original_etag = r.headers.get('etag') now = datetime.now(timezone.utc).astimezone().isoformat() + if r.status_code == 200: data = yaml.load(r.text, Loader=Loader) data["generated"] = now @@ -245,7 +255,7 @@ def update_index_and_push(indexfile,indexdir, repository, branch, category, orga crtentries.append(v) chart_entry["urls"] = [chart_url] - if not provider_delivery: + if not web_catalog_only: set_package_digest(chart_entry) chart_entry["annotations"]["charts.openshift.io/submissionTimestamp"] = now crtentries.append(chart_entry) @@ -271,12 +281,13 @@ def update_index_and_push(indexfile,indexdir, repository, branch, category, orga print("Git status:") print(out.stdout.decode("utf-8")) print(out.stderr.decode("utf-8")) - out = subprocess.run(["git", "commit", "-m", f"{organization}-{chart}-{version} index.yaml (#{pr_number})"], cwd=indexdir, capture_output=True) + out = subprocess.run(["git", "commit", "-m", f"{organization}-{chart}-{version} {indexfile} (#{pr_number})"], cwd=indexdir, capture_output=True) print(out.stdout.decode("utf-8")) err = out.stderr.decode("utf-8") if err.strip(): print(f"Error committing {indexfile}", "index directory", indexdir, "branch", branch, "error:", err) r = requests.head(f'https://raw.githubusercontent.com/{repository}/{branch}/{indexfile}') + etag = r.headers.get('etag') if original_etag and etag and (original_etag != etag): print(f"{indexfile} not updated. ETag mismatch.", "original ETag", original_etag, "new ETag", etag, "index directory", indexdir, "branch", branch) @@ -357,11 +368,11 @@ def main(): indexdir = create_worktree_for_index(branch) env = Env() - provider_delivery = env.bool("PROVIDER_DELIVERY",False) + web_catalog_only = env.bool("WEB_CATALOG_ONLY",False) - print(f'[INFO] provider delivery is {provider_delivery}') + print(f'[INFO] webCatalogOnly/providerDelivery is {web_catalog_only}') - if provider_delivery: + if web_catalog_only: indexfile = "unpublished-certified-charts.yaml" else: indexfile = "index.yaml" @@ -405,17 +416,17 @@ def main(): print("[INFO] Creating index from report") chart_entry, chart_url = create_index_from_report(category, report_path) - if not provider_delivery: + if not web_catalog_only: tag = os.environ.get("CHART_NAME_WITH_VERSION") if not tag: print("[ERROR] Internal error: missing chart name with version (tag)") sys.exit(1) - print(f"::set-output name=tag::{tag}") + gitutils.add_output("tag",tag) current_dir = os.getcwd() - print(f"::set-output name=report_file::{current_dir}/report.yaml") + gitutils.add_output("report_file",f"{current_dir}/report.yaml") if public_key_file: print(f"[INFO] Add key file for release : {current_dir}/{public_key_file}") - print(f"::set-output name=public_key_file::{current_dir}/{public_key_file}") + gitutils.add_output("public_key_file",f"{current_dir}/{public_key_file}") - update_index_and_push(indexfile,indexdir, args.repository, branch, category, organization, chart, version, chart_url, chart_entry, args.pr_number, provider_delivery) + update_index_and_push(indexfile,indexdir, args.repository, branch, category, organization, chart, version, chart_url, chart_entry, args.pr_number, web_catalog_only) diff --git a/scripts/src/chartrepomanager/indexannotations.py b/scripts/src/chartrepomanager/indexannotations.py index d2b593cf9c..b12a6134ce 100644 --- a/scripts/src/chartrepomanager/indexannotations.py +++ b/scripts/src/chartrepomanager/indexannotations.py @@ -2,6 +2,7 @@ import semantic_version import requests import yaml +import json sys.path.append('../') from report import report_info @@ -11,10 +12,11 @@ def getKubVersionMap(): if not kubeOpenShiftVersionMap: - content = requests.get("https://github.com/redhat-certification/chart-verifier/blob/main/internal/tool/kubeOpenShiftVersionMap.yaml?raw=true") - version_data = yaml.safe_load(content.text) - for kubeVersion in version_data["versions"]: - kubeOpenShiftVersionMap[kubeVersion["kube-version"]] = kubeVersion["ocp-version"] + content = requests.get("https://github.com/redhat-certification/chart-verifier/blob/main/internal/tool/kubeOpenShiftVersionMap.yaml?raw=true") + + version_data = yaml.safe_load(content.text) + for kubeVersion in version_data["versions"]: + kubeOpenShiftVersionMap[kubeVersion["kube-version"]] = kubeVersion["ocp-version"] return kubeOpenShiftVersionMap diff --git a/scripts/src/checkautomerge/checkautomerge.py b/scripts/src/checkautomerge/checkautomerge.py index 4ead5dcf6b..241dfec9ec 100644 --- a/scripts/src/checkautomerge/checkautomerge.py +++ b/scripts/src/checkautomerge/checkautomerge.py @@ -1,18 +1,25 @@ import time import sys import argparse +import os import requests def ensure_pull_request_not_merged(api_url): # api_url https://api.github.com/repos///pulls/1 - headers = {'Accept': 'application/vnd.github.v3+json'} + headers = {'Accept': 'application/vnd.github.v3+json','Authorization': f'Bearer {os.environ.get("BOT_TOKEN")}'} merged = False for i in range(20): r = requests.get(api_url, headers=headers) - if r.json()["merged"]: + response_content = r.json() + if "message" in response_content: + print(f'[ERROR] merge status: {response_content["message"]}') + sys.exit(1) + + if response_content["merged"]: merged = True break + time.sleep(10) if not merged: diff --git a/scripts/src/checkprcontent/checkpr.py b/scripts/src/checkprcontent/checkpr.py index 67a757255b..f91232b50d 100644 --- a/scripts/src/checkprcontent/checkpr.py +++ b/scripts/src/checkprcontent/checkpr.py @@ -2,6 +2,7 @@ import os import sys import argparse +import json import requests import semver @@ -14,11 +15,13 @@ sys.path.append('../') from owners import owners_file from report import verifier_report +from pullrequest import prartifact +from tools import gitutils ALLOW_CI_CHANGES = "allow/ci-changes" TYPE_MATCH_EXPRESSION = "(partners|redhat|community)" -def check_provider_delivery(report_in_pr,num_files_in_pr,report_file_match): +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}") @@ -29,12 +32,12 @@ def check_provider_delivery(report_in_pr,num_files_in_pr,report_file_match): found_owners,owner_data = owners_file.get_owner_data(category, organization, chart) if found_owners: - owner_provider_delivery = owners_file.get_provider_delivery(owner_data) - print(f"[INFO] providerDelivery from OWNERS : {owner_provider_delivery}") + 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) - print(f"::set-output name=owners-error-message::{msg}") + gitutils.add_output("owners-error-message",msg) sys.exit(1) if report_in_pr: @@ -43,47 +46,47 @@ def check_provider_delivery(report_in_pr,num_files_in_pr,report_file_match): found_report,report_data = verifier_report.get_report_data(report_file_path) if found_report: - report_provider_delivery = verifier_report.get_provider_delivery(report_data) - print(f"[INFO] providerDelivery from report : {report_provider_delivery}") + 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) - print(f"::set-output name=pr-content-error-message::{msg}") + gitutils.add_output("pr-content-error-message",msg) sys.exit(1) - provider_delivery = False + web_catalog_only = False if report_in_pr and num_files_in_pr > 1: - if report_provider_delivery or owner_provider_delivery: + if report_web_catalog_only or owner_web_catalog_only: msg = f"[ERROR] The web catalog distribution method requires the pull request to be report only." print(msg) - print(f"::set-output name=pr-content-error-message::{msg}") + gitutils.add_output("pr-content-error-message",msg) sys.exit(1) elif report_in_pr: - if report_provider_delivery and owner_provider_delivery: + if report_web_catalog_only and owner_web_catalog_only: if verifier_report.get_package_digest(report_data): - provider_delivery = True + web_catalog_only = True else: msg = f"[ERROR] The web catalog distribution method requires a package digest in the report." print(msg) - print(f"::set-output name=pr-content-error-message::{msg}") + gitutils.add_output("pr-content-error-message",msg) sys.exit(1) - elif report_provider_delivery: + elif report_web_catalog_only: msg = f"[ERROR] Report indicates web catalog only but the distribution method set for the chart is not web catalog only." print(msg) - print(f"::set-output name=pr-content-error-message::{msg}") + gitutils.add_output("pr-content-error-message",msg) sys.exit(1) - elif owner_provider_delivery: + elif owner_web_catalog_only: msg = f"[ERROR] The web catalog distribution method is set for the chart but is not set in the report." print(msg) - print(f"::set-output name=pr-content-error-message::{msg}") + gitutils.add_output("pr-content-error-message",msg) sys.exit(1) - if provider_delivery: - print(f"[INFO] providerDelivery is a go") - print(f"::set-output name=providerDelivery::True") + if web_catalog_only: + print(f"[INFO] webCatalogOnly/providerDelivery is a go") + gitutils.add_output("webCatalogOnly","True") else: - print(f"::set-output name=providerDelivery::False") - print(f"[INFO] providerDelivery is a no-go") + gitutils.add_output("webCatalogOnly","False") + print(f"[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 @@ -100,64 +103,59 @@ def get_file_match_compiled_patterns(): pattern = re.compile(r"charts/"+TYPE_MATCH_EXPRESSION+"/([\w-]+)/([\w-]+)/([\w\.-]+)/.*") reportpattern = re.compile(r"charts/"+TYPE_MATCH_EXPRESSION+"/([\w-]+)/([\w-]+)/([\w\.-]+)/report.yaml") - - return pattern,reportpattern + tarballpattern = re.compile(r"charts/(partners|redhat|community)/([\w-]+)/([\w-]+)/([\w\.-]+)/(.*\.tgz$)") + return pattern,reportpattern,tarballpattern def ensure_only_chart_is_modified(api_url, repository, branch): - # api_url https://api.github.com/repos///pulls/1 - headers = {'Accept': 'application/vnd.github.v3+json'} - r = requests.get(api_url, headers=headers) - for label in r.json()["labels"]: - if label["name"] == ALLOW_CI_CHANGES: + label_names = prartifact.get_labels(api_url) + for label_name in label_names: + if label_name == ALLOW_CI_CHANGES: return - files_api_url = f'{api_url}/files' - headers = {'Accept': 'application/vnd.github.v3+json'} - r = requests.get(files_api_url, headers=headers) - pattern,reportpattern = get_file_match_compiled_patterns() - page_number = 1 - max_page_size,page_size = 100,100 + + files = prartifact.get_modified_files(api_url) + pattern,reportpattern,tarballpattern = get_file_match_compiled_patterns() matches_found = 0 report_found = False none_chart_files = {} - file_count = 0 - - while page_size == max_page_size: - - files_api_query = f'{files_api_url}?per_page={page_size}&page={page_number}' - print(f"Query files : {files_api_query}") - r = requests.get(files_api_query,headers=headers) - files = r.json() - page_size = len(files) - file_count += page_size - page_number += 1 - - for f in files: - file_path = f["filename"] - match = pattern.match(file_path) - if not match: - file_name = os.path.basename(file_path) - none_chart_files[file_name] = file_path + + for file_path in files: + match = pattern.match(file_path) + if not match: + file_name = os.path.basename(file_path) + none_chart_files[file_name] = file_path + else: + matches_found += 1 + if reportpattern.match(file_path): + print(f"[INFO] Report found: {file_path}") + gitutils.add_output("report-exists","true") + report_found = True else: - matches_found += 1 - if reportpattern.match(file_path): - print(f"[INFO] Report found: {file_path}") - print("::set-output name=report-exists::true") - report_found = True - if matches_found == 1: - pattern_match = match - elif pattern_match.groups() != match.groups(): - msg = "[ERROR] A PR must contain only one chart. Current PR includes files for multiple charts." - print(msg) - print(f"::set-output name=pr-content-error-message::{msg}") - exit(1) + tar_match = tarballpattern.match(file_path) + if tar_match: + print(f"[INFO] tarball found: {file_path}") + _,_,chart_name,chart_version,tar_name = tar_match.groups() + expected_tar_name = f"{chart_name}-{chart_version}.tgz" + if tar_name != expected_tar_name: + msg = f"[ERROR] the tgz file is named incorrectly. Expected: {expected_tar_name}" + print(msg) + gitutils.add_output("pr-content-error-message",msg) + exit(1) + + if matches_found == 1: + pattern_match = match + elif pattern_match.groups() != match.groups(): + msg = "[ERROR] A PR must contain only one chart. Current PR includes files for multiple charts." + print(msg) + gitutils.add_output("pr-content-error-message",msg) + exit(1) if none_chart_files: - if file_count > 1 or "OWNERS" not in none_chart_files: #OWNERS not present or preset but not the only file + if len(files) > 1 or "OWNERS" not in none_chart_files: #OWNERS not present or preset but not the only file example_file = list(none_chart_files.values())[0] msg = f"[ERROR] PR includes one or more files not related to charts, e.g., {example_file}" print(msg) - print(f"::set-output name=pr-content-error-message::{msg}") + gitutils.add_output("pr-content-error-message",msg) if "OWNERS" in none_chart_files: file_path = none_chart_files["OWNERS"] @@ -166,33 +164,34 @@ def ensure_only_chart_is_modified(api_url, repository, branch): if category == "partners": msg = "[ERROR] OWNERS file should never be set directly by partners. See certification docs." print(msg) - print(f"::set-output name=owners-error-message::{msg}") + gitutils.add_output("owners-error-message",msg) elif matches_found>0: # There is a mix of chart and non-chart files including OWNERS msg = "[ERROR] Send OWNERS file by itself in a separate PR." print(msg) - print(f"::set-output name=owners-error-message::{msg}") - elif file_count == 1: # OWNERS file is the only file in PR + gitutils.add_output("owners-error-message",msg) + elif len(files) == 1: # OWNERS file is the only file in PR msg = "[INFO] OWNERS file changes require manual review by maintainers." print(msg) - print(f"::set-output name=owners-error-message::{msg}") + gitutils.add_output("owners-error-message",msg) sys.exit(1) - check_provider_delivery(report_found,matches_found,pattern_match) + check_web_catalog_only(report_found, matches_found, pattern_match) if matches_found>0: category, organization, chart, version = pattern_match.groups() - print(f"::set-output name=category::{'partner' if category == 'partners' else category}") - print(f"::set-output name=organization::{organization}") + gitutils.add_output("category",f"{'partner' if category == 'partners' else category}") + gitutils.add_output("organization",organization) if not semver.VersionInfo.isvalid(version): msg = f"[ERROR] Helm chart version is not a valid semantic version: {version}" print(msg) - print(f"::set-output name=pr-content-error-message::{msg}") + gitutils.add_output("pr-content-error-message",msg) sys.exit(1) print("Downloading index.yaml", category, organization, chart, version) r = requests.get(f'https://raw.githubusercontent.com/{repository}/{branch}/index.yaml') + if r.status_code == 200: data = yaml.load(r.text, Loader=Loader) else: @@ -201,25 +200,40 @@ def ensure_only_chart_is_modified(api_url, repository, branch): entry_name = f"{organization}-{chart}" d = data["entries"].get(entry_name, []) - print(f"::set-output name=chart-entry-name::{entry_name}") + gitutils.add_output("chart-entry-name",entry_name) for v in d: if v["version"] == version: msg = f"[ERROR] Helm chart release already exists in the index.yaml: {version}" print(msg) - print(f"::set-output name=pr-content-error-message::{msg}") + gitutils.add_output("pr-content-error-message",msg) sys.exit(1) tag_name = f"{organization}-{chart}-{version}" - print(f"::set-output name=chart-name-with-version::{tag_name}") + gitutils.add_output("chart-name-with-version",tag_name) tag_api = f"https://api.github.com/repos/{repository}/git/ref/tags/{tag_name}" - headers = {'Accept': 'application/vnd.github.v3+json'} + headers = {'Accept': 'application/vnd.github.v3+json','Authorization': f'Bearer {os.environ.get("BOT_TOKEN")}'} print(f"[INFO] checking tag: {tag_api}") r = requests.head(tag_api, headers=headers) if r.status_code == 200: msg = f"[ERROR] Helm chart release already exists in the GitHub Release/Tag: {tag_name}" print(msg) - print(f"::set-output name=pr-content-error-message::{msg}") + gitutils.add_output("pr-content-error-message",msg) sys.exit(1) + try: + if prartifact.xRateLimit in r.headers: + print(f'[DEBUG] {prartifact.xRateLimit} : {r.headers[prartifact.xRateLimit]}') + if prartifact.xRateRemain in r.headers: + print(f'[DEBUG] {prartifact.xRateRemain} : {r.headers[prartifact.xRateRemain]}') + + response_content = r.json() + if "message" in response_content: + print(f'[ERROR] getting index file content: {response_content["message"]}') + sys.exit(1) + except json.decoder.JSONDecodeError: + pass + + + def main(): parser = argparse.ArgumentParser() diff --git a/scripts/src/metrics/metrics.py b/scripts/src/metrics/metrics.py index 7f82b01be5..116cdd6c60 100644 --- a/scripts/src/metrics/metrics.py +++ b/scripts/src/metrics/metrics.py @@ -21,6 +21,8 @@ pr_merged="PR Merged v1.0" pr_outcome="PR Outcome v1.0" charts="charts" +xRateLimit = "X-RateLimit-Limit" +xRateRemain = "X-RateLimit-Remaining" def parse_response(response): result = [] @@ -36,12 +38,24 @@ def parse_response(response): def get_release_metrics(): result = [] for i in itertools.count(start=1): + request_headers = {'Accept': 'application/vnd.github.v3+json','Authorization': f'Bearer {os.environ.get("BOT_TOKEN")}'} response = requests.get( - f'https://api.github.com/repos/openshift-helm-charts/charts/releases?per_page=100&page={i}') + f'https://api.github.com/repos/openshift-helm-charts/charts/releases?per_page=100&page={i}',headers=request_headers) + if not 200 <= response.status_code < 300: print(f"[ERROR] unexpected response getting release data : {response.status_code} : {response.reason}") sys.exit(1) + response_json = response.json() + if xRateLimit in response.headers: + print(f'[DEBUG] {xRateLimit} : {response.headers[xRateLimit]}') + if xRateRemain in response.headers: + print(f'[DEBUG] {xRateRemain} : {response.headers[xRateRemain]}') + + if "message" in response_json: + print(f'[ERROR] getting pr files: {response_json["message"]}') + sys.exit(1) + if len(response_json) == 0: break result.extend(response_json) @@ -397,14 +411,13 @@ def check_rate_limit(g,force): print(f"[INFO] rate limit info: {rate_limit.core}") def getChartUpdate(type,partner,chart,cwd): - if type=="partner": directory_type="partners" else: directory_type=type - directoryPath=os.path.join(cwd, charts,directory_type, partner,chart) # Checking if the directory contains only the OWNERS file + directoryPath=os.path.join(cwd, charts,directory_type, partner,chart) + # Checking if the directory contains only the OWNERS file print(os.listdir(directoryPath)) - print(len(os.listdir(directoryPath))) if len(os.listdir(directoryPath)) == 1: return "new chart" else: @@ -445,7 +458,7 @@ def main(): print("Error: Segment write key not set") sys.exit(1) - g = Github(os.environ.get("github_token")) + g = Github(os.environ.get("BOT_TOKEN")) if args.type == "pull_request": repo_current = g.get_repo(args.repository) diff --git a/scripts/src/metrics/pushowners.py b/scripts/src/metrics/pushowners.py index 4c6d998efa..99c2df53c3 100644 --- a/scripts/src/metrics/pushowners.py +++ b/scripts/src/metrics/pushowners.py @@ -1,7 +1,4 @@ import argparse -import re -from github import Github -import yaml import sys import analytics sys.path.append('../') @@ -18,15 +15,15 @@ def getFileContent(changed_file): status,owner_data=owners_file.get_owner_data_from_file(changed_file) if status==True: users_included=owners_file.get_users_included(owner_data) - provider_delivery=owners_file.get_provider_delivery(owner_data) - if provider_delivery == False: - provider="No" + web_catalog_only=owners_file.get_web_catalog_only(owner_data) + if not web_catalog_only: + web_catalog_only_string="No" else: - provider="Yes" + web_catalog_only_string="Yes" vendor_name=owners_file.get_vendor(owner_data) chart_name=owners_file.get_chart(owner_data) vendor_type=getVendorType(changed_file) - return users_included,provider,vendor_name,chart_name,vendor_type + return users_included,web_catalog_only_string,vendor_name,chart_name,vendor_type else: print("Exception loading OWNERS file") return "","","","","" @@ -35,19 +32,19 @@ def process_pr(added_file,modified_file): if modified_file!='': action="update" update="existing-vendor" - users_included,provider_delivery,vendor_name,chart_name,vendor_type=getFileContent(modified_file) - return users_included,provider_delivery,vendor_name,chart_name,vendor_type,action,update + users_included,web_catalog_only,vendor_name,chart_name,vendor_type=getFileContent(modified_file) + return users_included,web_catalog_only,vendor_name,chart_name,vendor_type,action,update elif added_file!='': action="create" update="new-vendor" - users_included,provider_delivery,vendor_name,chart_name,vendor_type=getFileContent(added_file) - return users_included,provider_delivery,vendor_name,chart_name,vendor_type,action,update + users_included,web_catalog_only,vendor_name,chart_name,vendor_type=getFileContent(added_file) + return users_included,web_catalog_only,vendor_name,chart_name,vendor_type,action,update -def send_owner_metric(write_key,prefix,users_included,provider_delivery,partner,chart_name,type,action,update): +def send_owner_metric(write_key,prefix,users_included,web_catalog_only,partner,chart_name,type,action,update): if chart_name!="" and partner!="": id = f"{prefix}-{type}-{chart_name}" - properties = { "type" : type, "vendor": partner, "chart" : chart_name, "users_included" : users_included, "provider_delivery" :provider_delivery, "action" : action, "update" : update} + properties = { "type" : type, "vendor": partner, "chart" : chart_name, "users_included" : users_included, "provider_delivery" :web_catalog_only, "action" : action, "update" : update} send_metric(write_key,id,"owners v1.0",properties) def on_error(error,items): @@ -93,8 +90,8 @@ def main(): print("Error: Segment write key not set") sys.exit(1) - users_included,provider_delivery,vendor_name,chart_name,vendor_type,action,update = process_pr(args.added[0],args.modified[0]) - send_owner_metric(args.write_key,args.prefix,users_included,provider_delivery,vendor_name,chart_name,vendor_type,action,update) + users_included,web_catalog_only,vendor_name,chart_name,vendor_type,action,update = process_pr(args.added[0],args.modified[0]) + send_owner_metric(args.write_key,args.prefix,users_included,web_catalog_only,vendor_name,chart_name,vendor_type,action,update) if __name__ == '__main__': main() \ No newline at end of file diff --git a/scripts/src/owners/checkuser.py b/scripts/src/owners/checkuser.py index 1c6c460a07..5f02f0f6bb 100644 --- a/scripts/src/owners/checkuser.py +++ b/scripts/src/owners/checkuser.py @@ -21,6 +21,9 @@ except ImportError: from yaml import Loader, Dumper +sys.path.append('../') +from pullrequest import prartifact + OWNERS_FILE = "OWNERS" VERSION_FILE = "release/release_info.json" @@ -42,27 +45,15 @@ def verify_user(username): return False def check_for_restricted_file(api_url): - files_api_url = f'{api_url}/files' - headers = {'Accept': 'application/vnd.github.v3+json'} + files = prartifact.get_modified_files(api_url) pattern_owners = re.compile(OWNERS_FILE) pattern_versionfile = re.compile(VERSION_FILE) pattern_thisfile = re.compile(THIS_FILE) - page_number = 1 - max_page_size,page_size = 100,100 - - while (page_size == max_page_size): - files_api_query = f'{files_api_url}?per_page={page_size}&page={page_number}' - r = requests.get(files_api_query,headers=headers) - files = r.json() - page_size = len(files) - page_number += 1 - - for f in files: - filename = f["filename"] - if pattern_versionfile.match(filename) or pattern_owners.match(filename) or pattern_thisfile.match(filename): - print(f"[INFO] restricted file found: {filename}") - return True + for filename in files: + if pattern_versionfile.match(filename) or pattern_owners.match(filename) or pattern_thisfile.match(filename): + print(f"[INFO] restricted file found: {filename}") + return True return False diff --git a/scripts/src/owners/owners_file.py b/scripts/src/owners/owners_file.py index 10bbd48caa..dbe081faea 100644 --- a/scripts/src/owners/owners_file.py +++ b/scripts/src/owners/owners_file.py @@ -37,13 +37,16 @@ def get_chart(owner_data): pass return chart -def get_provider_delivery(owner_data): - provider_delivery = False +def get_web_catalog_only(owner_data): + web_catalog_only = False try: - provider_delivery = owner_data['providerDelivery'] + if 'webCatalogOnly' in owner_data: + web_catalog_only = owner_data['webCatalogOnly'] + elif 'providerDelivery' in owner_data: + web_catalog_only = owner_data['providerDelivery'] except Exception: pass - return provider_delivery + return web_catalog_only def get_users_included(owner_data): users_included="No" diff --git a/scripts/src/pullrequest/prartifact.py b/scripts/src/pullrequest/prartifact.py index efc22524b4..14849dca8f 100644 --- a/scripts/src/pullrequest/prartifact.py +++ b/scripts/src/pullrequest/prartifact.py @@ -9,32 +9,76 @@ sys.path.append('../') from checkprcontent import checkpr +from tools import gitutils + +pr_files = [] +pr_labels = [] +xRateLimit = "X-RateLimit-Limit" +xRateRemain = "X-RateLimit-Remaining" # TODO(baijum): Move this code under chartsubmission.chart module def get_modified_charts(api_url): - files_api_url = f'{api_url}/files' - headers = {'Accept': 'application/vnd.github.v3+json'} - r = requests.get(files_api_url, headers=headers) - pattern,_ = checkpr.get_file_match_compiled_patterns() - for f in r.json(): - m = pattern.match(f["filename"]) - if m: - category, organization, chart, version = m.groups() + files = get_modified_files(api_url) + pattern,_,_ = checkpr.get_file_match_compiled_patterns() + for file in files: + match = pattern.match(file) + if match: + category, organization, chart, version = match.groups() return category, organization, chart, version return "", "", "", "" def get_modified_files(api_url): - files_api_url = f'{api_url}/files' - headers = {'Accept': 'application/vnd.github.v3+json'} - r = requests.get(files_api_url, headers=headers) - pr_files = [] - print(f"[INFO] file info in PR {r.json()}") - for f in r.json(): - if "filename" in f: - pr_files.append(f["filename"]) + if not pr_files: + page_number = 1 + max_page_size,page_size = 100,100 + headers = {'Accept': 'application/vnd.github.v3+json','Authorization': f'Bearer {os.environ.get("BOT_TOKEN")}'} + files_api_url = f'{api_url}/files' + + while page_size == max_page_size: + + files_api_query = f'{files_api_url}?per_page={page_size}&page={page_number}' + print(f"[INFO] Query files : {files_api_query}") + r = requests.get(files_api_query,headers=headers) + files = r.json() + page_size = len(files) + page_number += 1 + + if xRateLimit in r.headers: + print(f'[DEBUG] {xRateLimit} : {r.headers[xRateLimit]}') + if xRateRemain in r.headers: + print(f'[DEBUG] {xRateRemain} : {r.headers[xRateRemain]}') + + if "message" in files: + print(f'[ERROR] getting pr files: {files["message"]}') + sys.exit(1) + else: + for file in files: + if "filename" in file: + pr_files.append(file["filename"]) + return pr_files +def get_labels(api_url): + if not pr_labels: + headers = {'Accept': 'application/vnd.github.v3+json','Authorization': f'Bearer {os.environ.get("BOT_TOKEN")}'} + r = requests.get(api_url, headers=headers) + pr_data = r.json() + + if xRateLimit in r.headers: + print(f'[DEBUG] {xRateLimit} : {r.headers[xRateLimit]}') + if xRateRemain in r.headers: + print(f'[DEBUG] {xRateRemain} : {r.headers[xRateRemain]}') + + if "message" in pr_data: + print(f'[ERROR] getting pr files: {pr_data["message"]}') + sys.exit(1) + if "labels" in pr_data: + for label in pr_data["labels"]: + pr_labels.append(label["name"]) + + return pr_labels + def save_metadata(directory, vendor_label, chart, number): with open(os.path.join(directory, "vendor"), "w") as fd: print(f"add {directory}/vendor as {vendor_label}") @@ -66,7 +110,7 @@ def main(): if args.get_files: pr_files = get_modified_files(args.api_url) print(f"[INFO] files in pr: {pr_files}") - print(f"::set-output name=pr_files::{pr_files}") + gitutils.add_output("pr_files",pr_files) else: os.makedirs(args.directory, exist_ok=True) category, organization, chart, version = get_modified_charts(args.api_url) diff --git a/scripts/src/pullrequest/prepare_pr_comment.py b/scripts/src/pullrequest/prepare_pr_comment.py index 67a043db56..00d3a00173 100644 --- a/scripts/src/pullrequest/prepare_pr_comment.py +++ b/scripts/src/pullrequest/prepare_pr_comment.py @@ -1,5 +1,6 @@ import os import sys +from tools import gitutils def get_success_coment(): return "Congratulations! Your chart has been certified and will be published shortly." @@ -38,9 +39,9 @@ def prepare_failure_comment(): {get_verifier_errors_trailer()} """ - print(f"::set-output name=error-message::{errors}") + gitutils.add_output("error-message",errors) else: - print(f"::set-output name=error-message::{get_failure_comment()}") + gitutils.add_output("error-message",get_failure_comment()) return msg def prepare_success_comment(): @@ -52,16 +53,16 @@ def prepare_pr_content_failure_comment(): 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: - print(f"::set-output name=error-message::{pr_content_error_msg}") + gitutils.add_output("error-message",pr_content_error_msg) msg += f"{pr_content_error_msg}\n\n" if owners_error_msg: - print(f"::set-output name=error-message::{owners_error_msg}") + gitutils.add_output("error-message",owners_error_msg) msg += f"{owners_error_msg}\n\n" return msg def prepare_run_verifier_failure_comment(): verifier_error_msg = os.environ.get("VERIFIER_ERROR_MESSAGE", "") - print(f"::set-output name=error-message::{verifier_error_msg}") + gitutils.add_output("error-message",verifier_error_msg) msg = f""" {verifier_error_msg} @@ -106,30 +107,30 @@ def main(): oc_install_result = os.environ.get("OC_INSTALL_RESULT", False) if pr_content_result == "failure": msg += prepare_pr_content_failure_comment() - print(f"::set-output name=pr_passed::false") + gitutils.add_output("pr_passed","false") elif run_verifier_result == "failure": msg += prepare_run_verifier_failure_comment() - print(f"::set-output name=pr_passed::false") + 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() - print(f"::set-output name=pr_passed::true") + gitutils.add_output("pr_passed","true") else: msg += prepare_failure_comment() - print(f"::set-output name=pr_passed::false") + gitutils.add_output("pr_passed","false") elif oc_install_result == "failure": msg += prepare_oc_install_fail_comment() - print(f"::set-output name=pr_passed::false") + gitutils.add_output("pr_passed","false") else: - print(f"::set-output name=pr_passed::true") + gitutils.add_output("pr_passed","true") msg += prepare_success_comment() msg += get_comment_footer(vendor_label, chart_name) with open("./pr/comment", "w") as fd: fd.write(msg) - print(f"::set-output name=message-file::{fd.name}") + gitutils.add_output("message-file",fd.name) if __name__ == "__main__": main() diff --git a/scripts/src/release/releasechecker.py b/scripts/src/release/releasechecker.py index 5238348741..170e11a110 100644 --- a/scripts/src/release/releasechecker.py +++ b/scripts/src/release/releasechecker.py @@ -36,6 +36,7 @@ sys.path.append('../') from owners import checkuser from tools import gitutils +from pullrequest import prartifact VERSION_FILE = "release/release_info.json" TYPE_MATCH_EXPRESSION = "(partners|redhat|community)" @@ -43,6 +44,8 @@ CHARTS_PR_HEAD_REPO = gitutils.CHARTS_REPO DEV_PR_BASE_REPO = gitutils.DEVELOPMENT_REPO DEV_PR_HEAD_REPO = gitutils.DEVELOPMENT_REPO +STAGE_PR_BASE_REPO = gitutils.STAGE_REPO +STAGE_PR_HEAD_REPO = gitutils.STAGE_REPO DEFAULT_BOT_NAME = "openshift-helm-charts-bot" ERROR_IF_MATCH_NOT_FOUND = False ERROR_IF_MATCH_FOUND = True @@ -50,31 +53,16 @@ def check_file_in_pr(api_url,pattern,error_value): print("[INFO] check if PR for matching files") - files_api_url = f'{api_url}/files' - headers = {'Accept': 'application/vnd.github.v3+json'} - page_number = 1 - max_page_size,page_size = 100,100 - file_count = 0 - - while page_size == max_page_size: - - files_api_query = f'{files_api_url}?per_page={page_size}&page={page_number}' - print(f"[INFO] Query files : {files_api_query}") - pr_files = requests.get(files_api_query,headers=headers) - files = pr_files.json() - page_size = len(files) - file_count += page_size - page_number += 1 - - for f in files: - file_path = f["filename"] - match = pattern.match(file_path) - if not match and not error_value: - print(f"[INFO] stop non match found : {file_path}") - return False - elif match and error_value: - print(f"[INFO] stop match found : {file_path}") - return False + files = prartifact.get_modified_files(api_url) + + for file_path in files: + match = pattern.match(file_path) + if not match and not error_value: + print(f"[INFO] stop non match found : {file_path}") + return False + elif match and error_value: + print(f"[INFO] stop match found : {file_path}") + return False return True @@ -146,7 +134,7 @@ def check_if_charts_release_branch(sender,pr_branch,pr_body,api_url,pr_head_repo print(f"Release part ({version}) of branch name {pr_branch} is not a valid semantic version.") return False - if not pr_head_repo.endswith(CHARTS_PR_HEAD_REPO): + if not pr_head_repo.endswith(CHARTS_PR_HEAD_REPO) and not pr_head_repo.endswith(STAGE_PR_HEAD_REPO): print(f"PR does not have the expected origin. Got: {pr_head_repo}, expected: {CHARTS_PR_HEAD_REPO}") return False @@ -164,7 +152,7 @@ def make_release_body(version, release_info): body += f"- {info}
" print(f"[INFO] Release body: {body}") - print(f"::set-output name=PR_release_body::{body}") + gitutils.add_output("PR_release_body",body) def get_version_info(): data = {} @@ -204,14 +192,15 @@ def main(): if args.pr_base_repo.endswith(DEV_PR_BASE_REPO): if check_if_dev_release_branch(args.sender,args.pr_branch,args.pr_body,args.api_url,args.pr_head_repo): print('[INFO] Dev release pull request found') - print(f'::set-output name=dev_release_branch::true') + gitutils.add_output("dev_release_branch","true") version = args.pr_branch.removeprefix(releaser.DEV_PR_BRANCH_NAME_PREFIX) - print(f'::set-output name=PR_version::{version}') - print(f"::set-output name=PR_release_body::{args.pr_body}") - elif args.pr_base_repo.endswith(CHARTS_PR_BASE_REPO): + gitutils.add_output("PR_version",version) + gitutils.add_output("PR_release_body",args.pr_body) + elif args.pr_base_repo.endswith(CHARTS_PR_BASE_REPO) or args.pr_base_repo.endswith(STAGE_PR_BASE_REPO): if check_if_charts_release_branch(args.sender,args.pr_branch,args.pr_body,args.api_url,args.pr_head_repo): print('[INFO] Workflow release pull request found') - print(f'::set-output name=charts_release_branch::true') + gitutils.add_output("charts_release_branch","true") + elif args.api_url: ## should be on PR branch if args.pr_base_repo.endswith(DEV_PR_BASE_REPO): @@ -219,17 +208,18 @@ def main(): user_authorized = checkuser.verify_user(args.sender) if version_only and user_authorized: organization = args.pr_base_repo.removesuffix(DEV_PR_BASE_REPO) - print(f'::set-output name=charts_repo::{organization}{CHARTS_PR_BASE_REPO}') + gitutils.add_output("charts_repo",f"{organization}{CHARTS_PR_BASE_REPO}") + gitutils.add_output("stage_repo",f"{organization}{STAGE_PR_BASE_REPO}") version = release_info.get_version("./") version_info = release_info.get_info("./") print(f'[INFO] Release found in PR files : {version}.') - print(f'::set-output name=PR_version::{version}') - print(f'::set-output name=PR_release_info::{version_info}') - print(f'::set-output name=PR_includes_release_only::true') + gitutils.add_output("PR_version",version) + gitutils.add_output("PR_release_info",version_info) + gitutils.add_output("PR_includes_release_only","true") make_release_body(version,version_info) elif version_only and not user_authorized: print(f'[ERROR] sender not authorized : {args.sender}.') - print(f'::set-output name=sender_not_authorized::true') + gitutils.add_output("sender_not_authorized","true") else: print('[INFO] Not a release PR') else: @@ -240,7 +230,7 @@ def main(): # should be on main branch if semver.compare(args.version,version) > 0 : print(f'[INFO] Release {args.version} found in PR files is newer than: {version}.') - print(f'::set-output name=release_updated::true') + gitutils.add_output("release_updated","true") else: print(f'[ERROR] Release found in PR files is not new : {args.version}.') else: diff --git a/scripts/src/release/releaser.py b/scripts/src/release/releaser.py index 2b5faa9478..3090ac5401 100644 --- a/scripts/src/release/releaser.py +++ b/scripts/src/release/releaser.py @@ -170,12 +170,12 @@ def main(): message = f'{CHARTS_PR_BRANCH_BODY_PREFIX} {branch_name}' outcome = gitutils.create_pr(branch_name,[],charts_repository,message,args.target_branch) if outcome == gitutils.PR_CREATED: - print(f'::set-output name=charts_pr_created::true') + gitutils.add_output("charts_pr_created","true") elif outcome == gitutils.PR_NOT_NEEDED: - print(f'::set-output name=charts_pr_not_needed::true') + gitutils.add_output("charts_pr_not_needed","true") else: print("[ERROR] error creating charts PR") - print(f'::set-output name=charts_pr_error::true') + gitutils.add_output("charts_pr_error","true") os.chdir(start_directory) return @@ -190,13 +190,13 @@ def main(): outcome = gitutils.create_pr(branch_name,[release_info.RELEASE_INFO_FILE],args.target_repository,args.dev_pr_body,args.target_branch) if outcome == gitutils.PR_CREATED: print("Dev PR successfully created.") - print(f'::set-output name=dev_pr_created::true') + gitutils.add_output("dev_pr_created","true") elif outcome == gitutils.PR_NOT_NEEDED: print("Dev PR not needed.") - print(f'::set-output name=dev_pr_not_needed::true') + gitutils.add_output("dev_pr_not_needed","true") else: print("[ERROR] error creating development PR.") - print('::set-output name=dev_pr_error::true') + gitutils.add_output("dev_pr_error","true") os.chdir(start_directory) @@ -209,12 +209,12 @@ def main(): message = f'{STAGE_PR_BRANCH_BODY_PREFIX} {branch_name}' outcome = gitutils.create_pr(branch_name,[],stage_repository,message,args.target_branch) if outcome == gitutils.PR_CREATED: - print(f'::set-output name=stage_pr_created::true') + gitutils.add_output("stage_pr_created","true") elif outcome == gitutils.PR_NOT_NEEDED: - print(f'::set-output name=stage_pr_not_needed::true') + gitutils.add_output("stage_pr_not_needed","true") else: print("[ERROR] error creating stage PR") - print(f'::set-output name=stage_pr_error::true') + gitutils.add_output("stage_pr_error","true") os.chdir(start_directory) return diff --git a/scripts/src/report/get_verify_params.py b/scripts/src/report/get_verify_params.py index d369b27a99..75257b93db 100644 --- a/scripts/src/report/get_verify_params.py +++ b/scripts/src/report/get_verify_params.py @@ -6,6 +6,7 @@ sys.path.append('../') from chartprreview import chartprreview from signedchart import signedchart +from tools import gitutils def generate_verify_options(directory,category, organization, chart, version): print("[INFO] Generate verify options. %s, %s, %s" % (organization,chart,version)) @@ -58,9 +59,9 @@ def main(): category, organization, chart, version = chartprreview.get_modified_charts(args.directory, args.api_url) flags,chart_uri,report_needed,cluster_needed = generate_verify_options(args.directory,category, organization, chart, version) - print(f"::set-output name=report_needed::{report_needed}") - print(f"::set-output name=cluster_needed::{cluster_needed}") + gitutils.add_output("report_needed",report_needed) + gitutils.add_output("cluster_needed",cluster_needed) if report_needed: - print(f"::set-output name=verify_args::{flags}") - print(f"::set-output name=verify_uri::{chart_uri}") + gitutils.add_output("verify_args",flags) + gitutils.add_output("verify_uri",chart_uri) diff --git a/scripts/src/report/verifier_report.py b/scripts/src/report/verifier_report.py index dd4d250a2d..c029cb8158 100644 --- a/scripts/src/report/verifier_report.py +++ b/scripts/src/report/verifier_report.py @@ -79,14 +79,17 @@ def get_profile_version(report_data): pass return profile_version -def get_provider_delivery(report_data): - provider_delivery = False +def get_web_catalog_only(report_data): + web_catalog_only = False try: - provider_delivery = report_data["metadata"]["tool"]["providerControlledDelivery"] + if "webCatalogOnly" in report_data["metadata"]["tool"]: + web_catalog_only = report_data["metadata"]["tool"]["webCatalogOnly"] + if "providerControlledDelivery" in report_data["metadata"]["tool"]: + web_catalog_only = report_data["metadata"]["tool"]["providerControlledDelivery"] except Exception as err: - print(f"Exception getting providerControlledDelivery {err=}, {type(err)=}") + print(f"Exception getting webCatalogOnly/providerControlledDelivery {err=}, {type(err)=}") pass - return provider_delivery + return web_catalog_only def get_package_digest(report_data): package_digest = None diff --git a/scripts/src/saforcertadmin/push_secrets.py b/scripts/src/saforcertadmin/push_secrets.py index 370da7150d..2e1a1d26c9 100644 --- a/scripts/src/saforcertadmin/push_secrets.py +++ b/scripts/src/saforcertadmin/push_secrets.py @@ -24,10 +24,14 @@ import logging import os import sys +import json import requests import argparse -token = os.environ.get("GITHUB_TOKEN") +sys.path.append('../') +from pullrequest import prartifact + +token = os.environ.get("BOT_TOKEN") headers = {'Accept': 'application/vnd.github.v3+json', 'Authorization': f'token {token}'} logging.basicConfig(level=logging.INFO) @@ -46,6 +50,16 @@ def get_repo_public_key(repo): logging.error(f"unexpected response getting repo public key : {response.status_code} : {response.reason}") sys.exit(1) response_json = response.json() + + if prartifact.xRateLimit in r.headers: + print(f'[DEBUG] {prartifact.xRateLimit} : {r.headers[prartifact.xRateLimit]}') + if prartifact.xRateRemain in r.headers: + print(f'[DEBUG] {prartifact.xRateRemain} : {r.headers[prartifact.xRateRemain]}') + + if "message" in response_json: + print(f'[ERROR] getting public key: {response_json["message"]}') + sys.exit(1) + return response_json['key_id'], response_json['key'] def get_repo_secrets(repo): @@ -53,9 +67,12 @@ def get_repo_secrets(repo): secret_names = [] response = requests.get(f'https://api.github.com/repos/{repo}/actions/secrets', headers=headers) if response.status_code != 200: - logging.error(f"unexpected response getting secrets : {response.status_code} : {response.reason}") + logging.error(f"[ERROR] unexpected response getting repo secrets : {response.status_code} : {response.reason}") sys.exit(1) response_json = response.json() + if "message" in response_json: + print(f'[ERROR] getting repo secrets: {response_json["message"]}') + sys.exit(1) for i in range(response_json['total_count']): secret_names.append(response_json['secrets'][i]['name']) return secret_names @@ -66,7 +83,15 @@ def create_or_update_repo_secrets(repo, secret_name, key_id, encrypted_value): if response.status_code != 201 and response.status_code != 204: logging.error(f"unexpected response during put request : {response.status_code} : {response.reason}") sys.exit(1) - #response_json = response.json() + try: + response_json = response.json() + if "message" in response_json: + print(f'[ERROR] updating repo secret: {response_json["message"]}') + sys.exit(1) + except json.decoder.JSONDecodeError: + pass + + logging.info(f'Secret {secret_name} create or update successful') def main(): diff --git a/scripts/src/signedchart/signedchart.py b/scripts/src/signedchart/signedchart.py index c4f91c0a87..b0fa5f627c 100644 --- a/scripts/src/signedchart/signedchart.py +++ b/scripts/src/signedchart/signedchart.py @@ -14,6 +14,7 @@ sys.path.append('../') from report import verifier_report from owners import owners_file +from pullrequest import prartifact def check_and_prepare_signed_chart(api_url,report_path,owner_path,key_file_path): @@ -45,18 +46,16 @@ def get_verifier_flags(tar_file,owners_file,temp_dir): def is_chart_signed(api_url,report_path): if api_url: - files_api_url = f'{api_url}/files' - headers = {'Accept': 'application/vnd.github.v3+json'} - r = requests.get(files_api_url, headers=headers) + files = prartifact.get_modified_files(api_url) tgz_pattern = re.compile(r"charts/(\w+)/([\w-]+)/([\w-]+)/([\w\.-]+)/.*.tgz") tgz_found = False prov_pattern = re.compile(r"charts/(\w+)/([\w-]+)/([\w-]+)/([\w\.-]+)/.*.tgz.prov") prov_found = False - for f in r.json(): - if tgz_pattern.match(f["filename"]): + for file_path in files: + if tgz_pattern.match(file_path): tgz_found = True - if prov_pattern.match(f["filename"]): + if prov_pattern.match(file_path): prov_found = True if tgz_found and prov_found: diff --git a/scripts/src/tools/gitutils.py b/scripts/src/tools/gitutils.py index 877688385e..ea24fd479c 100644 --- a/scripts/src/tools/gitutils.py +++ b/scripts/src/tools/gitutils.py @@ -13,6 +13,7 @@ import os +import sys import json import requests from git import Repo @@ -41,23 +42,38 @@ def set_git_username_email(repo, username, email): repo.config_writer().set_value("user", "email", email).release() -def github_api_post(endpoint, bot_token, headers={}, json={}): +def github_api_post(endpoint, headers, json): r = requests.post(f'{GITHUB_BASE_URL}/{endpoint}', headers=headers, json=json) + + try: + response_json = r.json() + + if "message" in response_json: + print(f'[ERROR] from post request: {response_json["message"]}') + sys.exit(1) + except json.JSONDecodeError: + pass + + return r -def github_api_get(endpoint, bot_token, headers={}): +def github_api_get(endpoint, headers): r = requests.get(f'{GITHUB_BASE_URL}/{endpoint}', headers=headers) + response_json = r.json() + if "message" in response_json: + print(f'[ERROR] get request: {response_json["message"]}') + sys.exit(1) + return r -def github_api(method, endpoint, bot_token, headers={}, data={}, json={}): - if not headers: - headers = {'Accept': 'application/vnd.github.v3+json', - 'Authorization': f'Bearer {bot_token}'} +def github_api(method, endpoint, bot_token, json={}): + headers = {'Accept': 'application/vnd.github.v3+json', + 'Authorization': f'Bearer {bot_token}'} if method == 'get': - return github_api_get(endpoint, bot_token, headers=headers) + return github_api_get(endpoint,headers) elif method == 'post': - return github_api_post(endpoint, bot_token, headers=headers, json=json) + return github_api_post(endpoint,headers,json) else: raise ValueError( f"Github API method {method} not implemented in helper function") @@ -101,7 +117,7 @@ def create_pr(branch_name,skip_files,repository,message,target_branch): 'title': branch_name, 'body': f'{message}'} r = github_api( - 'post', f'repos/{repository}/pulls', bot_token, json=data) + 'post', f'repos/{repository}/pulls',bot_token,json=data) j = json.loads(r.text) if 'number' in j: @@ -144,3 +160,7 @@ def add_changes(repo,skip_files): repo.git.add(add) return len(repo.index.diff("HEAD")) > 0 + +def add_output(name,value): + with open(os.environ['GITHUB_OUTPUT'],'a') as fh: + print(f'{name}={value}',file=fh) diff --git a/scripts/src/workflowtesting/checkprforci.py b/scripts/src/workflowtesting/checkprforci.py index 697effe837..96cf61093f 100644 --- a/scripts/src/workflowtesting/checkprforci.py +++ b/scripts/src/workflowtesting/checkprforci.py @@ -10,47 +10,38 @@ from yaml import CLoader as Loader, CDumper as Dumper except ImportError: from yaml import Loader, Dumper +from tools import gitutils + +sys.path.append('../') +from pullrequest import prartifact def check_if_ci_only_is_modified(api_url): # api_url https://api.github.com/repos///pulls/1 - files_api_url = f'{api_url}/files' - headers = {'Accept': 'application/vnd.github.v3+json'} - + files = prartifact.get_modified_files(api_url) workflow_files = [re.compile(r".github/workflows/.*"),re.compile(r"scripts/.*"),re.compile(r"tests/.*")] test_files = [re.compile(r"tests/functional/step_defs/.*_test_.*"),re.compile(r"tests/functional/behave_features/.*.feature")] skip_build_files = [re.compile(r"release/release_info.json"),re.compile(r"README.md"),re.compile(r"docs/([\w-]+)\.md")] - page_number = 1 - max_page_size,page_size = 100,100 workflow_found = False others_found = False tests_included = False - while (page_size == max_page_size): - - files_api_query = f'{files_api_url}?per_page={page_size}&page={page_number}' - r = requests.get(files_api_query,headers=headers) - files = r.json() - page_size = len(files) - page_number += 1 - - for f in files: - filename = f["filename"] - if any([pattern.match(filename) for pattern in workflow_files]): - workflow_found = True - if any([pattern.match(filename) for pattern in test_files]): - tests_included = True - elif any([pattern.match(filename) for pattern in skip_build_files]): - others_found = True - else: - return False + for filename in files: + if any([pattern.match(filename) for pattern in workflow_files]): + workflow_found = True + if any([pattern.match(filename) for pattern in test_files]): + tests_included = True + elif any([pattern.match(filename) for pattern in skip_build_files]): + others_found = True + else: + return False if others_found and not workflow_found: - print("::set-output name=do-not-build::true") + gitutils.add_output("do-not-build","true") elif tests_included: print(f"[INFO] set full_tests_in_pr to true") - print("::set-output name=full_tests_in_pr::true") + gitutils.add_output("full_tests_in_pr","true") return workflow_found @@ -81,17 +72,17 @@ def main(): if not args.api_url: if verify_user(args.username): print(f"[INFO] User authorized for manual invocation - run tests.") - print(f"::set-output name=run-tests::true") + gitutils.add_output("run-tests","true") else: print(f"[INFO] User not authorized for manual invocation - do not run tests.") - print(f"::set-output name=workflow-only-but-not-authorized::true") + gitutils.add_output("workflow-only-but-not-authorized","true") elif check_if_ci_only_is_modified(args.api_url): if verify_user(args.username): print(f"[INFO] PR is workflow changes only and user is authorized - run tests.") - print(f"::set-output name=run-tests::true") + gitutils.add_output("run-tests","true") else: print(f"[INFO] PR is workflow changes only but user is not authorized - do not run tests.") - print(f"::set-output name=workflow-only-but-not-authorized::true") + gitutils.add_output("workflow-only-but-not-authorized","true") else: print(f"[INFO] Non workflow changes were found - do not run tests") diff --git a/tests/data/HC-10/signed_chart/report/partner/report.yaml b/tests/data/HC-10/signed_chart/report/partner/report.yaml index 3e91a22764..183f114dc9 100644 --- a/tests/data/HC-10/signed_chart/report/partner/report.yaml +++ b/tests/data/HC-10/signed_chart/report/partner/report.yaml @@ -6,13 +6,13 @@ metadata: profile: VendorType: partner version: v1.2 - reportDigest: uint64:16989833708667280020 + reportDigest: uint64:5138098052618695524 chart-uri: https://github.com/openshift-helm-charts/development/blob/main/tests/data/HC-10/signed_chart/vault-0.17.0.tgz?raw=true digests: chart: sha256:f01dd362d81fe4b1ef99bb7fa5be268fa94dc96f3009d28bc93a18517aa1ef7a package: df206272be1282a05af0576a054c9b35a8d2cebb836dc990d8b87dced82dcdb9 publicKey: 662ba24b23e80f0b26a634f3c215e74d973943e32d0ee85868759365a5640995 - lastCertifiedTimestamp: "2022-11-22T11:49:38.322793+05:30" + lastCertifiedTimestamp: "2023-01-17T14:39:57.760254+05:30" testedOpenShiftVersion: "4.11" supportedOpenShiftVersions: '>=4.2' providerControlledDelivery: false @@ -48,40 +48,22 @@ metadata: type: "" chart-overrides: "" results: - - check: v1.0/contains-values-schema + - check: v1.0/contains-test type: Mandatory outcome: PASS - reason: Values schema file exist + reason: Chart test files exist - check: v1.0/helm-lint type: Mandatory outcome: PASS reason: Helm lint successful - - check: v1.0/images-are-certified - type: Mandatory - outcome: PASS - reason: |- - Image is Red Hat certified : registry.connect.redhat.com/hashicorp/vault-k8s:0.14.0-ubi - Image is Red Hat certified : registry.connect.redhat.com/hashicorp/vault:1.8.4-ubi - - check: v1.0/is-helm-v3 - type: Mandatory - outcome: PASS - reason: API version is V2, used in Helm 3 - - check: v1.0/chart-testing - type: Mandatory - outcome: PASS - reason: Chart tests have passed - check: v1.0/not-contains-crds type: Mandatory outcome: PASS reason: Chart does not contain CRDs - - check: v1.0/has-readme - type: Mandatory - outcome: PASS - reason: Chart has a README - - check: v1.0/contains-test + - check: v1.0/contains-values type: Mandatory outcome: PASS - reason: Chart test files exist + reason: Values file exist - check: v1.0/not-contain-csi-objects type: Mandatory outcome: PASS @@ -90,16 +72,34 @@ results: type: Mandatory outcome: PASS reason: All required annotations present + - check: v1.0/chart-testing + type: Mandatory + outcome: PASS + reason: Chart tests have passed + - check: v1.1/has-kubeversion + type: Mandatory + outcome: PASS + reason: Kubernetes version specified - check: v1.0/signature-is-valid type: Mandatory outcome: PASS reason: 'Chart is signed : Signature verification passed' - - check: v1.0/contains-values + - check: v1.0/contains-values-schema type: Mandatory outcome: PASS - reason: Values file exist - - check: v1.1/has-kubeversion + reason: Values schema file exist + - check: v1.0/has-readme type: Mandatory outcome: PASS - reason: Kubernetes version specified + reason: Chart has a README + - check: v1.1/images-are-certified + type: Mandatory + outcome: PASS + reason: |- + Image is Red Hat certified : registry.connect.redhat.com/hashicorp/vault-k8s:0.14.0-ubi + Image is Red Hat certified : registry.connect.redhat.com/hashicorp/vault:1.8.4-ubi + - check: v1.0/is-helm-v3 + type: Mandatory + outcome: PASS + reason: API version is V2, used in Helm 3 diff --git a/tests/data/HC-10/signed_chart/report/redhat/report.yaml b/tests/data/HC-10/signed_chart/report/redhat/report.yaml index 530095ccde..6920d2b631 100644 --- a/tests/data/HC-10/signed_chart/report/redhat/report.yaml +++ b/tests/data/HC-10/signed_chart/report/redhat/report.yaml @@ -6,13 +6,13 @@ metadata: profile: VendorType: redhat version: v1.2 - reportDigest: uint64:16923642309854382860 + reportDigest: uint64:18038370130181201778 chart-uri: https://github.com/openshift-helm-charts/development/blob/main/tests/data/HC-10/signed_chart/vault-0.17.0.tgz?raw=true digests: chart: sha256:f01dd362d81fe4b1ef99bb7fa5be268fa94dc96f3009d28bc93a18517aa1ef7a package: df206272be1282a05af0576a054c9b35a8d2cebb836dc990d8b87dced82dcdb9 publicKey: 662ba24b23e80f0b26a634f3c215e74d973943e32d0ee85868759365a5640995 - lastCertifiedTimestamp: "2022-11-22T11:51:58.909069+05:30" + lastCertifiedTimestamp: "2023-01-17T14:49:46.03165+05:30" testedOpenShiftVersion: "4.11" supportedOpenShiftVersions: '>=4.2' providerControlledDelivery: false @@ -48,32 +48,40 @@ metadata: type: "" chart-overrides: "" results: - - check: v1.0/contains-values-schema + - check: v1.0/is-helm-v3 type: Mandatory outcome: PASS - reason: Values schema file exist - - check: v1.0/images-are-certified + reason: API version is V2, used in Helm 3 + - check: v1.0/not-contain-csi-objects + type: Mandatory + outcome: PASS + reason: CSI objects do not exist + - check: v1.0/has-readme + type: Mandatory + outcome: PASS + reason: Chart has a README + - check: v1.1/images-are-certified type: Mandatory outcome: PASS reason: |- Image is Red Hat certified : registry.connect.redhat.com/hashicorp/vault-k8s:0.14.0-ubi Image is Red Hat certified : registry.connect.redhat.com/hashicorp/vault:1.8.4-ubi - - check: v1.0/required-annotations-present + - check: v1.0/not-contains-crds type: Mandatory outcome: PASS - reason: All required annotations present - - check: v1.0/is-helm-v3 + reason: Chart does not contain CRDs + - check: v1.0/required-annotations-present type: Mandatory outcome: PASS - reason: API version is V2, used in Helm 3 + reason: All required annotations present - check: v1.0/chart-testing type: Mandatory outcome: PASS reason: Chart tests have passed - - check: v1.0/not-contains-crds + - check: v1.0/contains-values-schema type: Mandatory outcome: PASS - reason: Chart does not contain CRDs + reason: Values schema file exist - check: v1.0/contains-test type: Mandatory outcome: PASS @@ -82,6 +90,10 @@ results: type: Mandatory outcome: PASS reason: Kubernetes version specified + - check: v1.0/helm-lint + type: Mandatory + outcome: PASS + reason: Helm lint successful - check: v1.0/signature-is-valid type: Mandatory outcome: PASS @@ -90,16 +102,4 @@ results: type: Mandatory outcome: PASS reason: Values file exist - - check: v1.0/has-readme - type: Mandatory - outcome: PASS - reason: Chart has a README - - check: v1.0/helm-lint - type: Mandatory - outcome: PASS - reason: Helm lint successful - - check: v1.0/not-contain-csi-objects - type: Mandatory - outcome: PASS - reason: CSI objects do not exist diff --git a/tests/functional/behave_features/HC-10_signed_chart.feature b/tests/functional/behave_features/HC-10_signed_chart.feature new file mode 100644 index 0000000000..5cd2907f1a --- /dev/null +++ b/tests/functional/behave_features/HC-10_signed_chart.feature @@ -0,0 +1,111 @@ +Feature: Signed chart submission + Partners or redhat users can publish their signed chart + + Scenario Outline: [HC-10-001] A partner or redhat associate submits a signed chart tarball without report + Given the vendor "" has a valid identity as "" + And a signed chart tarball is used in "" and public key in "" + When the user sends a pull request with the chart + Then the user sees the pull request is merged + And the index.yaml file is updated with an entry for the submitted chart + And a release is published with corresponding report, tarball, prov and key + + @partners @smoke @full + Examples: + | vendor_type | vendor | chart_path | public_key_file | + | partners | hashicorp | tests/data/HC-10/signed_chart/vault-0.17.0.tgz | tests/data/HC-10/signed_chart/public_key_good.asc | + + @redhat @full + Examples: + | vendor_type | vendor | chart_path | public_key_file | + | redhat | redhat | tests/data/HC-10/signed_chart/vault-0.17.0.tgz | tests/data/HC-10/signed_chart/public_key_good.asc | + + Scenario Outline: [HC-10-002] A partner or redhat associate submits a signed chart tarball with report + Given the vendor "" has a valid identity as "" + And a signed chart tar is used in "", report in "" and public key in "" + When the user sends a pull request with the chart + Then the user sees the pull request is merged + And the index.yaml file is updated with an entry for the submitted chart + And a release is published with corresponding report, tarball, prov and key + + @partners @full + Examples: + | vendor_type | vendor | chart_path | report_path | public_key_file | + | partners | hashicorp | tests/data/HC-10/signed_chart/vault-0.17.0.tgz | tests/data/HC-10/signed_chart/report/partner/report.yaml | tests/data/HC-10/signed_chart/public_key_good.asc | + + @redhat @full + Examples: + | vendor_type | vendor | chart_path | report_path | public_key_file | + | redhat | redhat | tests/data/HC-10/signed_chart/vault-0.17.0.tgz | tests/data/HC-10/signed_chart/report/redhat/report.yaml | tests/data/HC-10/signed_chart/public_key_good.asc | + + Scenario Outline: [HC-10-003] A partner or redhat associate submits a signed chart report + Given the vendor "" has a valid identity as "" + And signed chart report used in "" and public key in "" + When the user sends a pull request with the report + Then the user sees the pull request is merged + And the index.yaml file is updated with an entry for the submitted chart + And a release is published with corresponding report and key + + @partners @smoke @full + Examples: + | vendor_type | vendor | report_path | public_key_file | + | partners | hashicorp | tests/data/HC-10/signed_chart/report/partner/report.yaml | tests/data/HC-10/signed_chart/public_key_good.asc | + + @redhat @full + Examples: + | vendor_type | vendor | report_path | public_key_file | + | redhat | redhat | tests/data/HC-10/signed_chart/report/redhat/report.yaml | tests/data/HC-10/signed_chart/public_key_good.asc | + + Scenario Outline: [HC-10-004] A partner or redhat associate submits an unsigned chart tarball + Given the vendor "" has a valid identity as "" + And unsigned chart tarball is used in "" and public key used "" in owners + When the user sends a pull request with the chart + Then the user sees the pull request is merged + And the index.yaml file is updated with an entry for the submitted chart + And a release is published with corresponding report and chart tarball + + @partners @full + Examples: + | vendor_type | vendor | chart_path | public_key_file | + | partners | hashicorp | tests/data/vault-0.17.0.tgz | tests/data/HC-10/signed_chart/public_key_good.asc | + + @redhat @full + Examples: + | vendor_type | vendor | chart_path | public_key_file | + | redhat | redhat | tests/data/vault-0.17.0.tgz | tests/data/HC-10/signed_chart/public_key_good.asc | + + Scenario Outline: [HC-10-005] A partner or redhat associate submits a signed chart tarball when public key is not in owners + Given the vendor "" has a valid identity as "" + And signed chart tar used in "" + When the user sends a pull request with the chart + Then the user sees the pull request is merged + And the index.yaml file is updated with an entry for the submitted chart + And a release is published with corresponding report, chart tar and prov file + + @partners @full + Examples: + | vendor_type | vendor | chart_path | + | partners | hashicorp | tests/data/HC-10/signed_chart/vault-0.17.0.tgz | + + @redhat @full + Examples: + | vendor_type | vendor | chart_path | + | redhat | redhat | tests/data/HC-10/signed_chart/vault-0.17.0.tgz | + + Scenario Outline: [HC-10-006] A partner or redhat associate submits a signed chart tarball with wrong key in OWNERS file + Given the vendor "" has a valid identity as "" + And a signed chart tarball is used in "" and public key in "" + When the user sends a pull request with the chart + Then the pull request is not merged + And user gets the "" in the pull request comment + + @partners @smoke @full + Examples: + | vendor_type | vendor | chart_path | public_key_file | message | + | partners | hashicorp | tests/data/HC-10/signed_chart/vault-0.17.0.tgz | tests/data/HC-10/signed_chart/public_key_bad.asc | Signature verification failed | + + # @redhat @full + # Examples: + # | vendor_type | vendor | chart_path | public_key_file | message | + # | redhat | redhat | tests/data/HC-10/signed_chart/vault-0.17.0.tgz | tests/data/HC-10/signed_chart/public_key_bad.asc | Signature verification failed | + + diff --git a/tests/functional/behave_features/common/utils/chart_certification.py b/tests/functional/behave_features/common/utils/chart_certification.py index b56079b9c3..f56b67e2b5 100644 --- a/tests/functional/behave_features/common/utils/chart_certification.py +++ b/tests/functional/behave_features/common/utils/chart_certification.py @@ -655,7 +655,7 @@ def check_pull_request_comments(self, expect_message: str): response = json.loads(r.text) logging.debug(f"CHECK PULL_REQUEST COMMENT RESPONSE: {response}") if len(response) == 0: - raise AssertionError("No comment found in the PR") + raise AssertionError(f"No comment found in the PR {self.secrets.pr_number}") complete_comment = response[0]['body'] if expect_message in complete_comment: