diff --git a/.github/actions/setup-python/action.yml b/.github/actions/setup-python/action.yml new file mode 100644 index 00000000..9c917b09 --- /dev/null +++ b/.github/actions/setup-python/action.yml @@ -0,0 +1,10 @@ +name: Setup Python +description: | + Consistently installs python across this project. + Should be used as a replacement for direct calls to actions/setup-python. +runs: + using: composite + steps: + - uses: actions/setup-python@v5 + with: + python-version: '3.10' \ No newline at end of file diff --git a/.github/workflows/behave.yml b/.github/workflows/behave.yml new file mode 100644 index 00000000..bfbc2bb0 --- /dev/null +++ b/.github/workflows/behave.yml @@ -0,0 +1,140 @@ +name: Behave Testing + +# Behave Testing will run the repository's Behave testing with each feature file +# getting its own runner. All feature files found within the specific path are +# included. + +on: + workflow_call: + inputs: + tags: + type: string + required: true + description: | + The behave tags to use. E.g "full". Multiple tags should be specified + separated by a comma, e.g. "owners,redhat". + pr-body: + type: string + required: true + description: | + Every pull request created by this automation will have this pr-body. + behave-logging-level: + type: string + required: false + default: WARNING + description: | + Value passed to behave's --logging-level flag. + # actions/checkout related inputs used for testing. In some cases behave + # calls will use the PR branch instead of the main branch. E.g. went doing + # release testing + checkout-fetch-depth: + type: number + required: false + default: 1 # aligns with actions/checkout default. + description: | + fetch-depth flag to actions/checkout. + + If setting to a pull request, caller is responsible + for verifying the user is a trusted user. + checkout-repository: + type: string + required: false + default: "" + description: | + repository flag to actions/checkout + + If setting to a pull request, caller is responsible + for verifying the user is a trusted user. + checkout-ref: + type: string + required: false + default: "" + description: | + ref flag to actions/checkout + + If setting to a pull request, caller is responsible + for verifying the user is a trusted user. + secrets: + # NOTE(komish): Not technically secret, but must be listed as a secret + # because you can't pass the ${{ secrets }} context as an input in the + # calling workflow, and our repos have this configured as a secret. + bot-name: + required: true + description: | + The name of the GitHub user that will send pull requests. + bot-token: + description: | + A GitHub token for the bot user that will initiate pull + requests for testing. Should NOT be set to GITHUB_TOKEN. + required: true +jobs: + get-features: + runs-on: ubuntu-latest + outputs: + features: ${{ steps.find-features.outputs.features }} + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + ref: ${{ inputs.checkout-ref }} + repository: ${{ inputs.checkout-repository }} + fetch-depth: ${{ inputs.checkout-fetch-depth }} + - name: find features + id: find-features + # TODO(JOSE) Sanity check - make sure this is more than one feature in length. + run: | + cd tests/functional/behave_features + # echo features=$(find . -name '*.feature' | sed -e 's%\./%%g' | jq -R -s -c 'split("\n") | del(.[] | select(length == 0))') | tee -a $GITHUB_OUTPUT + # NOTE(JOSE): temporarily restrict this to a small number of tests for debugging other things. + # To Revert: remove the next line, and uncomment the line previous this comment. + echo features=$(find . -name '*.feature' | sed -e 's%\./%%g' | jq -R -s -c 'split("\n") | del(.[] | select(length == 0))[:2]') | tee -a $GITHUB_OUTPUT + run-tests: + runs-on: ubuntu-latest + needs: [get-features] + strategy: + fail-fast: false + max-parallel: 4 + matrix: + feature-file: ${{ fromJson(needs.get-features.outputs.features) }} + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + with: + token: ${{ secrets.bot-token }} + ref: ${{ inputs.checkout-ref }} + repository: ${{ inputs.checkout-repository }} + fetch-depth: ${{ inputs.checkout-fetch-depth }} + + - name: Set up Python 3 + uses: ./.github/actions/setup-python + + - name: Set up CI scripts + run: | + # set up python scripts + echo "set up python script in $PWD" + python3 -m venv ve1 + cd scripts + ../ve1/bin/pip3 install -r requirements.txt + ../ve1/bin/pip3 install . + cd .. + + # Pull request numbers are included in generated chart names in E2E, so it's included + # as an environment variable which E2E consumes. + - name: Populate PR_NUMBER environment variable + if: github.event_name == 'pull_request_target' || github.event_name == 'pull_request' + run: | + echo "PR_NUMBER=${{ github.event.pull_request.number }}" | tee $GITHUB_ENV + + - name: Run Tests + env: + GITHUB_TOKEN: ${{ secrets.github-token }} + BOT_NAME: ${{ secrets.bot-name }} + BOT_TOKEN: ${{ secrets.bot-token }} + PR_BODY: ${{ inputs.pr-body }} + run: | + ve1/bin/behave tests/functional/behave_features/ \ + --include ${{ matrix.feature-file }} \ + --tags=${{ inputs.tags }} \ + --logging-level=${{ inputs.behave-logging-level }} \ + --no-capture \ + --no-color \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 11a79c3c..da879b9f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -392,7 +392,7 @@ jobs: # The token we use for this changes for the Sandbox repository because the sandbox repository # receives PRs from the openshift-helm-charts-bot, and that same bot cannot approve its own # PRs which breaks workflows. Instead, for the Sandbox repo, we approve with the GHA bot. - github-token: ${{ github.repository == 'openshift-helm-charts/sandbox' && secrets.GITHUB_TOKEN || secrets.BOT_TOKEN }} + github-token: ${{ github.repository == 'practice-room/sandbox' && secrets.GITHUB_TOKEN || secrets.BOT_TOKEN }} - name: Merge PR id: merge_pr diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d2b6b9d2..26cf05c5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,28 +1,13 @@ name: Test Workflow +# Test Workflow is executed when workflow changes are being made. For +# development repos, this is testing every workflow modification For other +# repos, this is effectively testing release PRs to development as workflow +# changes are not made directly to those repos. + on: pull_request_target: types: [opened, synchronize, reopened] - workflow_dispatch: - inputs: - dry-run: - description: "Run tests but do not create issues {true,false}" - required: true - default: "true" - vendor-type: - description: "Vendor type {all,partner,redhat,community}" - required: true - default: "all" - software-name: - description: "Software Name" - required: true - software-version: - description: "Software Version" - required: true - notify-id: - description: "(Optional) Issue notification {github id}" - required: false - default: "" jobs: check-contributor: @@ -31,13 +16,17 @@ jobs: with: user: ${{ github.event.pull_request.user.login }} - workflow-test: + determine-workflow-conditions: name: Workflow Test needs: [check-contributor] runs-on: ubuntu-22.04 if: | github.event.pull_request.draft == false && needs.check-contributor.outputs.is-repo-owner == 'true' + outputs: + run-tests: ${{ steps.check_request.outputs.run-tests }} + is-charts-release-branch: ${{ steps.check_if_release_pr.outputs.charts_release_branch }} + test-tags: ${{ needs.determine-workflow-conditions.outputs.test-tags }} steps: - name: Checkout uses: actions/checkout@v4 @@ -67,14 +56,8 @@ jobs: BOT_TOKEN: ${{ secrets.BOT_TOKEN }} run: | # check if workflow testing should run. - echo "Request type: '$GITHUB_EVENT_NAME'" - if [ "$GITHUB_EVENT_NAME" == "pull_request_target" ]; then - echo "[INFO] check if PR contains only workflow changes and user is authorized" - ve1/bin/check-pr-for-ci --verify-user=${{ github.event.pull_request.user.login }} --api-url=${{ github.event.pull_request._links.self.href }} - else - echo "[INFO] manual invocation - check if user is authorized" - ve1/bin/check-pr-for-ci --verify-user=${{ github.actor }} - fi + echo "[INFO] check if PR contains only workflow changes and user is authorized" + ve1/bin/check-pr-for-ci --verify-user=${{ github.event.pull_request.user.login }} --api-url=${{ github.event.pull_request._links.self.href }} - name: Check Request Result id: check_request_result @@ -84,6 +67,11 @@ jobs: # workflow only change but user not authorized exit 1 + # BUG: This task attempts to run the `full` behave tag if the PR under + # test is a release from dev to prod, but the matcher condition that would + # emit this appears broken. Investigate the setting of the + # charts_release_branch output, or just run smoke tests and remove the + # condition associated with this output. - name: (PR) check for release flow id: check_if_release_pr if: | @@ -104,60 +92,61 @@ jobs: --pr_base_repo='${{ github.event.pull_request.base.repo.full_name }}' \ --pr_head_repo='${{ github.event.pull_request.head.repo.full_name }}' - - name: (PR) Test CI Workflow - if: | - github.event_name == 'pull_request_target' && steps.check_request.outputs.run-tests == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BOT_NAME: ${{ secrets.BOT_NAME }} - BOT_TOKEN: ${{ secrets.BOT_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - PR_BODY: "Test triggered by ${{ github.event.pull_request.html_url }}." + - name: Determine test tags + id: determine-test-tags + if: steps.check_request.outputs.run-tests == 'true' run: | echo "Full test in pr : ${{ steps.check_request.outputs.full_tests_in_pr }}" - if ${{steps.check_if_release_pr.outputs.charts_release_branch == 'true' || steps.check_request.outputs.full_tests_in_pr == 'true' }} ; then - echo "Release PR from dev to charts, oer PR with new full test, so running full tests" - ve1/bin/behave tests/functional/behave_features/ --tags=full --logging-level=WARNING --no-capture --no-color - else - echo "Not a release PR from dev to charts, so running only smoke tests" - ve1/bin/behave tests/functional/behave_features/ --tags=smoke --logging-level=WARNING --no-capture --no-color + echo "Is charts release branch : ${{ steps.check_request.outputs.full_tests_in_pr }}" + if ${{ steps.check_if_release_pr.outputs.charts_release_branch == 'true' || steps.check_request.outputs.full_tests_in_pr == 'true' }} ; then + echo "Release PR from dev to charts, or PR with new full test, so running full tests" + echo "test-tags=full" | tee -a $GITHUB_OUTPUT + exit 0 fi - - name: (Manual) Test CI Workflow - if: | - github.event_name == 'workflow_dispatch' && steps.check_request.outputs.run-tests == 'true' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DRY_RUN: ${{ github.event.inputs.dry-run }} - VENDOR_TYPE: ${{ github.event.inputs.vendor-type }} - NOTIFY_ID: ${{ github.event.inputs.notify-id }} - SOFTWARE_NAME: ${{ github.event.inputs.software-name }} - SOFTWARE_VERSION: ${{ github.event.inputs.software-version }} - BOT_NAME: ${{ secrets.BOT_NAME }} - BOT_TOKEN: ${{ secrets.BOT_TOKEN }} - PR_BODY: "Triggerd by ${{ github.event.sender.html_url }} from ${{ github.event.repository.html_url }} on `${{ github.event.ref }}`." - run: | - echo "[INFO] Dry run '${{ env.DRY_RUN }}'" - echo "[INFO] Vendor type '${{ env.VENDOR_TYPE }}'" - echo "[INFO] Notify ID '${{ env.NOTIFY_ID }}'" - echo "[INFO] Software Name '${{ env.SOFTWARE_NAME }}'" - echo "[INFO] Software Version '${{ env.SOFTWARE_VERSION }}'" - ve1/bin/behave tests/functional/behave_features/ --tags=version-change --logging-level=WARNING --no-capture --no-color - - - name: Approve PR - id: approve_pr - if: ${{ steps.check_if_release_pr.outputs.charts_release_branch == 'true' }} - uses: hmarr/auto-approve-action@v4 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} + echo "Not a release PR from dev to charts, so running only smoke tests" + echo "test-tags=smoke" | tee -a $GITHUB_OUTPUT + + run-tests: + # No further pull request author checking done here because + # check-contributor gates the needed jobs. + name: Run Tests + needs: + - determine-workflow-conditions + if: | + needs.determine-workflow-conditions.outputs.run-tests == 'true' + uses: ./.github/workflows/behave.yml + with: + tags: ${{ needs.determine-workflow-conditions.outputs.test-tags }} + behave-logging-level: WARNING + pr-body: "Test triggered by release PR ${{ github.event.pull_request.html_url }}." + # checkout parameters passed to ensure we're testing the release content + checkout-fetch-depth: 0 + checkout-repository: ${{ github.event.pull_request.head.repo.full_name }} + checkout-ref: ${{ github.event.pull_request.head.sha }} + secrets: + bot-name: ${{ secrets.BOT_NAME }} + bot-token: ${{ secrets.BOT_TOKEN }} - - name: Merge PR - id: merge_pr - if: ${{ steps.check_if_release_pr.outputs.charts_release_branch == 'true' }} - uses: pascalgn/automerge-action@v0.16.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - MERGE_METHOD: squash - MERGE_LABELS: "" + approve-and-merge: + name: Approve and merge + needs: + - determine-workflow-conditions + - run-tests + runs-on: ubuntu-22.04 + if: needs.determine-workflow-conditions.outputs.is-charts-release-branch == 'true' + steps: + - name: Approve PR + id: approve_pr + uses: hmarr/auto-approve-action@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Merge PR + id: merge_pr + uses: pascalgn/automerge-action@v0.16.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MERGE_METHOD: squash + MERGE_LABELS: "" diff --git a/scripts/src/release/releaser.py b/scripts/src/release/releaser.py index 8d0fcc81..24e1b1e0 100644 --- a/scripts/src/release/releaser.py +++ b/scripts/src/release/releaser.py @@ -12,8 +12,6 @@ Performs these action. - Gets a list of updates to perform from the pr_dir releases/release_info.json file. These updates are then made to the charts and development repositories. -- Adds the cron job to .github/worklfows/schedule.yml and changes the verifier image used in .github/worklfows/schedule.yml - to latest, as required. The charts repo is updated from development repo which necessitates these update. - Create a PR against the charts repo containing the workflow updates. This requires manual merge. - Directly commits to the development main branch any new charts added to the charts repo since the last update. @@ -30,7 +28,7 @@ sys.path.append("../") from tools import gitutils -VERSION_CHECK_YAML_FILE = ".github/workflows/version_check.yml" +# VERSION_CHECK_YAML_FILE = ".github/workflows/version_check.yml" BUILD_YAML_FILE = ".github/workflows/build.yml" DEV_PR_BRANCH_BODY_PREFIX = "Charts workflow version" DEV_PR_BRANCH_NAME_PREFIX = "Auto-Release-" @@ -39,30 +37,31 @@ STAGE_PR_BRANCH_BODY_PREFIX = "Workflow and script updates from development repository" STAGE_PR_BRANCH_NAME_PREFIX = "Release-" -SCHEDULE_INSERT = [ - " # Daily trigger to check updates", - " schedule:", - ' - cron: "0 0 * * *"', -] - - -def update_workflow(): - lines = [] - with open(VERSION_CHECK_YAML_FILE, "r") as schedule_file: - lines = schedule_file.readlines() - - for line in lines: - if line.strip() == "on:": - insert_location = lines.index(line) + 1 - if SCHEDULE_INSERT[0] not in lines[insert_location].rstrip(): - print("[INFO] add cron job to schedule.yaml") - lines.insert(insert_location, f"{SCHEDULE_INSERT[0]}\n") - lines.insert(insert_location + 1, f"{SCHEDULE_INSERT[1]}\n") - lines.insert(insert_location + 2, f"{SCHEDULE_INSERT[2]}\n") - break - - with open(VERSION_CHECK_YAML_FILE, "w") as schedule_file: - schedule_file.write("".join(lines)) +# SCHEDULE_INSERT = [ +# " # Daily trigger to check updates", +# " schedule:", +# ' - cron: "0 0 * * *"', +# ] + +# (JOSE) Marked for removal. This function (and call locations) modify +# version_check.yml which will be removed. +# def update_workflow(): +# lines = [] +# with open(VERSION_CHECK_YAML_FILE, "r") as schedule_file: +# lines = schedule_file.readlines() + +# for line in lines: +# if line.strip() == "on:": +# insert_location = lines.index(line) + 1 +# if SCHEDULE_INSERT[0] not in lines[insert_location].rstrip(): +# print("[INFO] add cron job to schedule.yaml") +# lines.insert(insert_location, f"{SCHEDULE_INSERT[0]}\n") +# lines.insert(insert_location + 1, f"{SCHEDULE_INSERT[1]}\n") +# lines.insert(insert_location + 2, f"{SCHEDULE_INSERT[2]}\n") +# break + +# with open(VERSION_CHECK_YAML_FILE, "w") as schedule_file: +# schedule_file.write("".join(lines)) def make_required_changes(release_info_dir, origin, destination): @@ -208,7 +207,9 @@ def main(): print("edit files in charts") os.chdir(args.charts_dir) - update_workflow() + # (JOSE) Marked for removal. This function (and call locations) modify + # version_check.yml which will be removed. + # update_workflow() organization = args.target_repository.split("/")[0] charts_repository = f"{organization}{gitutils.CHARTS_REPO}" diff --git a/tests/functional/behave_features/common/utils/chart_certification.py b/tests/functional/behave_features/common/utils/chart_certification.py index 15c2491c..728753fb 100644 --- a/tests/functional/behave_features/common/utils/chart_certification.py +++ b/tests/functional/behave_features/common/utils/chart_certification.py @@ -962,6 +962,7 @@ def cleanup_release(self): super().cleanup_release(expected_tag) +# (jose) mark for deletion - this class isn't necessary if it's only used by version-change. @dataclass class ChartCertificationE2ETestMultiple(ChartCertificationE2ETest): secrets: E2ETestSecretRecursive = E2ETestSecretRecursive() diff --git a/tests/functional/behave_features/common/utils/env.py b/tests/functional/behave_features/common/utils/env.py index 494e9a4c..e910d345 100644 --- a/tests/functional/behave_features/common/utils/env.py +++ b/tests/functional/behave_features/common/utils/env.py @@ -5,7 +5,7 @@ from common.utils.setttings import * - +# (jose): this can stay def get_bot_name_and_token(): bot_name = os.environ.get("BOT_NAME") logging.debug(f"Enviroment variable value BOT_NAME: {bot_name}") @@ -21,47 +21,50 @@ def get_bot_name_and_token(): raise Exception("BOT_NAME set but BOT_TOKEN not specified") return bot_name, bot_token + +# (jose) mark for deletion +# def get_dry_run(): +# # Accepts 'true' or 'false', depending on whether we want to notify +# # Don't notify on dry runs, default to True +# dry_run = False if os.environ.get("DRY_RUN") == "false" else True +# # Don't notify if not triggerd on PROD_REPO and PROD_BRANCH +# if not dry_run: +# triggered_branch = os.environ.get("GITHUB_REF").split("/")[-1] +# triggered_repo = os.environ.get("GITHUB_REPOSITORY") +# if triggered_repo != PROD_REPO or triggered_branch != PROD_BRANCH: +# dry_run = True +# return dry_run -def get_dry_run(): - # Accepts 'true' or 'false', depending on whether we want to notify - # Don't notify on dry runs, default to True - dry_run = False if os.environ.get("DRY_RUN") == "false" else True - # Don't notify if not triggerd on PROD_REPO and PROD_BRANCH - if not dry_run: - triggered_branch = os.environ.get("GITHUB_REF").split("/")[-1] - triggered_repo = os.environ.get("GITHUB_REPOSITORY") - if triggered_repo != PROD_REPO or triggered_branch != PROD_BRANCH: - dry_run = True - return dry_run - - -def get_notify_id(): - # Accepts comma separated Github IDs or empty strings to override people to tag in notifications - notify_id = os.environ.get("NOTIFY_ID") - if notify_id: - notify_id = [vt.strip() for vt in notify_id.split(",")] - else: - notify_id = ["dperaza", "mmulholla"] - return notify_id - +# (jose) mark for deletion +# def get_notify_id(): +# # Accepts comma separated Github IDs or empty strings to override people to tag in notifications +# notify_id = os.environ.get("NOTIFY_ID") +# if notify_id: +# notify_id = [vt.strip() for vt in notify_id.split(",")] +# else: +# notify_id = ["dperaza", "mmulholla"] +# return notify_id -def get_software_name_version(): - software_name = os.environ.get("SOFTWARE_NAME") - if not software_name: - raise Exception("SOFTWARE_NAME environment variable not defined") - software_version = os.environ.get("SOFTWARE_VERSION").strip('"') - if not software_version: - raise Exception("SOFTWARE_VERSION environment variable not defined") - elif software_version.startswith("sha256"): - software_version = software_version[-8:] +# (jose) mark for deletion +# def get_software_name_version(): +# software_name = os.environ.get("SOFTWARE_NAME") +# if not software_name: +# raise Exception("SOFTWARE_NAME environment variable not defined") - return software_name, software_version +# software_version = os.environ.get("SOFTWARE_VERSION").strip('"') +# if not software_version: +# raise Exception("SOFTWARE_VERSION environment variable not defined") +# elif software_version.startswith("sha256"): +# software_version = software_version[-8:] +# return software_name, software_version -def get_vendor_type(): - vendor_type = os.environ.get("VENDOR_TYPE") - if not vendor_type: - logging.info("VENDOR_TYPE environment variable not defined, default to `all`") - vendor_type = "all" - return vendor_type +# (jose) mark for deletion. note that there is another function with the same +# name that should remain and will continue to be used. +# def get_vendor_type(): +# vendor_type = os.environ.get("VENDOR_TYPE") +# if not vendor_type: +# logging.info("VENDOR_TYPE environment variable not defined, default to `all`") +# vendor_type = "all" +# return vendor_type diff --git a/tests/functional/behave_features/common/utils/setttings.py b/tests/functional/behave_features/common/utils/setttings.py index b3e9c64e..c1d56181 100644 --- a/tests/functional/behave_features/common/utils/setttings.py +++ b/tests/functional/behave_features/common/utils/setttings.py @@ -3,9 +3,9 @@ GITHUB_BASE_URL = "https://api.github.com" # The sandbox repository where we run all our tests on -TEST_REPO = "openshift-helm-charts/sandbox" +TEST_REPO = "practice-room/sandbox" # The prod repository where we create notification issues -PROD_REPO = "openshift-helm-charts/charts" +PROD_REPO = "practice-room/charts" # The prod branch where we store all chart files PROD_BRANCH = "main" # (Deprecated) This is used to find chart certification workflow run id diff --git a/tests/functional/behave_features/environment.py b/tests/functional/behave_features/environment.py index 433198f4..0b6a1924 100644 --- a/tests/functional/behave_features/environment.py +++ b/tests/functional/behave_features/environment.py @@ -11,6 +11,7 @@ def workflow_test(context): context.workflow_test.cleanup() +# (jose) mark for deletion - this fixture only seems like it's used in version change scenarios @fixture def submitted_chart_test(context): context.chart_test = ChartCertificationE2ETestMultiple() @@ -25,6 +26,7 @@ def owners_file_test(context): def before_scenario(context, scenario): context.test_name = scenario.name.split("@")[0][:-4].split("]")[1] + # mark for deletion - this version-change tag will go away. if "version-change" in scenario.tags: print("[INFO] Using submitted charts fixture") use_fixture(submitted_chart_test, context)