diff --git a/.github/workflows/pr-review.yaml b/.github/workflows/pr-review.yaml new file mode 100644 index 000000000..a611cb9fd --- /dev/null +++ b/.github/workflows/pr-review.yaml @@ -0,0 +1,180 @@ +name: Review Pull Request + +on: + pull_request_target: + types: [ opened, synchronize, edited, labeled, unlabeled ] + branches: + - main + - develop + +permissions: + pull-requests: write + contents: read + +# Use concurrency to ensure that only one instance of this workflow is running at a time +concurrency: + group: pr-lint-checker-${{ github.sha }} + cancel-in-progress: true + +jobs: + load-config: + uses: AibelDevs/action-toolbox/.github/workflows/tool-load-config.yaml@main + + pr-review: + uses: AibelDevs/action-toolbox/.github/workflows/tool-review-pr.yaml@main + needs: load-config + secrets: + SOURCE_KEY: ${{ secrets.SOURCE_KEY }} + + python-review: + uses: AibelDevs/action-toolbox/.github/workflows/tool-review-python.yaml@main + with: + use_pylint: false + needs: load-config + if : ${{ needs.load-config.outputs.python_enabled == 'true' }} + secrets: + CONDA_API_TOKEN: ${{ secrets.CONDA_API_TOKEN }} + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + CUSTOM_PYPI_USERNAME: ${{ secrets.CUSTOM_PYPI_USERNAME }} + CUSTOM_PYPI_PASSWORD: ${{ secrets.CUSTOM_PYPI_PASSWORD }} + CUSTOM_PYPI_URL: ${{ secrets.CUSTOM_PYPI_URL }} + QUETZ_API_KEY: ${{ secrets.QUETZ_API_KEY }} + QUETZ_URL: ${{ secrets.QUETZ_URL }} + + docker-review: + uses: AibelDevs/action-toolbox/.github/workflows/tool-review-docker.yaml@main + needs: load-config + if : ${{ needs.load-config.outputs.docker_enabled == 'true' }} + secrets: + CONTAINER_REGISTRY_URL: ${{ secrets.CONTAINER_REGISTRY_URL }} + CONTAINER_REGISTRY_USERNAME: ${{ secrets.CONTAINER_REGISTRY_USERNAME }} + CONTAINER_REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + + gitops-review: + uses: AibelDevs/action-toolbox/.github/workflows/tool-review-gitops.yaml@main + needs: load-config + if : ${{ needs.load-config.outputs.gitops_enabled == 'true' }} + secrets: + CONTAINER_REGISTRY_URL: ${{ secrets.CONTAINER_REGISTRY_URL }} + SOURCE_KEY: ${{ secrets.SOURCE_KEY }} + GITOPS_KEY: ${{ secrets.GITOPS_KEY }} + + pr-review-summary: + name: Python Check Pull Request + needs: [ load-config, pr-review, python-review, docker-review, gitops-review ] + if: always() + runs-on: ubuntu-latest + steps: + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: 3.11 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install toml==0.10.2 + + - name: Checkout PR + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + + - name: Review all results and write a summary + id: comment_body + shell: python + run: | + import os + import base64 + + def set_output(name, value, encode_it=False): + if encode_it: + value = base64.b64encode(value.encode('utf-8')).decode('utf-8') + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + print(f'{name}={value}', file=fh) + + def deserialize_str(s): + return base64.b64decode(s).decode('utf-8') + + all_checks = [] + review_str = '' + + # PR + review_str += deserialize_str('${{ needs.pr-review.outputs.pr_review_str }}') + all_checks.append('${{ needs.pr-review.outputs.pr_review_ok }}' == 'true') + + # Python + if ${{ needs.load-config.outputs.python_enabled }}: + review_str += deserialize_str('${{ needs.python-review.outputs.python_review_str }}') + all_checks.append('${{ needs.python-review.outputs.python_review_ok }}' == 'true') + + # Docker + if ${{ needs.load-config.outputs.docker_enabled }}: + review_str += deserialize_str('${{ needs.docker-review.outputs.docker_review_str }}') + all_checks.append('${{ needs.docker-review.outputs.docker_review_ok }}' == 'true') + + # GitOps + if ${{ needs.load-config.outputs.gitops_enabled }}: + review_str += deserialize_str('${{ needs.gitops-review.outputs.gitops_review_str }}') + all_checks.append('${{ needs.gitops-review.outputs.gitops_review_ok }}' == 'true') + + # check if all_checks are true + if all(all_checks): + header = "👋 Hi there! I have checked your PR and found no issues. Thanks for your contribution!\n" + set_output('REVIEW_OK', 'true') + else: + header = "👋 Hi there! I have checked your PR and found the following:\n\n" + set_output('REVIEW_OK', 'false') + + body = header + review_str + + # Set the output + set_output('body', body, True) + + print(body) + + - name: Delete previous bot comment + uses: actions/github-script@v7 + with: + script: | + const issue_number = context.issue.number + const owner = context.repo.owner + const repo = context.repo.repo + + // List all comments on the PR + const comments = await github.rest.issues.listComments({ + issue_number, + owner, + repo + }); + + // Find the last comment made by the bot + const botComment = comments.data.reverse().find(comment => comment.user.login === 'github-actions[bot]'); + + // If a previous comment by the bot exists, delete it + if (botComment) { + await github.rest.issues.deleteComment({ + owner, + repo, + comment_id: botComment.id + }); + } + + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const bodyBase64 = '${{ steps.comment_body.outputs.body }}' + const body = Buffer.from(bodyBase64, 'base64').toString('utf-8') + + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: body + }) + + - name: Fail if linting failed + if: steps.comment_body.outputs.REVIEW_OK == 'false' + run: exit 1 \ No newline at end of file diff --git a/.github/workflows/pre-release-dispatch.yaml b/.github/workflows/pre-release-dispatch.yaml new file mode 100644 index 000000000..c9bf8a074 --- /dev/null +++ b/.github/workflows/pre-release-dispatch.yaml @@ -0,0 +1,127 @@ +name: Issue pre-release + +on: + workflow_dispatch: + inputs: + release_pypi: + description: 'Issue Pre-Release to PyPI' + required: false + type: boolean + default: false + release_conda: + description: 'Issue Pre-Release to conda' + required: false + type: boolean + default: false + release_docker: + description: 'Issue Pre-Release to docker' + required: false + type: boolean + default: true + release_gitops: + description: 'Issue Pre-Release to gitops' + required: false + type: boolean + default: true + + +permissions: + id-token: write + contents: write + pull-requests: read + +concurrency: + group: release-${{ github.sha }} + cancel-in-progress: true + +jobs: + load-config: + uses: AibelDevs/action-toolbox/.github/workflows/tool-load-config.yaml@main + + # Add a step to create a patch version "next.run_id" + bump_version: + runs-on: ubuntu-latest + outputs: + current_version: ${{ steps.get_current_version.outputs.current_version }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetches all history for all branches and tags + + - name: Create "next" version + id: get_current_version + shell: python + run: | + import os + + def set_output(name, value): + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + print(f'{name}={value}', file=fh) + + # get the latest tagged release + tag_release = os.popen('git describe --tags --abbrev=0').read().strip() + print(f"{tag_release=}") + + # strip v from the tag + if tag_release.startswith('v'): + tag_release = tag_release[1:] + + # add a pre-release "version-next.build_id" to the latest tagged release + build_id = os.getenv('GITHUB_RUN_NUMBER') + print(f"{build_id=}") + + # concatenate the version and build_id + current_version = f"{tag_release}-next.{build_id}" + + print(current_version) + set_output('current_version', current_version) + + release_pypi: + if: ${{ github.event.inputs.release_pypi == 'true' }} + needs: [ load-config, bump_version ] + uses: AibelDevs/action-toolbox/.github/workflows/tool-release-pip.yaml@main + with: + use_custom_pypi: ${{ needs.load-config.outputs.custom_pypi_server }} + version_override: ${{ needs.bump_version.outputs.current_version }} + secrets: + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + CUSTOM_PYPI_USERNAME: ${{ secrets.CUSTOM_PYPI_USERNAME }} + CUSTOM_PYPI_PASSWORD: ${{ secrets.CUSTOM_PYPI_PASSWORD }} + CUSTOM_PYPI_URL: ${{ secrets.CUSTOM_PYPI_URL }} + + release_conda: + if: ${{ github.event.inputs.release_conda == 'true' }} + needs: [ load-config, bump_version ] + uses: AibelDevs/action-toolbox/.github/workflows/tool-release-conda.yaml@main + with: + use_quetz_server: ${{ needs.load-config.outputs.custom_quetz_server }} + push_conda: "true" + version_override: ${{ needs.bump_version.outputs.current_version }} + secrets: + CONDA_API_TOKEN: ${{ secrets.CONDA_API_TOKEN }} + QUETZ_API_KEY: ${{ secrets.QUETZ_API_KEY }} + QUETZ_URL: ${{ secrets.QUETZ_URL }} + + release_docker: + if: ${{ github.event.inputs.release_docker == 'true' }} + needs: bump_version + uses: AibelDevs/action-toolbox/.github/workflows/tool-release-docker.yaml@main + with: + docker_tag_override: ${{ needs.bump_version.outputs.current_version }} + push_docker: "true" + secrets: + CONTAINER_REGISTRY_URL: ${{ secrets.CONTAINER_REGISTRY_URL }} + CONTAINER_REGISTRY_USERNAME: ${{ secrets.CONTAINER_REGISTRY_USERNAME }} + CONTAINER_REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + + release_gitops: + if: ${{ github.event.inputs.release_gitops == 'true' }} + needs: [bump_version, release_docker] + uses: AibelDevs/action-toolbox/.github/workflows/tool-release-gitops-update.yaml@main + with: + push_commit: "true" + release_tag_override: ${{ needs.bump_version.outputs.current_version }} + secrets: + CONTAINER_REGISTRY_URL: ${{ secrets.CONTAINER_REGISTRY_URL }} + GITOPS_KEY: ${{ secrets.GITOPS_KEY }} \ No newline at end of file diff --git a/.github/workflows/release-on-new-tag.yaml b/.github/workflows/release-on-new-tag.yaml new file mode 100644 index 000000000..c13d60abb --- /dev/null +++ b/.github/workflows/release-on-new-tag.yaml @@ -0,0 +1,86 @@ +name: Make Release on push of new tag + +# triggered on a tagged release +on: + push: + tags: + - 'v*.*.*' + +permissions: + id-token: write + contents: write + +jobs: + interpret_tag: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.interpret_tag.outputs.tagged_release }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Read and output tag (remove 'v' prefix) + id: interpret_tag + shell: python + run: | + import os + + def set_output(name, value): + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: + print(f'{name}={value}', file=fh) + + tag = os.getenv('GITHUB_REF').split('/')[-1] + if tag.startswith('v'): + tag = tag[1:] + + print(f"{tag=}") + set_output('tagged_release', tag) + + load-config: + uses: AibelDevs/action-toolbox/.github/workflows/tool-load-config.yaml@main + + release_pypi: + needs: [ load-config, interpret_tag ] + if: ${{ needs.load-config.outputs.pip_enabled == 'true' }} + uses: AibelDevs/action-toolbox/.github/workflows/tool-release-pip.yaml@main + with: + use_custom_pypi: ${{ needs.load-config.outputs.custom_pypi_server }} + secrets: + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + CUSTOM_PYPI_USERNAME: ${{ secrets.CUSTOM_PYPI_USERNAME }} + CUSTOM_PYPI_PASSWORD: ${{ secrets.CUSTOM_PYPI_PASSWORD }} + CUSTOM_PYPI_URL: ${{ secrets.CUSTOM_PYPI_URL }} + + release_conda: + needs: [ load-config, interpret_tag ] + if: ${{ needs.load-config.outputs.conda_enabled == 'true' }} + uses: AibelDevs/action-toolbox/.github/workflows/tool-release-conda.yaml@main + with: + use_quetz_server: ${{ needs.load-config.outputs.custom_quetz_server }} + push_conda: 'true' + secrets: + CONDA_API_TOKEN: ${{ secrets.CONDA_API_TOKEN }} + QUETZ_API_KEY: ${{ secrets.QUETZ_API_KEY }} + QUETZ_URL: ${{ secrets.QUETZ_URL }} + + release_docker: + needs: [ load-config, interpret_tag ] + if: ${{ needs.load-config.outputs.docker_enabled == 'true' }} + uses: AibelDevs/action-toolbox/.github/workflows/tool-release-docker.yaml@main + with: + push_docker: 'true' + docker_tag_override: ${{ needs.interpret_tag.outputs.version }} + secrets: + CONTAINER_REGISTRY_URL: ${{ secrets.CONTAINER_REGISTRY_URL }} + CONTAINER_REGISTRY_USERNAME: ${{ secrets.CONTAINER_REGISTRY_USERNAME }} + CONTAINER_REGISTRY_PASSWORD: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }} + + release_gitops: + needs: [ load-config, interpret_tag, release_docker ] + if: ${{ needs.load-config.outputs.gitops_enabled == 'true' }} + uses: AibelDevs/action-toolbox/.github/workflows/tool-release-gitops-update.yaml@main + with: + push_commit: 'true' + release_tag_override: ${{ needs.interpret_tag.outputs.version }} + secrets: + CONTAINER_REGISTRY_URL: ${{ secrets.CONTAINER_REGISTRY_URL }} + GITOPS_KEY: ${{ secrets.GITOPS_KEY }} \ No newline at end of file diff --git a/.github/workflows/tag-on-pr-merge.yaml b/.github/workflows/tag-on-pr-merge.yaml new file mode 100644 index 000000000..855c80e0e --- /dev/null +++ b/.github/workflows/tag-on-pr-merge.yaml @@ -0,0 +1,23 @@ +name: Create Tag on PR Merge + +on: + pull_request: + branches: + - main + types: [closed] + +permissions: + id-token: write + contents: write + pull-requests: read + +concurrency: + group: release-${{ github.sha }} + cancel-in-progress: true + +jobs: + release_if_ok: + if: github.event.pull_request.merged == true + uses: AibelDevs/action-toolbox/.github/workflows/tool-generate-semantic-version.yaml@main + secrets: + SOURCE_KEY: ${{ secrets.SOURCE_KEY }} diff --git a/action_config.toml b/action_config.toml new file mode 100644 index 000000000..0d055fae2 --- /dev/null +++ b/action_config.toml @@ -0,0 +1,84 @@ +[tool.python] +enabled = true +[tool.python.pip] +enabled = true + +[tool.python.conda] +enabled = true +#pkg_name_override +recipe_dir = "./conda" +owner = "Krandedev" +# +# info +# +# If you are uploading to public/private anaconda you need to set this +# secrets.CONDA_API_TOKEN +# +# Or if you will use a private QUETZ server you need to set these +# secrets.QUETZ_API_KEY +# secrets.QUETZ_URL + +[tool.semantic_release] +version_toml = ["pyproject.toml:project.version"] +# info +# +# This will generate a release for your project if added +# generate_release_auto.yaml expects to find this under github repo secrets +# secrets.SOURCE_KEY +# This key needs to be a deploy key with write privileges on this source repository where you plan to do release +# PS! do not enable Do not allow bypassing the above settings under branch protection, if you do we can not push + +assets = [] +commit_message = "{version}\n\nAutomatically generated by python-semantic-release" +commit_parser = "angular" +logging_use_named_masks = false +major_on_zero = true +tag_format = "v{version}" + +[tool.semantic_release.branches.main] +match = "(main|master)" +prerelease_token = "rc" +prerelease = false + +[tool.semantic_release.changelog] +template_dir = "templates" +changelog_file = "CHANGELOG.md" +exclude_commit_patterns = [] + +[tool.semantic_release.changelog.environment] +block_start_string = "{%" +block_end_string = "%}" +variable_start_string = "{{" +variable_end_string = "}}" +comment_start_string = "{#" +comment_end_string = "#}" +trim_blocks = false +lstrip_blocks = false +newline_sequence = "\n" +keep_trailing_newline = false +extensions = [] +autoescape = true + +[tool.semantic_release.commit_author] +env = "GIT_COMMIT_AUTHOR" +default = "semantic-release " + +[tool.semantic_release.commit_parser_options] +allowed_tags = [ + "chore", + "feat", + "fix" +] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] + +[tool.semantic_release.remote] +name = "origin" +type = "github" +ignore_token_for_push = true + +[tool.semantic_release.remote.token] +env = "GH_TOKEN" + +[tool.semantic_release.publish] +upload_to_vcs_release = true