diff --git a/.github/workflows/run_one_use_cases_example.yaml b/.github/workflows/run_one_use_cases_example.yaml new file mode 100644 index 000000000..b3a6f6ed3 --- /dev/null +++ b/.github/workflows/run_one_use_cases_example.yaml @@ -0,0 +1,140 @@ +name: Run One Use Case Example +on: + workflow_dispatch: + inputs: + use_case: + # --- refresh_use_cases_list.py: refresh list of use cases currently available [START] --- + # --- do not edit, auto generated part by `make refresh_use_cases_list` --- + - cifar/cifar_brevitas_finetuning + - cifar/cifar_brevitas_training + - credit_scoring + - disease_prediction + - federated_learning + - hybrid_model + - llm + - sentiment_analysis_with_transformer + - titanic + # --- refresh_use_cases_list.py: refresh list of use cases currently available [END] --- + required: true + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +env: + ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + +jobs: + start-runner-linux: + name: Start EC2 runner + runs-on: ubuntu-20.04 + outputs: + label-38: ${{ steps.start-ec2-runner-38.outputs.label }} + ec2-instance-id-38: ${{ steps.start-ec2-runner-38.outputs.ec2-instance-id || '' }} + steps: + - name: Checkout Code + uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@8c3f20df09ac63af7b3ae3d7c91f105f857d8497 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Start EC2 runner python 38 + id: start-ec2-runner-38 + uses: machulav/ec2-github-runner@4e0303de215db88e1c489e07a15ca4d867f488ea + with: + mode: start + github-token: ${{ secrets.EC2_RUNNER_BOT_TOKEN }} + ec2-image-id: ${{ secrets.AWS_EC2_AMI }} + ec2-instance-type: "m6i.metal" + subnet-id: ${{ secrets.AWS_EC2_SUBNET_ID }} + security-group-id: ${{ secrets.AWS_EC2_SECURITY_GROUP_ID }} + + run-use-case-examples: + needs: [start-runner-linux] + runs-on: ${{ needs.start-runner-linux.outputs.label-38 }} + container: + image: ubuntu:20.04 + defaults: + run: + shell: bash + steps: + + - name: Set up Environment + run: | + # Setup commands if any, for example, installing dependencies, etc. + apt-get update && apt-get install -y python3-venv make git git-lfs binutils + + - name: Checkout Code + uses: actions/checkout@v4 + with: + lfs: true + + - name: Run One Use Case Example Script + run: | + make run_one_use_case_example USE_CASE=${{ github.event.inputs.use_case }} + + stop-runner-linux: + name: Stop EC2 runner + needs: [run-use-case-examples, start-runner-linux] + runs-on: ubuntu-20.04 + if: ${{ always() && (needs.start-runner-linux.result != 'skipped') }} + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@8c3f20df09ac63af7b3ae3d7c91f105f857d8497 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Stop EC2 runner python 38 + uses: machulav/ec2-github-runner@4e0303de215db88e1c489e07a15ca4d867f488ea + if: ${{ always() && needs.start-runner-linux.outputs.ec2-instance-id-38 }} + with: + github-token: ${{ secrets.EC2_RUNNER_BOT_TOKEN }} + label: ${{ needs.start-runner-linux.outputs.label-38 }} + ec2-instance-id: ${{ needs.start-runner-linux.outputs.ec2-instance-id-38 }} + mode: stop + + send-report: + if: ${{ always() }} + needs: + [ + start-runner-linux, + run-use-case-examples, + stop-runner-linux, + ] + name: Send Slack notification + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 + + - name: Prepare whole job status + if: ${{ always() }} + continue-on-error: true + env: + NEEDS_JSON: ${{ toJSON(needs) }} + run: | + echo "${NEEDS_JSON}" > /tmp/needs_context.json + JOB_STATUS=$(python3 ./script/actions_utils/actions_combine_status.py \ + --needs_context_json /tmp/needs_context.json) + echo "JOB_STATUS=${JOB_STATUS}" >> "$GITHUB_ENV" + + - name: Slack Notification + if: ${{ always() }} + continue-on-error: true + uses: rtCamp/action-slack-notify@b24d75fe0e728a4bf9fc42ee217caa686d141ee8 + env: + SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }} + SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png + SLACK_COLOR: ${{ env.JOB_STATUS || 'failure' }} + SLACK_MESSAGE: "Full run of use case examples finished with status ${{ env.JOB_STATUS || 'failure' }} \ + (${{ env.ACTION_RUN_URL }})\n\ + - start-runner-linux: ${{ needs.start-runner-linux.result || 'Did not run.'}}\n\n\ + - run-use-case-examples: ${{ needs.run-use-case-examples.result || 'Did not run.' }}\n\n\ + - stop-runner-linux: ${{ needs.stop-runner-linux.result || 'Did not run.'}}" + SLACK_USERNAME: ${{ secrets.BOT_USERNAME }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} \ No newline at end of file diff --git a/.github/workflows/run_use_cases_examples.yaml b/.github/workflows/run_use_cases_examples.yaml index db1c23fd8..3d0f55a4a 100644 --- a/.github/workflows/run_use_cases_examples.yaml +++ b/.github/workflows/run_use_cases_examples.yaml @@ -54,7 +54,7 @@ jobs: apt-get update && apt-get install -y python3-venv make git git-lfs binutils - name: Checkout Code - uses: actions/checkout@v4 + uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 with: lfs: true @@ -95,7 +95,7 @@ jobs: name: Send Slack notification runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 # Update to the latest stable version + - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 - name: Prepare whole job status if: ${{ always() }} diff --git a/Makefile b/Makefile index 243bd4484..6b626affe 100644 --- a/Makefile +++ b/Makefile @@ -191,7 +191,7 @@ spcc: PCC_DEPS := check_python_format check_finalize_nb python_linting mypy_ci pydocstyle shell_lint PCC_DEPS += check_version_coherence check_licenses check_nbqa check_supported_ops -PCC_DEPS += check_refresh_notebooks_list check_mdformat +PCC_DEPS += check_refresh_notebooks_list check_refresh_use_cases_list check_mdformat PCC_DEPS += check_unused_images check_utils_use_case gitleaks .PHONY: pcc_internal @@ -532,10 +532,18 @@ jupyter_execute_parallel: refresh_notebooks_list: poetry run python script/actions_utils/refresh_notebooks_list.py .github/workflows/refresh-one-notebook.yaml +.PHONY: refresh_use_cases_list # Refresh the list of use cases currently available +refresh_use_cases_list: + poetry run python script/actions_utils/refresh_use_cases_list.py .github/workflows/run_one_use_cases_example.yaml + .PHONY: check_refresh_notebooks_list # Check if the list of notebooks currently available hasn't change check_refresh_notebooks_list: poetry run python script/actions_utils/refresh_notebooks_list.py .github/workflows/refresh-one-notebook.yaml --check +.PHONY: check_refresh_use_cases_list # Check if the list of use cases currently available hasn't change +check_refresh_use_cases_list: + poetry run python script/actions_utils/refresh_use_cases_list.py .github/workflows/run_one_use_cases_example.yaml --check + .PHONY: release_docker # Build a docker release image release_docker: EV_FILE="$$(mktemp tmp.docker.XXXX)" && \ @@ -825,7 +833,7 @@ clean_pycache: clean_sklearn_cache: rm -rf ~/scikit_learn_data -.PHONY: run_one_use_case_example # Run one use-case example (USE_CASE, eg use_case_examples/hybrid_model) +.PHONY: run_one_use_case_example # Run one use-case example (USE_CASE, e.g. hybrid_model) run_one_use_case_example: USE_CASE=$(USE_CASE) ./script/make_utils/run_use_case_examples.sh diff --git a/script/actions_utils/refresh_use_cases_list.py b/script/actions_utils/refresh_use_cases_list.py new file mode 100644 index 000000000..48cdd9f9c --- /dev/null +++ b/script/actions_utils/refresh_use_cases_list.py @@ -0,0 +1,107 @@ +import argparse +from pathlib import Path + +SCRIPT_NAME = Path(__file__).name +USE_CASES_DIRS = [Path(r"use_case_examples")] + +SMALL_TAB = " " +TAB = SMALL_TAB * 4 + +# Headers for updating sections +SECTION_HEADERS = { + "use_cases": { + "start": TAB + + f"# --- {SCRIPT_NAME}: refresh list of use cases currently available [START] ---", + "end": TAB + + f"# --- {SCRIPT_NAME}: refresh list of use cases currently available [END] ---", + }, + "paths": { + "start": SMALL_TAB + + f"# --- {SCRIPT_NAME}: refresh list of use case paths currently available [START] ---", + "end": SMALL_TAB + + f"# --- {SCRIPT_NAME}: refresh list of use case paths currently available [END] ---", + }, +} + + +def no_edit_message(indent): + """Generates no edit message with appropriate indentation.""" + return indent + "# --- do not edit, auto generated part by `make refresh_use_cases_list` ---\n" + + +def find_use_cases_with_makefile(): + """Finds directories containing a Makefile, indicating a valid use case.""" + use_case_paths = [] + for use_cases_dir in USE_CASES_DIRS: + for use_cases_path in use_cases_dir.rglob("*"): + if use_cases_path.is_dir() and (use_cases_path / "Makefile").exists(): + relative_path = Path(*use_cases_path.parts[1:]) + use_case_paths.append(relative_path) + use_case_paths.sort(key=lambda path: path.as_posix().lower()) + return use_case_paths + + +def update_section(lines, start_header, end_header, new_content, indent): + """Updates specific sections of the file with new content between start and end headers.""" + new_lines = [] + in_update_section = False + + for line in lines: + if line.startswith(start_header): + in_update_section = True + new_lines.append(line) + new_lines.append(no_edit_message(indent)) + new_lines.extend(new_content) + elif line.startswith(end_header): + in_update_section = False + new_lines.append(line) + elif not in_update_section: + new_lines.append(line) + + return new_lines + + +def main(file_to_update, check_mode=False): + use_case_paths = find_use_cases_with_makefile() + + with open(file_to_update, "r", encoding="utf-8") as file: + lines = file.readlines() + + # Update use case descriptions + use_cases_content = [TAB + f"- {path.as_posix()}\n" for path in use_case_paths] + lines = update_section( + lines, + SECTION_HEADERS["use_cases"]["start"], + SECTION_HEADERS["use_cases"]["end"], + use_cases_content, + TAB, + ) + + # Update use case paths + use_case_paths_content = [ + SMALL_TAB + f'{path.stem}: "{path.as_posix()}"\n' for path in use_case_paths + ] + lines = update_section( + lines, + SECTION_HEADERS["paths"]["start"], + SECTION_HEADERS["paths"]["end"], + use_case_paths_content, + SMALL_TAB, + ) + + if check_mode: + with open(file_to_update, "r", encoding="utf-8") as file: + assert ( + file.readlines() == lines + ), "List of use cases is not up to date. Please run `make refresh_use_cases_list`." + else: + with open(file_to_update, "w", encoding="utf-8") as file: + file.writelines(lines) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Update list of currently available use cases") + parser.add_argument("--check", action="store_true", help="flag to enable just checking mode") + parser.add_argument("file_to_update", type=str, help=".yaml file to update") + args = parser.parse_args() + main(args.file_to_update, check_mode=args.check)