Oasislmf Testing

# Workflow to run the package unit tests on multiple python versions (CI)
#
#  inputs:
#    ktools_branch: If set, build ktools from scratch and use output to build package
#                   These new ktools binaries, will be used when testing the oasislmf
#                   instead of the pinned ktools version

name: Oasislmf Testing

on:
  pull_request:
  push:
    branches:
      - main
      - stable**
  workflow_dispatch:
    inputs:
      ktools_branch:
        description: "Build Ktools before tests: [git ref]"
        required: false

      ods_branch:
        description: "Git install ods_tools branch before tests: [git ref]"
        required: false

jobs:
  build:
    uses: ./.github/workflows/build.yml
    secrets: inherit
    with:
      ktools_branch: ${{ inputs.ktools_branch }}

  ods_tools:
    if: ${{ inputs.ods_branch != '' || github.event_name != 'workflow_dispatch' }}
    uses: OasisLMF/ODS_Tools/.github/workflows/build.yml@main
    secrets: inherit
    with:
      ods_branch: ${{ github.event_name != 'workflow_dispatch' && 'main' ||  inputs.ods_branch }} # Enable 'ods-tools' build by default

  unittest:
    if: ${{ ! failure() || ! cancelled() }}
    runs-on: ubuntu-latest
    needs: [build, ods_tools]
    strategy:
      matrix:
        cfg:
          - { python-version: "3.8", pkg-version: "", upload-cov: false }
          - { python-version: "3.9", pkg-version: "", upload-cov: false }
          - { python-version: "3.10", pkg-version: "", upload-cov: false }
          - { python-version: "3.11", pkg-version: "", upload-cov: false }
          - { python-version: "3.12", pkg-version: "", upload-cov: false }
          - { python-version: "3.11", pkg-version: "", upload-cov: true }

    steps:
      - uses: actions/checkout@v3
      - name: Set up Python ${{ matrix.cfg.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.cfg.python-version }}

      - name: Install pip-tools
        run: |
          python -m pip install --upgrade pip
          pip install pip-tools

      - name: Set pip resolver flag
        if: matrix.cfg.python-version == 3.7
        run: |
          echo "PIP_RESOLVER=--resolver=backtracking" >> $GITHUB_ENV
      - name: Pip Compile
        run: |
          rm -f requirements.txt
          if [ -z "${{ matrix.cfg.pkg-version }}" ]; then
            pip-compile requirements.in -o requirements.txt ${PIP_RESOLVER}
          else
            pip-compile --upgrade-package "${{ matrix.cfg.pkg-version }}" requirements.in -o requirements.txt ${PIP_RESOLVER}
          fi
          pip install -r requirements.txt

      - name: Download package
        if: matrix.cfg.upload-cov != true
        uses: actions/download-artifact@v3
        with:
          name: oasislmf-bin-pkg_linux
          path: ${{ github.workspace }}/

      - name: Install Package (From Package)
        if: matrix.cfg.upload-cov != true
        run: |
          pip install ${{ needs.build.outputs.linux_pkg_filename }}

      - name: Install Oasislmf (Editable mode)
        if: matrix.cfg.upload-cov == true
        run: |
          pip install -v -e .

      - name: download ods_tools
        if: needs.ods_tools.outputs.whl_filename != ''
        uses: actions/download-artifact@v3
        with:
          name: bin_package
          path: ${{ github.workspace }}/

      - name: install ods_tools
        if: needs.ods_tools.outputs.whl_filename != ''
        run: |
          pip uninstall ods_tools -y
          pip install ${{ needs.ods_tools.outputs.whl_filename }}

      - name: run pytest (JIT enabled)
        if: matrix.cfg.upload-cov != true
        run: pytest -p no:flaky --ignore=fm_testing_tool --ignore=validation --cov-config=tox.ini --cov=oasislmf --cov-report=xml --cov-report=term

      - name: run pytest (JIT disabled)
        if: matrix.cfg.upload-cov == true
        run: pytest -p no:flaky --ignore=fm_testing_tool --ignore=validation --cov-config=tox.ini --cov=oasislmf --cov-report=xml --cov-report=term --gul-rtol 1e-3 --gul-atol 1e-3
        env:
          NUMBA_DISABLE_JIT: 1

      - name: Upload coverage to Codecov
        if: matrix.cfg.upload-cov == true
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage.xml


Oasislmf Version

# Workflow to update the version tags in the oasislmf repo
#
#  inputs:
#    ktools_version: Update the pinned ktools version stored in setup.py
#                    Note: (without the "v") so for the ktools release v3.0.0, input '3.0.0'
#
#    oasislmf_version: Update the package version stored in __init__.py

name: Oasislmf Version

on:
  workflow_dispatch:
    inputs:
      ktools_version:
        description: 'Update the ktools version [semvar]'
        required: false
        default: ""
      oasislmf_version:
        description: 'Update the package version [semvar]'
        required: false
        default: ""

  workflow_call:
    inputs:
      ktools_version:
        description: 'Update the ktools version'
        required: false
        default: ""
        type: string
      oasislmf_version:
        description: 'Update the package version'
        required: false
        default: ""
        type: string

env:
  ktools_file: 'setup.py'
  ktools_regex: '^KTOOLS_VERSION'

  oasislmf_file: 'oasislmf/__init__.py'
  oasislmf_regex: '^__version__'

jobs:
  version:
    runs-on: ubuntu-latest
    steps:

    - name: Check input is valid semvar (Ktools)
      if: inputs.ktools_version != ''
      run: |
        VALID=$(echo ${{ inputs.ktools_version }} | grep -oPc "^(\d+)\.(\d+)\.(\d+)rc(\d+)|(\d+)\.(\d+)\.(\d+)$")
        [[ "$VALID" -eq "1" ]] || exit 1

    - name: Check input is valid semvar (Oasislmf)
      if: inputs.oasislmf_version != ''
      run: |
        VALID=$(echo ${{ inputs.oasislmf_version }} | grep -oPc "^(\d+)\.(\d+)\.(\d+)rc(\d+)|(\d+)\.(\d+)\.(\d+)$")
        [[ "$VALID" -eq "1" ]] || exit 1

    - name: Checkout
      if: inputs.ktools_version != '' ||  inputs.oasislmf_version != ''
      uses: actions/checkout@v3
      with:
        fetch-depth: 0 # fetch the whole repo for complete history

    - name: Setup github user
      if: inputs.ktools_version != '' ||  inputs.oasislmf_version != ''
      run: |
        git config --global user.email ${{ env.GIT_EMAIL }}
        git config --global user.name ${{ env.GIT_USERNAME }}
        git config --global pull.ff only
      env:
        GIT_EMAIL: ${{ secrets.BUILD_GIT_EMAIL }}
        GIT_USERNAME: ${{ secrets.BUILD_GIT_USERNAME }}

    - name: Write ktools version
      if: inputs.ktools_version != ''
      run: |
        CURRENT_VER=$(grep ${{ env.ktools_regex }} ${{ env.ktools_file }} | awk -F"'" '{print $2}')
        sed -i 's|'$CURRENT_VER'|'${{ inputs.ktools_version }}'|g' ${{ env.ktools_file }}
        git add ${{ env.ktools_file }}
        # Only commit if something changed
        [[ -z $(git status -s) ]] || git commit -m "Set Ktools to version ${{ inputs.ktools_version }}"

    - name: Write oasislmf version
      if: inputs.oasislmf_version != ''
      run: |
        CURRENT_VER=$(grep ${{ env.oasislmf_regex }} ${{ env.oasislmf_file }} | awk -F"'" '{print $2}')
        sed -i 's|'$CURRENT_VER'|'${{ inputs.oasislmf_version }}'|g' ${{ env.oasislmf_file }}
        git add ${{ env.oasislmf_file }}
        # Only commit if something changed
        [[ -z $(git status -s) ]] || git commit -m "Set oasislmf to version ${{ inputs.oasislmf_version }}"

    - name: Push
      if: inputs.ktools_version != '' ||  inputs.oasislmf_version != ''
      run: git push
      env:
        GITHUB_TOKEN: ${{ secrets.BUILD_GIT_TOKEN }}


Publish Docker image

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

# GitHub recommends pinning actions to a commit SHA.
# To get a newer version, you will need to update the SHA.
# You can also reference a tag or branch, but the action may change without warning.

name: Publish Docker image

on:
  release:
    types: [published]
  workflow_dispatch:

jobs:
  push_to_registry:
    name: Push Docker image to Docker Hub
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repo
        uses: actions/checkout@v4

      - name: Log in to Docker Hub
        uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
        with:
          username: ${{ secrets.DOCKER_USER }}
          password: ${{ secrets.DOCKER_TOKEN }}

      - name: Extract metadata (tags, labels) for Docker
        id: meta
        uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
        with:
          images: jblance/mppsolar

      - name: Build and push Docker image
        uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
        with:
          context: .
          file: ./Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}


Upload Python Package

# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Upload Python Package

on:
  release:
    types: [published]

permissions:
  contents: read

jobs:
  deploy:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.x'
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install build
    - name: Build package
      run: python -m build --wheel
    - name: Publish package
      uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
      with:
        user: __token__
        password: ${{ secrets.PYPI_TOKEN }}


Check & Deploy

name: Check & Deploy

on:
  release:
    types:
      - published

jobs:
  check-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout git repository
        uses: actions/checkout@v2
        with:
          submodules: true
          path: _src/

      - name: Install Python dependencies
        run: |
          pushd _src
          pip install .[sampling,plotting,testing]
          pip install pytest
          popd

      - name: Test with pytest
        run: |
          pushd _src
          pytest
          popd

      - name: Verify release version matches source code version
        shell: bash
        run: |
          pushd _src
          export TAG_VERSION=${GITHUB_REF##refs/tags/v}
          export SRC_VERSION=$(python3 -c "from flavio._version import __version__; print(__version__)")
          if [[ ${TAG_VERSION} != ${SRC_VERSION} ]] ; then
            echo "tag/release version and source code version disagree, exiting"
            exit 1
          fi
          popd

      - name: Build bdist
        shell: bash
        run: |
          mkdir dist
          pushd _src
          python3 ./setup.py sdist -d ../dist/
          python3 ./setup.py bdist_wheel -d ../dist/
          popd

      - name: Test installing the wheel
        shell: bash
        run: |
          python3 -m pip install dist/flavio-*.whl

      - name: Upload build as artifact
        uses: actions/upload-artifact@v1
        with:
          name: flavio-dist-${{ github.sha }}
          path: dist

      - name: Upload to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          user:     ${{ secrets.TWINE_USERNAME }}
          password: ${{ secrets.TWINE_PASSWORD }}


Test

name: Test

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.7, 3.8, 3.9, '3.10']
    steps:
      - name: Checkout git repository
        uses: actions/checkout@v2

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install Python dependencies
        run: |
          pip install .[sampling,plotting,testing]
          pip install pytest

      - name: Test with pytest
        run: |
          pytest


Check URLs

name: Check URLs

on:
  push:
    branches: [devel]
  pull_request:
    branches: [devel]
  schedule:
    - cron: '0 0 * * *'

jobs:
  urlcheck:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    - name: URLs-checker
      uses: urlstechie/urlchecker-action@0.0.34
      with:
        # check this subfolder only
        subfolder: docs

        # clone devel
        branch: devel
        # A comma-separated list of file types to cover in the URL checks
        file_types: .md,.py,.rst

        # Choose whether to include file with no URLs in the prints.
        print_all: false

        # timeout in 10 seconds if url is not reached
        timeout: 10

        # How many times to retry a failed request (defaults to 1)
        retry_count: 5

        # Cannot check private GitHub settings
        exclude_patterns: https://github.com/buildtesters/buildtest/settings,https://hpckp.org/past-edition/hpckp-17/,https://hpckp.org/past-edition/hpckp-18,https://sc19.supercomputing.org/presentation/?id=bof195&sess=sess324,https://sc19.supercomputing.org/,https://trac.mcs.anl.gov/projects/cobalt,https://cache.e4s.io,https://readthedocs.org/accounts/signup/,http://localhost:9000,https://www.hpcwire.com/2019/01/17/pfizer-hpc-engineer-aims-to-automate-software-stack-testing/,https://my.cdash.org/

        verbose: true


regressiontest

name: regressiontest

on:
  pull_request:
    branches: [devel]
    paths:
      - 'buildtest/**'
      - 'tests/**'
      - '.github/workflows/regression.yml'
      - 'requirements.txt'
      - 'pyproject.toml'

jobs:

  buildtest_regtest:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: true

      matrix:
        os: [macos-latest, macos-13, ubuntu-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11']

    steps:
    - uses: actions/checkout@v4

    - name: Setup Python ${{ matrix.python-version }}
      uses: actions/setup-python@v5.0.0
      with:
        python-version: ${{ matrix.python-version }}

    - name: Run buildtest unittests for os - ${{ matrix.os }}, python version - ${{ matrix.python-version }}
      env:
        OS: ${{ matrix.os }}
      shell: bash
      run: |
        if [ $OS = "ubuntu-latest"  ] ; then sudo apt-get install -y csh tcsh zsh && sudo add-apt-repository ppa:kelleyk/emacs && sudo apt-get install -y emacs28; else brew install tcsh zsh emacs ;  fi
        cat /etc/shells
        source setup.sh
        pip install pytest
        pip install coverage
        python $BUILDTEST_ROOT/buildtest/tools/unittests.py -c
        returncode=$?
        if [ $returncode != 0 ]; then exit $returncode; fi
        buildtest cdash upload --site=github unittests

    - name: Setup tmate session
      if: ${{ failure() }}
      uses: mxschmitt/action-tmate@v3
      timeout-minutes: 30

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v4
      with:
        verbose: true
      env:
        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}


installation

name: installation

on:
  pull_request:
    branches: [devel]

jobs:

  buildtest-installation:
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        os: [macos-latest, macos-13, ubuntu-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11']

    steps:
    - uses: actions/checkout@v4

    - name: Setup Python ${{ matrix.python-version }}
      uses: actions/setup-python@v5.0.0
      with:
        python-version: ${{ matrix.python-version }}

    - name: "Install Zsh and Tcsh"
      run: |
        if [[ ${{ matrix.os }} == 'ubuntu-latest' ]]; then
          sudo apt-get install -y tcsh zsh
        else
          brew install tcsh zsh
        fi
    - name: Buildtest Installation for os - ${{ matrix.os }}, python version - ${{ matrix.python-version }}, shell - bash
      env:
        OS: '${{ matrix.os }}'
      shell: bash
      run: |
        echo "$SHELL"
        pip install -U pip
        source setup.sh
        which buildtest
        buildtest --help

    - name: Buildtest Installation for os - ${{ matrix.os }}, python version - ${{ matrix.python-version }}, shell - zsh
      env:
        OS: '${{ matrix.os }}'
      shell: zsh {0}
      run: |
        echo "$SHELL"
        pip install -U pip
        source setup.sh
        which buildtest
        buildtest --help

    - uses: actions/checkout@v4
    - name: Setup tmate session
      if: ${{ failure() }}
      uses: mxschmitt/action-tmate@v3
      timeout-minutes: 30


Build Container

name: Build Container

on:
  pull_request:
    branches: [devel]

jobs:
  build_container:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - name: Check out the repo
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          tags: ghcr.io/buildtesters/buildtest_spack:spack-sc23
          context: scripts/spack_container
          push: true


Nightly Regression Test

name: Nightly Regression Test

on:

  schedule:
    - cron: '0 0 * * *'

jobs:

  buildtest_nightly_regression_test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: true

      matrix:
        os: [macos-latest, macos-13, ubuntu-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11']

    steps:
    - uses: actions/checkout@v4

    - name: Setup Python ${{ matrix.python-version }}
      uses: actions/setup-python@v5.0.0
      with:
        python-version: ${{ matrix.python-version }}

    - name: Run buildtest unittests for os - ${{ matrix.os }}, python version - ${{ matrix.python-version }}
      env:
        OS: ${{ matrix.os }}
      shell: bash
      run: |
        if [ $OS = "ubuntu-latest"  ] ; then sudo apt-get install -y csh tcsh zsh && sudo add-apt-repository ppa:kelleyk/emacs && sudo apt-get install -y emacs28; else brew install tcsh zsh emacs ;  fi
        cat /etc/shells
        source setup.sh
        pip install pytest
        pip install coverage
        python $BUILDTEST_ROOT/buildtest/tools/unittests.py -c
        returncode=$?
        if [ $returncode != 0 ]; then exit $returncode; fi
        buildtest cdash upload --site=github nightly-$(date)

    - name: Setup tmate session
      if: ${{ failure() }}
      uses: mxschmitt/action-tmate@v3
      timeout-minutes: 30

    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v4
      with:
        verbose: true
      env:
        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}


Style Checks

name: Style Checks

on:
  push:
    branches:
      - master
      - devel
  pull_request:
    branches:
      - master
      - devel

jobs:
  style_checks:
    runs-on: ubuntu-latest
    steps:

      # use isort github action see
      #https://pycqa.github.io/isort/docs/configuration/github_action.html
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5.0.0
        with:
          python-version: 3.8

      - name: Check imports with pyflakes
        run: |
          pip install pyflakes
          pyflakes buildtest tests scripts

      - name: Running isort check
        uses: jamescurtin/isort-action@master
        with:
          #requirementsFiles: "requirements.txt docs/requirements.txt"
          sortPaths: "buildtest tests docs"

      - name: Running black check
      # use black github action see
      #https://black.readthedocs.io/en/stable/integrations/github_actions.html
        uses: psf/black@stable
        with:
          options: "--check --verbose"
          src: "buildtest tests scripts"

      - name: Run yamllint
        run: |
          pip install yamllint
          yamllint -c .yamllint.yml .github .gitlab buildtest tests docs


Shell Check

name: Shell Check

on:
  push:
    branches:
      - devel
  pull_request:
    branches:
      - devel

jobs:
  style_checks:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
      - name: Run Shell Check
        run: |
          sudo apt-get install -y shellcheck
          shellcheck --version
          shellcheck -x  setup.sh scripts/spack_container/*.sh


regressiontest

name: regressiontest

on:
  pull_request:
    branches: [devel]

jobs:

  commandline_tutorial_check:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5.0.0
        with:
          python-version: 3.8

      - name: Buildtest CLI Check
        shell: bash
        run: |
          sudo apt-get install -y csh tcsh zsh
          source setup.sh
          # tutorial check
          bash .github/workflows/cli.sh


Regression Test with Spack Support

name: Regression Test with Spack Support

on:
  pull_request:
    branches: [devel]
    paths:
      - 'buildtest/**'
      - 'tests/**'
      - '.github/workflows/spack_regression_test.yml'
      - 'requirements.txt'
      - 'pyproject.toml'

jobs:

  buildtest_regtest_with_spack:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4
      - name: Setup Python
        uses: actions/setup-python@v5.0.0
      - name: Set up Spack
        uses: spack/setup-spack@v2
      - name: Spack Regression Test
        shell: spack-bash {0}
        run: |
          spack --version
          whoami
          spack find
          source setup.sh
          pip install pytest coverage
          python $BUILDTEST_ROOT/buildtest/tools/unittests.py -c -p "-m spack"
          returncode=$?
          if [ $returncode != 0 ]; then exit $returncode; fi
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          verbose: true


Backport

name: Backport
on:
  workflow_dispatch:
    inputs:
      number:
        description: "The pull request # to backport"
        required: true

jobs:
  backport:
    runs-on: ubuntu-latest
    steps:
      - run: |
          if [[ ! $GITHUB_REF_NAME =~ ^release/v[0-9]+\.[0-9]+\.x-0\.[0-9]+bx$ ]]; then
            echo this workflow should only be run against long-term release branches
            exit 1
          fi

      - uses: actions/checkout@v4
        with:
          # history is needed to run git cherry-pick below
          fetch-depth: 0

      - name: Use CLA approved github bot
        run: .github/scripts/use-cla-approved-github-bot.sh

      - name: Create pull request
        env:
          NUMBER: ${{ github.event.inputs.number }}
          # not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows
          GITHUB_TOKEN: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }}
        run: |
          commit=$(gh pr view $NUMBER --json mergeCommit --jq .mergeCommit.oid)
          title=$(gh pr view $NUMBER --json title --jq .title)

          branch="opentelemetrybot/backport-${NUMBER}-to-${GITHUB_REF_NAME//\//-}"

          git cherry-pick $commit
          git push origin HEAD:$branch
          gh pr create --title "[$GITHUB_REF_NAME] $title" \
                       --body "Clean cherry-pick of #$NUMBER to the \`$GITHUB_REF_NAME\` branch." \
                       --head $branch \
                       --base $GITHUB_REF_NAME


changelog

# This action requires that any PR targeting the main branch should touch at
# least one CHANGELOG file. If a CHANGELOG entry is not required, add the "Skip
# Changelog" label to disable this action.

name: changelog

on:
  pull_request:
    types: [opened, synchronize, reopened, labeled, unlabeled]
    branches:
      - main

jobs:
  changelog:
    runs-on: ubuntu-latest
    if: |
      !contains(github.event.pull_request.labels.*.name, 'Skip Changelog')
      && github.actor != 'opentelemetrybot'

    steps:
      - uses: actions/checkout@v4

      - name: Check for CHANGELOG changes
        run: |
          # Only the latest commit of the feature branch is available
          # automatically. To diff with the base branch, we need to
          # fetch that too (and we only need its latest commit).
          git fetch origin ${{ github.base_ref }} --depth=1
          if [[ $(git diff --name-only FETCH_HEAD | grep CHANGELOG) ]]
          then
            echo "A CHANGELOG was modified. Looks good!"
          else
            echo "No CHANGELOG was modified."
            echo "Please add a CHANGELOG entry, or add the \"Skip Changelog\" label if not required."
            false
          fi


CodeQL Analysis

name: CodeQL Analysis

on:
  workflow_dispatch:
  schedule:
    #        ┌───────────── minute (0 - 59)
    #        │  ┌───────────── hour (0 - 23)
    #        │  │ ┌───────────── day of the month (1 - 31)
    #        │  │ │ ┌───────────── month (1 - 12 or JAN-DEC)
    #        │  │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
    #        │  │ │ │ │
    #        │  │ │ │ │
    #        │  │ │ │ │
    #        *  * * * *
    - cron: '30 1 * * *'

jobs:
  CodeQL-Build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      # Initializes the CodeQL tools for scanning.
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v3
        with:
          languages: python

      - name: Autobuild
        uses: github/codeql-action/autobuild@v3

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v3


Component Owners

# This action assigns and requires approval from owners of components for
# PRs that are open against those components. Components are defined as
# individual paths within this repository.
name: 'Component Owners'

on:
  pull_request_target:

jobs:
  run_self:
    runs-on: ubuntu-latest
    name: Auto Assign Owners
    # Don't fail tests if this workflow fails. Some pending issues:
    # - https://github.com/dyladan/component-owners/issues/8
    continue-on-error: true
    steps:
      - uses: dyladan/component-owners@main


Contrib Repo Tests

name: Contrib Repo Tests

on:
  push:
    branches-ignore:
    - 'release/*'
  pull_request:
env:
  CORE_REPO_SHA: 141a6a2e473ef7f0ec4915dfb71e3c0fa595283e

jobs:
  instrumentations-0:
    env:
      # We use these variables to convert between tox and GHA version literals
      py38: 3.8
      py39: 3.9
      py310: "3.10"
      py311: "3.11"
      py312: "3.12"
      pypy3: pypy-3.8
      RUN_MATRIX_COMBINATION: ${{ matrix.python-version }}-${{ matrix.package }}-${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false  # ensures the entire test matrix is run, even if one permutation fails
      matrix:
        python-version: [py38, py39, py310, py311, py312, pypy3]
        package:
          # Do not add more instrumentations here, add them in instrumentations_1.yml.
          # The reason for this separation of instrumentations into more than one YAML file is
          # the limit of jobs that can be run from a Github actions matrix:
          # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs
          # "A matrix will generate a maximum of 256 jobs per workflow run. This limit applies
          # to both GitHub-hosted and self-hosted runners."
          - "aiohttp-client"
          - "aiohttp-server"
          - "aiopg"
          - "aio-pika"
          - "asgi"
          - "asyncpg"
          - "aws-lambda"
          - "boto"
          - "boto3sqs"
          - "botocore"
          - "cassandra"
          - "celery"
          - "confluent-kafka"
          - "dbapi"
          - "django"
          - "elasticsearch"
          - "falcon"
          - "fastapi"
          - "flask"
          - "grpc"
          - "httpx"
          - "jinja2"
          - "kafka-python"
          - "logging"
          - "mysql"
          - "mysqlclient"
          - "sio-pika"
          - "psycopg2"
          - "pymemcache"
          - "pymongo"
          - "pymysql"
          - "pyramid"
          - "redis"
          - "remoulade"
          - "requests"
          - "sklearn"
          - "sqlalchemy"
          - "sqlite3"
          - "starlette"
          - "system-metrics"
          - "tornado"
          - "tortoiseorm"
        os: [ubuntu-20.04]
        exclude:
          - python-version: py39
            package: "sklearn"
          - python-version: py310
            package: "sklearn"
          - python-version: py311
            package: "sklearn"
          - python-version: py312
            package: "sklearn"
          - python-version: py312
            package: "boto"
          - python-version: py312
            package: "kafka-python"
          - python-version: pypy3
            package: "aiopg"
          - python-version: pypy3
            package: "asyncpg"
          - python-version: pypy3
            package: "boto"
          - python-version: pypy3
            package: "boto3sqs"
          - python-version: pypy3
            package: "botocore"
          - python-version: pypy3
            package: "psycopg2"
          - python-version: pypy3
            package: "remoulade"
          - python-version: pypy3
            package: "requests"
          - python-version: pypy3
            package: "sklearn"
          - python-version: pypy3
            package: "confluent-kafka"
          - python-version: pypy3
            package: "grpc"
    steps:
      - name: Checkout Contrib Repo @ SHA - ${{ github.sha }}
        uses: actions/checkout@v4
      - name: Set up Python ${{ env[matrix.python-version] }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env[matrix.python-version] }}
      - name: Install tox
        run: pip install tox
      - name: Cache tox environment
        # Preserves .tox directory between runs for faster installs
        uses: actions/cache@v4
        with:
          path: |
            .tox
            ~/.cache/pip
          key: v7-build-tox-cache-${{ env.RUN_MATRIX_COMBINATION }}-${{ hashFiles('tox.ini', 'gen-requirements.txt', 'dev-requirements.txt') }}
      - name: run tox
        run: tox -f ${{ matrix.python-version }}-${{ matrix.package }} -- -ra --benchmark-json=${{ env.RUN_MATRIX_COMBINATION }}-benchmark.json


name: Contrib Repo Tests

on:
  push:
    branches-ignore:
    - 'release/*'
  pull_request:
env:
  CORE_REPO_SHA: 141a6a2e473ef7f0ec4915dfb71e3c0fa595283e

jobs:
  instrumentations-1:
    env:
      # We use these variables to convert between tox and GHA version literals
      py38: 3.8
      py39: 3.9
      py310: "3.10"
      py311: "3.11"
      py312: "3.12"
      pypy3: pypy-3.8
      RUN_MATRIX_COMBINATION: ${{ matrix.python-version }}-${{ matrix.package }}-${{ matrix.os }}
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false  # ensures the entire test matrix is run, even if one permutation fails
      matrix:
        python-version: [py38, py39, py310, py311, py312, pypy3]
        package:
          - "urllib"
          - "urllib3"
          - "wsgi"
          - "distro"
          - "richconsole"
          - "psycopg"
          - "prometheus-remote-write"
          - "sdk-extension-aws"
          - "propagator-aws-xray"
          - "propagator-ot-trace"
          - "resource-detector-container"
        os: [ubuntu-20.04]
        exclude:
          - python-version: pypy3
            package: "prometheus-remote-write"
    steps:
      - name: Checkout Contrib Repo @ SHA - ${{ github.sha }}
        uses: actions/checkout@v4
      - name: Set up Python ${{ env[matrix.python-version] }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env[matrix.python-version] }}
      - name: Install tox
        run: pip install tox
      - name: Cache tox environment
        # Preserves .tox directory between runs for faster installs
        uses: actions/cache@v4
        with:
          path: |
            .tox
            ~/.cache/pip
          key: v7-build-tox-cache-${{ env.RUN_MATRIX_COMBINATION }}-${{ hashFiles('tox.ini', 'gen-requirements.txt', 'dev-requirements.txt') }}
      - name: run tox
        run: tox -f ${{ matrix.python-version }}-${{ matrix.package }} -- -ra --benchmark-json=${{ env.RUN_MATRIX_COMBINATION }}-benchmark.json


Lint tests

name: Lint tests

on:
  push:
    branches-ignore:
    - 'release/*'
  pull_request:
env:
  CORE_REPO_SHA: 141a6a2e473ef7f0ec4915dfb71e3c0fa595283e

jobs:
  lint-3_12:
    strategy:
      fail-fast: false  # ensures the entire test matrix is run, even if one permutation fails
      matrix:
        package:
          - "distro"
          - "exporter-prometheus-remote-write"
          - "exporter-richconsole"
          - "instrumentation-aio-pika"
          - "instrumentation-aiohttp-client"
          - "instrumentation-aiohttp-server"
          - "instrumentation-aiopg"
          - "instrumentation-asgi"
          - "instrumentation-asyncio"
          - "instrumentation-asyncpg"
          - "instrumentation-aws-lambda"
          - "instrumentation-boto"
          - "instrumentation-boto3sqs"
          - "instrumentation-botocore"
          - "instrumentation-cassandra"
          - "instrumentation-celery"
          - "instrumentation-confluent-kafka"
          - "instrumentation-dbapi"
          - "instrumentation-django"
          - "instrumentation-elasticsearch"
          - "instrumentation-falcon"
          - "instrumentation-fastapi"
          - "instrumentation-flask"
          - "instrumentation-grpc"
          - "instrumentation-httpx"
          - "instrumentation-jinja2"
          - "instrumentation-kafka-python"
          - "instrumentation-logging"
          - "instrumentation-mysql"
          - "instrumentation-mysqlclient"
          - "instrumentation-psycopg"
          - "instrumentation-psycopg2"
          - "instrumentation-pymemcache"
          - "instrumentation-pymongo"
          - "instrumentation-pymysql"
          - "instrumentation-pyramid"
          - "instrumentation-redis"
          - "instrumentation-remoulade"
          - "instrumentation-requests"
          - "instrumentation-sio-pika"
          - "instrumentation-sqlalchemy"
          - "instrumentation-sqlite3"
          - "instrumentation-starlette"
          - "instrumentation-system-metrics"
          - "instrumentation-threading"
          - "instrumentation-tornado"
          - "instrumentation-tortoiseorm"
          - "instrumentation-urllib"
          - "instrumentation-urllib3"
          - "instrumentation-wsgi"
          - "opentelemetry-instrumentation"
          - "processor-baggage"
          - "propagator-aws-xray"
          - "propagator-ot-trace"
          - "resource-detector-container"
          - "sdk-extension-aws"
        os: [ubuntu-20.04]
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout Contrib Repo @ SHA - ${{ github.sha }}
        uses: actions/checkout@v4
      - name: Set up Python 3.12
        uses: actions/setup-python@v5
        with:
          python-version: 3.12
      - name: Install tox
        run: pip install tox
      - name: Cache tox environment
        # Preserves .tox directory between runs for faster installs
        uses: actions/cache@v4
        with:
          path: |
            .tox
            ~/.cache/pip
          key: v7-build-tox-cache-${{ matrix.package }}-${{ hashFiles('tox.ini', 'gen-requirements.txt', 'dev-requirements.txt') }}
      - name: run tox
        run: tox -e lint-${{ matrix.package }}

  lint-3_8:
    strategy:
      fail-fast: false  # ensures the entire test matrix is run, even if one permutation fails
      matrix:
        package:
          - "instrumentation-sklearn"
        os: [ubuntu-20.04]
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout Contrib Repo @ SHA - ${{ github.sha }}
        uses: actions/checkout@v4
      - name: Set up Python 3.8
        uses: actions/setup-python@v5
        with:
          python-version: 3.8
      - name: Install tox
        run: pip install tox
      - name: Cache tox environment
        # Preserves .tox directory between runs for faster installs
        uses: actions/cache@v4
        with:
          path: |
            .tox
            ~/.cache/pip
          key: v7-build-tox-cache-${{ matrix.package }}-${{ hashFiles('tox.ini', 'gen-requirements.txt', 'dev-requirements.txt') }}
      - name: run tox
        run: tox -e lint-${{ matrix.package }}


Prepare patch release

name: Prepare patch release
on:
  workflow_dispatch:

jobs:
  prepare-patch-release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - run: |
          if [[ ! $GITHUB_REF_NAME =~ ^release/v[0-9]+\.[0-9]+\.x-0\.[0-9]+bx$ ]]; then
            echo this workflow should only be run against long-term release branches
            exit 1
          fi

          if ! grep --quiet "^## Unreleased$" CHANGELOG.md; then
            echo the change log is missing an \"Unreleased\" section
            exit 1
          fi

      - name: Set environment variables
        run: |
          stable_version=$(./scripts/eachdist.py version --mode stable)
          unstable_version=$(./scripts/eachdist.py version --mode prerelease)

          if [[ $stable_version =~ ^([0-9]+\.[0-9]+)\.([0-9]+)$ ]]; then
            stable_major_minor="${BASH_REMATCH[1]}"
            stable_patch="${BASH_REMATCH[2]}"
          else
            echo "unexpected stable_version: $stable_version"
            exit 1
          fi

          if [[ $unstable_version =~ ^0\.([0-9]+)b([0-9]+)$ ]]; then
            unstable_minor="${BASH_REMATCH[1]}"
            unstable_patch="${BASH_REMATCH[2]}"
          else
            echo "unexpected unstable_version: $unstable_version"
            exit 1
          fi

          stable_version="$stable_major_minor.$((stable_patch + 1))"
          unstable_version="0.${unstable_minor}b$((unstable_patch + 1))"

          echo "STABLE_VERSION=$stable_version" >> $GITHUB_ENV
          echo "UNSTABLE_VERSION=$unstable_version" >> $GITHUB_ENV

      - name: Update version
        run: .github/scripts/update-version.sh $STABLE_VERSION $UNSTABLE_VERSION

      - name: Set up Python 3.9
        uses: actions/setup-python@v5
        with:
          python-version: 3.9
      - name: Install tox
        run: pip install tox
      - name: run tox
        run: tox -e generate

      - name: Update the change log with the approximate release date
        run: |
          date=$(date "+%Y-%m-%d")
          sed -Ei "s/^## Unreleased$/## Version ${STABLE_VERSION}\/${UNSTABLE_VERSION} ($date)/" CHANGELOG.md

      - name: Use CLA approved github bot
        run: .github/scripts/use-cla-approved-github-bot.sh

      - name: Create pull request
        env:
          # not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows
          GITHUB_TOKEN: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }}
        run: |
          message="Prepare release ${STABLE_VERSION}/${UNSTABLE_VERSION}"
          branch="opentelemetrybot/prepare-release-${STABLE_VERSION}-${UNSTABLE_VERSION}"

          git commit -a -m "$message"
          git push origin HEAD:$branch
          gh pr create --title "[$GITHUB_REF_NAME] $message" \
                       --body "$message." \
                       --head $branch \
                       --base $GITHUB_REF_NAME


Prepare release branch

name: Prepare release branch
on:
  workflow_dispatch:
    inputs:
      prerelease_version:
        description: "Pre-release version number? (e.g. 1.9.0rc2)"
        required: false

jobs:
  prereqs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Verify prerequisites
        env:
          PRERELEASE_VERSION: ${{ github.event.inputs.prerelease_version }}
        run: |
          if [[ $GITHUB_REF_NAME != main ]]; then
            echo this workflow should only be run against main
            exit 1
          fi

          if ! grep --quiet "^## Unreleased$" CHANGELOG.md; then
            echo the change log is missing an \"Unreleased\" section
            exit 1
          fi

          if [[ ! -z $PRERELEASE_VERSION ]]; then
            stable_version=$(./scripts/eachdist.py version --mode stable)
            stable_version=${stable_version//.dev/}
            if [[ $PRERELEASE_VERSION != ${stable_version}* ]]; then
              echo "$PRERELEASE_VERSION is not a prerelease for the version on main ($stable_version)"
              exit 1
            fi
          fi

  create-pull-request-against-release-branch:
    runs-on: ubuntu-latest
    needs: prereqs
    steps:
      - uses: actions/checkout@v4

      - name: Create release branch
        env:
          PRERELEASE_VERSION: ${{ github.event.inputs.prerelease_version }}
        run: |
          if [[ -z $PRERELEASE_VERSION ]]; then
            stable_version=$(./scripts/eachdist.py version --mode stable)
            stable_version=${stable_version//.dev/}
          else
            stable_version=$PRERELEASE_VERSION
          fi

          unstable_version=$(./scripts/eachdist.py version --mode prerelease)
          unstable_version=${unstable_version//.dev/}

          if [[ $stable_version =~ ^([0-9]+)\.([0-9]+)\.0$ ]]; then
            stable_version_branch_part=$(echo $stable_version | sed -E 's/([0-9]+)\.([0-9]+)\.0/\1.\2.x/')
            unstable_version_branch_part=$(echo $unstable_version | sed -E 's/0\.([0-9]+)b0/0.\1bx/')
            release_branch_name="release/v${stable_version_branch_part}-${unstable_version_branch_part}"
          elif [[ $stable_version =~ ^([0-9]+)\.([0-9]+)\.0 ]]; then
            # pre-release version, e.g. 1.9.0rc2
            release_branch_name="release/v$stable_version-$unstable_version"
          else
            echo "unexpected version: $stable_version"
            exit 1
          fi

          git push origin HEAD:$release_branch_name

          echo "STABLE_VERSION=$stable_version" >> $GITHUB_ENV
          echo "UNSTABLE_VERSION=$unstable_version" >> $GITHUB_ENV
          echo "RELEASE_BRANCH_NAME=$release_branch_name" >> $GITHUB_ENV

      - name: Update version
        run: .github/scripts/update-version.sh $STABLE_VERSION $UNSTABLE_VERSION

      - name: Set up Python 3.9
        uses: actions/setup-python@v5
        with:
          python-version: 3.9
      - name: Install tox
        run: pip install tox
      - name: run tox
        run: tox -e generate

      - name: Update the change log with the approximate release date
        run: |
          date=$(date "+%Y-%m-%d")
          sed -Ei "s/^## Unreleased$/## Version ${STABLE_VERSION}\/${UNSTABLE_VERSION} ($date)/" CHANGELOG.md

      - name: Use CLA approved github bot
        run: .github/scripts/use-cla-approved-github-bot.sh

      - name: Create pull request against the release branch
        env:
          # not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows
          GITHUB_TOKEN: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }}
        run: |
          message="Prepare release ${STABLE_VERSION}/${UNSTABLE_VERSION}"
          branch="opentelemetrybot/prepare-release-${STABLE_VERSION}-${UNSTABLE_VERSION}"

          git commit -a -m "$message"
          git push origin HEAD:$branch
          gh pr create --title "[$RELEASE_BRANCH_NAME] $message" \
                       --body "$message." \
                       --head $branch \
                       --base $RELEASE_BRANCH_NAME

  create-pull-request-against-main:
    runs-on: ubuntu-latest
    needs: prereqs
    steps:
      - uses: actions/checkout@v4

      - name: Set environment variables
        env:
          PRERELEASE_VERSION: ${{ github.event.inputs.prerelease_version }}
        run: |
          if [[ -z $PRERELEASE_VERSION ]]; then
            stable_version=$(./scripts/eachdist.py version --mode stable)
            stable_version=${stable_version//.dev/}
          else
            stable_version=$PRERELEASE_VERSION
          fi

          unstable_version=$(./scripts/eachdist.py version --mode prerelease)
          unstable_version=${unstable_version//.dev/}

          if [[ $stable_version =~ ^([0-9]+)\.([0-9]+)\.0$ ]]; then
            stable_major="${BASH_REMATCH[1]}"
            stable_minor="${BASH_REMATCH[2]}"
            stable_next_version="$stable_major.$((stable_minor + 1)).0"
          elif [[ $stable_version =~ ^([0-9]+)\.([0-9]+)\.0 ]]; then
            # pre-release version, e.g. 1.9.0rc2
            stable_major="${BASH_REMATCH[1]}"
            stable_minor="${BASH_REMATCH[2]}"
            stable_next_version="$stable_major.$stable_minor.0"
          else
            echo "unexpected stable_version: $stable_version"
            exit 1
          fi

          if [[ $unstable_version =~ ^0\.([0-9]+)b[0-9]+$ ]]; then
            unstable_minor="${BASH_REMATCH[1]}"
          else
            echo "unexpected unstable_version: $unstable_version"
            exit 1
          fi

          unstable_next_version="0.$((unstable_minor + 1))b0"

          echo "STABLE_VERSION=${stable_version}" >> $GITHUB_ENV
          echo "STABLE_NEXT_VERSION=${stable_next_version}.dev" >> $GITHUB_ENV

          echo "UNSTABLE_VERSION=${unstable_version}" >> $GITHUB_ENV
          echo "UNSTABLE_NEXT_VERSION=${unstable_next_version}.dev" >> $GITHUB_ENV

      - name: Update version
        run: .github/scripts/update-version.sh $STABLE_NEXT_VERSION $UNSTABLE_NEXT_VERSION

      - name: Set up Python 3.9
        uses: actions/setup-python@v5
        with:
          python-version: 3.9
      - name: Install tox
        run: pip install tox
      - name: run tox
        run: tox -e generate

      - name: Update the change log on main
        run: |
          # the actual release date on main will be updated at the end of the release workflow
          date=$(date "+%Y-%m-%d")
          sed -Ei "s/^## Unreleased$/## Unreleased\n\n## Version ${STABLE_VERSION}\/${UNSTABLE_VERSION} ($date)/" CHANGELOG.md

      - name: Use CLA approved github bot
        run: .github/scripts/use-cla-approved-github-bot.sh

      - name: Create pull request against main
        env:
          # not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows
          GITHUB_TOKEN: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }}
        run: |
          message="Update version to ${STABLE_NEXT_VERSION}/${UNSTABLE_NEXT_VERSION}"
          body="Update version to \`${STABLE_NEXT_VERSION}/${UNSTABLE_NEXT_VERSION}\`."
          branch="opentelemetrybot/update-version-to-${STABLE_NEXT_VERSION}-${UNSTABLE_NEXT_VERSION}"

          git commit -a -m "$message"
          git push origin HEAD:$branch
          gh pr create --title "$message" \
                       --body "$body" \
                       --head $branch \
                       --base main


Publish a Package from Tag

name: Publish a Package from Tag

on:
  push:
    tags:
      - 'opentelemetry-*==[1-9]*.*'

jobs:
  publish-a-package:
    name: Publish package from tag
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with:
        python-version: '3.9'
    - name: Log tag that triggered publish workflow
      run:  echo "Attempting to publish package from tag $GITHUB_REF"
    - name: Build wheel for tag
      run: ./scripts/build_a_package.sh
    - name: Install twine
      run: |
        pip install twine
    # We don't need to publish to TestPyPI because we only publish 1 package.
    # If it fails no other work needs to be reversed.
    - name: Publish to PyPI
      env:
        TWINE_USERNAME: '__token__'
        TWINE_PASSWORD: ${{ secrets.pypi_password }}
      run: |
        twine upload --skip-existing --verbose dist/*


Release

name: Release
on:
  workflow_dispatch:

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - run: |
          if [[ $GITHUB_REF_NAME != release/* ]]; then
            echo this workflow should only be run against release branches
            exit 1
          fi

      - uses: actions/checkout@v4

      - name: Set environment variables
        run: |
          stable_version=$(./scripts/eachdist.py version --mode stable)
          unstable_version=$(./scripts/eachdist.py version --mode prerelease)

          if [[ $stable_version =~ ^([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
            stable_major="${BASH_REMATCH[1]}"
            stable_minor="${BASH_REMATCH[2]}"
            stable_patch="${BASH_REMATCH[3]}"
          else
            echo "unexpected stable_version: $stable_version"
            exit 1
          fi
          if [[ $stable_patch != 0 ]]; then
            if [[ $unstable_version =~ ^0\.([0-9]+)b([0-9]+)$ ]]; then
              unstable_minor="${BASH_REMATCH[1]}"
              unstable_patch="${BASH_REMATCH[2]}"
            else
              echo "unexpected unstable_version: $unstable_version"
              exit 1
            fi
            if [[ $unstable_patch != 0 ]]; then
              prior_version_when_patch="$stable_major.$stable_minor.$((stable_patch - 1))/0.${unstable_minor}b$((unstable_patch - 1))"
            fi
          fi

          echo "STABLE_VERSION=$stable_version" >> $GITHUB_ENV
          echo "UNSTABLE_VERSION=$unstable_version" >> $GITHUB_ENV

          echo "PRIOR_VERSION_WHEN_PATCH=$prior_version_when_patch" >> $GITHUB_ENV

      - run: |
          if [[ -z $PRIOR_VERSION_WHEN_PATCH ]]; then
            # not making a patch release
            if ! grep --quiet "^## Version ${STABLE_VERSION}/${UNSTABLE_VERSION} " CHANGELOG.md; then
              echo the pull request generated by prepare-release-branch.yml needs to be merged first
              exit 1
            fi
          fi

      # check out main branch to verify there won't be problems with merging the change log
      # at the end of this workflow
      - uses: actions/checkout@v4
        with:
          ref: main

        # back to the release branch
      - uses: actions/checkout@v4

        # next few steps publish to pypi
      - uses: actions/setup-python@v5
        with:
          python-version: '3.8'

      - name: Build wheels
        run: ./scripts/build.sh

      - name: Install twine
        run: |
          pip install twine

      # The step below publishes to testpypi in order to catch any issues
      # with the package configuration that would cause a failure to upload
      # to pypi. One example of such a failure is if a classifier is
      # rejected by pypi (e.g "3 - Beta"). This would cause a failure during the
      # middle of the package upload causing the action to fail, and certain packages
      # might have already been updated, this would be bad.
      # EDIT: 5/31/2024 - TestPypi now requires a verified email. Commenting out as a temporary measure
      # until we found TestPypi credentials.
      # - name: Publish to TestPyPI
      #   env:
      #     TWINE_USERNAME: '__token__'
      #     TWINE_PASSWORD: ${{ secrets.test_pypi_token }}
      #   run: |
      #     twine upload --repository testpypi --skip-existing --verbose dist/*

      - name: Publish to PyPI
        env:
          TWINE_USERNAME: '__token__'
          TWINE_PASSWORD: ${{ secrets.pypi_password }}
        run: |
          twine upload --skip-existing --verbose dist/*

      - name: Generate release notes
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          # conditional block not indented because of the heredoc
          if [[ ! -z $PRIOR_VERSION_WHEN_PATCH ]]; then
          cat > /tmp/release-notes.txt << EOF
          This is a patch release on the previous $PRIOR_VERSION_WHEN_PATCH release, fixing the issue(s) below.

          EOF
          fi

          # CHANGELOG_SECTION.md is also used at the end of the release workflow
          # for copying the change log updates to main
          sed -n "0,/^## Version ${STABLE_VERSION}\/${UNSTABLE_VERSION} /d;/^## Version /q;p" CHANGELOG.md \
            > /tmp/CHANGELOG_SECTION.md

          # the complex perl regex is needed because markdown docs render newlines as soft wraps
          # while release notes render them as line breaks
          perl -0pe 's/(?<!\n)\n *(?!\n)(?![-*] )(?![1-9]+\. )/ /g' /tmp/CHANGELOG_SECTION.md \
            >> /tmp/release-notes.txt

      - name: Create GitHub release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create --target $GITHUB_REF_NAME \
                            --title "Version ${STABLE_VERSION}/${UNSTABLE_VERSION}" \
                            --notes-file /tmp/release-notes.txt \
                            --discussion-category announcements \
                            v$UNSTABLE_VERSION

      - uses: actions/checkout@v4
        with:
          # the step below is creating a pull request against main
          ref: main

      - name: Copy change log updates to main
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          if [[ -z $PRIOR_VERSION_WHEN_PATCH ]]; then
            # this was not a patch release, so the version exists already in the CHANGELOG.md

            # update the release date
            date=$(gh release view v$STABLE_VERSION --json publishedAt --jq .publishedAt | sed 's/T.*//')
            sed -Ei "s/## Version ${STABLE_VERSION}\/${UNSTABLE_VERSION} .*/## Version ${STABLE_VERSION}\/${UNSTABLE_VERSION} ($date)/" CHANGELOG.md

            # the entries are copied over from the release branch to support workflows
            # where change log entries may be updated after preparing the release branch

            # copy the portion above the release, up to and including the heading
            sed -n "0,/^## Version ${STABLE_VERSION}\/${UNSTABLE_VERSION} ($date)/p" CHANGELOG.md > /tmp/CHANGELOG.md

            # copy the release notes
            cat /tmp/CHANGELOG_SECTION.md >> /tmp/CHANGELOG.md

            # copy the portion below the release
            sed -n "0,/^## Version ${STABLE_VERSION}\/${UNSTABLE_VERSION} /d;0,/^## Version /{/^## Version/!d};p" CHANGELOG.md \
              >> /tmp/CHANGELOG.md

            # update the real CHANGELOG.md
            cp /tmp/CHANGELOG.md CHANGELOG.md
          else
            # this was a patch release, so the version does not exist already in the CHANGELOG.md

            # copy the portion above the top-most release, not including the heading
            sed -n "0,/^## Version /{ /^## Version /!p }" CHANGELOG.md > /tmp/CHANGELOG.md

            # add the heading
            date=$(gh release view v$STABLE_VERSION --json publishedAt --jq .publishedAt | sed 's/T.*//')
            echo "## Version ${STABLE_VERSION}/${UNSTABLE_VERSION} ($date)" >> /tmp/CHANGELOG.md

            # copy the release notes
            cat /tmp/CHANGELOG_SECTION.md >> /tmp/CHANGELOG.md

            # copy the portion starting from the top-most release
            sed -n "/^## Version /,\$p" CHANGELOG.md >> /tmp/CHANGELOG.md

            # update the real CHANGELOG.md
            cp /tmp/CHANGELOG.md CHANGELOG.md
          fi

      - name: Use CLA approved github bot
        run: .github/scripts/use-cla-approved-github-bot.sh

      - name: Create pull request against main
        env:
          # not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows
          GITHUB_TOKEN: ${{ secrets.OPENTELEMETRYBOT_GITHUB_TOKEN }}
        run: |
          message="Copy change log updates from $GITHUB_REF_NAME"
          body="Copy log updates from \`$GITHUB_REF_NAME\`."
          branch="opentelemetrybot/copy-change-log-updates-from-${GITHUB_REF_NAME//\//-}"

          if [[ -z $PRIOR_VERSION_WHEN_PATCH ]]; then
            if git diff --quiet; then
              echo there are no updates needed to the change log on main, not creating pull request
              exit 0 # success
            fi
          fi

          git commit -a -m "$message"
          git push origin HEAD:$branch
          gh pr create --title "$message" \
                       --body "$body" \
                       --head $branch \
                       --base main


Shellcheck

name: Shellcheck

on:
  push:
    branches-ignore:
    - 'release/*'
  pull_request:

jobs:
  shellcheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install shellcheck
        run: sudo apt update && sudo apt install --assume-yes shellcheck

      - name: Run shellcheck
        run: find . -name \*.sh | xargs shellcheck --severity=warning


Contrib Repo Tests

name: Contrib Repo Tests

on:
  push:
    branches-ignore:
    - 'release/*'
  pull_request:
env:
  CORE_REPO_SHA: 141a6a2e473ef7f0ec4915dfb71e3c0fa595283e

jobs:
  misc:
    strategy:
      fail-fast: false
      matrix:
        tox-environment: [ "docker-tests", "spellcheck", "docs", "generate" ]
    name: ${{ matrix.tox-environment }}
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout Contrib Repo @ SHA - ${{ github.sha }}
        uses: actions/checkout@v4
      - name: Set up Python 3.10
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"
      - name: Install tox
        run: pip install tox
      - name: Cache tox environment
        # Preserves .tox directory between runs for faster installs
        uses: actions/cache@v4
        with:
          path: |
            .tox
            ~/.cache/pip
          key: v7-misc-tox-cache-${{ matrix.tox-environment }}-${{ hashFiles('tox.ini', 'dev-requirements.txt', 'gen-requirements.txt', 'docs-requirements.txt') }}
      - name: run tox
        run: tox -e ${{ matrix.tox-environment }}
      - name: Ensure generated code is up to date
        if: matrix.tox-environment == 'generate'
        run: git diff --exit-code || (echo 'Generated code is out of date, please run "tox -e generate" and commit the changes in this PR.' && exit 1)


build

name: build

on:
  release:
    types: [ released ]
  pull_request:
  workflow_dispatch:

defaults:
  run:
    shell: micromamba-shell {0}

jobs:
  build:
    uses: OpenAstronomy/github-actions-workflows/.github/workflows/publish_pure_python.yml@v1
    with:
      upload_to_pypi: ${{ (github.event_name == 'release') && (github.event.action == 'released') }}
    secrets:
      pypi_token: ${{ secrets.PYPI_PASSWORD_STSCI_MAINTAINER }}
  freeze:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os:
          [ "ubuntu-latest", "macos-latest" ]
        python-version:
          [ "3.11", "3.12" ]
      fail-fast: false
    name: freeze versions (Python ${{ matrix.python-version }}, ${{ matrix.os }})
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: mamba-org/setup-micromamba@v1
        with:
          environment-name: jwql-${{ runner.os }}-py${{ matrix.python-version }}
          environment-file: environment.yml
          create-args: >-
            python=${{ matrix.python-version }}
            conda
          init-shell: none
          generate-run-shell: true
      - run: pip install .
      - run: pip list
      - id: version
        uses: mtkennerly/dunamai-action@v1
        with:
          args: --strict --pattern "default-unprefixed" --style semver
      - id: filename
        run: echo "filename=jwql_${{ steps.version.outputs.version }}_conda_${{ runner.os }}_${{ runner.arch }}_py${{ matrix.python-version }}.yml" >> $GITHUB_OUTPUT
      - run: conda env export --no-build | grep -v "name:" | grep -v "prefix:" > ${{ steps.filename.outputs.filename }}
      - run: cat ${{ steps.filename.outputs.filename }}
      - uses: actions/upload-artifact@v4
        with:
          name: ${{ steps.filename.outputs.filename }}
          path: ${{ steps.filename.outputs.filename }}
      - if: (github.event_name == 'release' && github.event.action == 'released')
        uses: svenstaro/upload-release-action@v2
        with:
          file: ${{ steps.filename.outputs.filename }}


test

name: test

on:
  push:
  pull_request:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v4
        with:
          python-version: "3.11"
      - run: pip install bandit
      - run: bandit ./jwql/ -c .bandit

  test:
    name: test (Python ${{ matrix.python-version }}, ${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ "ubuntu-latest", "macos-latest" ]
        python-version: [ "3.11", "3.12" ]
      fail-fast: false
    env:
      PYTHONUNBUFFERED: 1
    defaults:
      run:
        shell: micromamba-shell {0}
    steps:
      - uses: actions/checkout@v4

      - uses: mamba-org/setup-micromamba@v1
        with:
          environment-name: jwql-${{ runner.os }}-py${{ matrix.python-version }}
          environment-file: environment.yml
          create-args: >-
            python=${{ matrix.python-version }}
          init-shell: none
          generate-run-shell: true

      - run: pip install -e .[test] pytest-xdist

      - run: pip list

      - run: micromamba env export

      - if: runner.os == 'Linux'
        uses: supercharge/redis-github-action@1.4.0
        with:
          redis-version: 5.0

      - run: python -c "import jwql; print('Version ' + jwql.__version__); print('Path ' + jwql.__path__[0])"

      - run: pytest -n auto jwql/tests/


Acceptance tests

name: Acceptance tests

on:
  pull_request:
    types:
      - opened
      - edited
      - synchronize
      - reopened
      - closed

jobs:
  cleanup-previous-runs:
    runs-on: ubuntu-latest
    if: github.event.action != 'closed'
    steps:
      - uses: rokroskar/workflow-run-cleanup-action@v0.3.3
        env:
          GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
  check-deploy:
    runs-on: ubuntu-20.04
    outputs:
      pr-contains-string: ${{ steps.deploy-comment.outputs.pr-contains-string }}
      renku: ${{ steps.deploy-comment.outputs.renku}}
      renku-gateway: ${{ steps.deploy-comment.outputs.renku-gateway}}
      renku-graph: ${{ steps.deploy-comment.outputs.renku-graph}}
      renku-notebooks: ${{ steps.deploy-comment.outputs.renku-notebooks}}
      renku-ui: ${{ steps.deploy-comment.outputs.renku-ui}}
      renku-data-services: ${{ steps.deploy-comment.outputs.renku-data-services}}
      test-enabled: ${{ steps.deploy-comment.outputs.test-enabled}}
      extra-values: ${{ steps.deploy-comment.outputs.extra-values}}
      persist: ${{ steps.deploy-comment.outputs.persist}}
    steps:
      - id: deploy-comment
        uses: SwissDataScienceCenter/renku-actions/check-pr-description@v1.9.1
        with:
          string: /deploy
          pr_ref: ${{ github.event.number }}
  deploy-pr:
    needs: check-deploy
    if: github.event.action != 'closed' && needs.check-deploy.outputs.pr-contains-string == 'true'
    runs-on: ubuntu-20.04
    environment:
      name: renku-ci-rp-${{ github.event.number }}
    steps:
      - name: deploy-pr
        uses: SwissDataScienceCenter/renku-actions/deploy-renku@v1.9.1
        env:
          DOCKER_PASSWORD: ${{ secrets.RENKU_DOCKER_PASSWORD }}
          DOCKER_USERNAME: ${{ secrets.RENKU_DOCKER_USERNAME }}
          GITLAB_TOKEN: ${{ secrets.DEV_GITLAB_TOKEN }}
          KUBECONFIG: "${{ github.workspace }}/renkubot-kube.config"
          RANCHER_PROJECT_ID: ${{ secrets.CI_RANCHER_PROJECT }}
          RENKU_RELEASE: renku-ci-rp-${{ github.event.number }}
          RENKU_VALUES_FILE: "${{ github.workspace }}/values.yaml"
          RENKU_VALUES: ${{ secrets.COMBINED_CHARTS_CI_RENKU_VALUES }}
          RENKUBOT_KUBECONFIG: ${{ secrets.RENKUBOT_DEV_KUBECONFIG }}
          RENKUBOT_RANCHER_BEARER_TOKEN: ${{ secrets.RENKUBOT_RANCHER_BEARER_TOKEN }}
          RANCHER_DEV_API_ENDPOINT: ${{ secrets.RANCHER_DEV_API_ENDPOINT }}
          RENKU_BOT_DEV_PASSWORD: ${{ secrets.RENKU_BOT_DEV_PASSWORD }}
          RENKU_ANONYMOUS_SESSIONS: true
          RENKU_TESTS_ENABLED: true
          TEST_ARTIFACTS_PATH: "tests-artifacts-${{ github.sha }}"
          renku_core: "@${{ github.head_ref }}"
          renku: "${{ needs.check-deploy.outputs.renku }}"
          renku_gateway: "${{ needs.check-deploy.outputs.renku-gateway }}"
          renku_graph: "${{ needs.check-deploy.outputs.renku-graph }}"
          renku_notebooks: "${{ needs.check-deploy.outputs.renku-notebooks }}"
          renku_ui: "${{ needs.check-deploy.outputs.renku-ui }}"
          renku_data_services: "${{ needs.check-deploy.outputs.renku-data-services }}"
          extra_values: "${{ needs.check-deploy.outputs.extra-values }}"
      - name: Check existing renkubot comment
        uses: peter-evans/find-comment@v2
        id: findcomment
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: "RenkuBot"
          body-includes: "You can access the deployment of this PR at"
      - name: Create comment pre deploy
        if: steps.findcomment.outputs.comment-id == 0
        uses: peter-evans/create-or-update-comment@v3
        with:
          token: ${{ secrets.RENKUBOT_GITHUB_TOKEN }}
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            You can access the deployment of this PR at https://renku-ci-rp-${{ github.event.number }}.dev.renku.ch

  test-pr:
    runs-on: ubuntu-20.04
    if: ${{ github.event.action != 'closed' && needs.check-deploy.outputs.pr-contains-string == 'true' && needs.check-deploy.outputs.test-enabled == 'true' }}
    needs: [check-deploy, deploy-pr]
    steps:
      - uses: SwissDataScienceCenter/renku-actions/test-renku@v1.9.1
        with:
          kubeconfig: ${{ secrets.RENKUBOT_DEV_KUBECONFIG }}
          renku-release: renku-ci-rp-${{ github.event.number }}
          gitlab-token: ${{ secrets.DEV_GITLAB_TOKEN }}
          persist: "${{ needs.check-deploy.outputs.persist }}"
          s3-results-access-key: ${{ secrets.ACCEPTANCE_TESTS_BUCKET_ACCESS_KEY }}
          s3-results-secret-key: ${{ secrets.ACCEPTANCE_TESTS_BUCKET_SECRET_KEY }}

  cleanup:
    needs: check-deploy
    if: github.event.action == 'closed' && needs.check-deploy.outputs.pr-contains-string == 'true'
    runs-on: ubuntu-20.04
    steps:
      - name: renku teardown
        uses: SwissDataScienceCenter/renku-actions/cleanup-renku-ci-deployments@v1.9.1
        env:
          HELM_RELEASE_REGEX: "^renku-ci-rp-${{ github.event.number }}$"
          GITLAB_TOKEN: ${{ secrets.DEV_GITLAB_TOKEN }}
          RENKUBOT_KUBECONFIG: ${{ secrets.RENKUBOT_DEV_KUBECONFIG }}
          MAX_AGE_SECONDS: 0
          DELETE_NAMESPACE: "true"


Create cheatsheet

name: Create cheatsheet

on:
  push:
    branches:
      - "**"
    tags:
      - "v*.*.*"

jobs:
  cheatsheet:
    runs-on: ubuntu-latest
    if: "'refs/heads/master' != github.ref && 'refs/heads/develop' != github.ref && !startsWith(github.ref, 'refs/tags/')"
    steps:
      - uses: actions/checkout@v3.6.0
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install-linux
      - name: Install xetex
        run: |
          sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu jammy main restricted multiverse universe"
          sudo apt-get update -y
          sudo apt-get install -y -t jammy texlive-xetex
      - name: Build cheatsheet
        run: |
          sphinx-build -b latex -c docs/cheatsheet/ docs docs/latex/ -W
          cd docs/latex/
          xelatex cheatsheet
          cat <(md5sum cheatsheet.tex) <(md5sum sdsc_cheatsheet.cls) > ../cheatsheet_hash_new
          cd ../..
        env:
          CHEATSHEET_TARGET: rp
      - name: Build cheatsheet json
        run: |
          sphinx-build -b json -c docs/cheatsheet/ docs docs/json/ -W
          cd docs/json/
          python -c 'import json; print(json.dumps(json.loads(json.load(open("cheatsheet.fjson", "r"))["body"]), indent=2, sort_keys=True))' > cheatsheet.json
          cat <(md5sum cheatsheet.json) > ../cheatsheet_json_hash_new
          cd ../..
      - name: Publish cheatsheet if changed
        id: publish-pdf
        if: ${{ hashFiles('docs/cheatsheet_hash') != hashFiles('docs/cheatsheet_hash_new') }}
        run: |
          cat docs/cheatsheet_hash
          cat docs/cheatsheet_hash_new
          cd docs
          rm cheatsheet_hash
          mv cheatsheet_hash_new cheatsheet_hash
          cp latex/cheatsheet.pdf _static/cheatsheet/
          git add cheatsheet_hash _static/cheatsheet/cheatsheet.pdf
          git commit -m "chore: Update cheatsheet pdf"
      - name: Publish UI cheatsheet if changed
        id: publish-json
        if: ${{ hashFiles('docs/cheatsheet_json_hash') != hashFiles('docs/cheatsheet_json_hash_new') }}
        run: |
          cat docs/cheatsheet_json_hash
          cat docs/cheatsheet_json_hash_new
          cd docs
          rm cheatsheet_json_hash
          mv cheatsheet_json_hash_new cheatsheet_json_hash
          cp json/cheatsheet.json _static/cheatsheet/
          git add cheatsheet_json_hash _static/cheatsheet/cheatsheet.json
          git commit -m "chore: Update cheatsheet json"
      - name: Push changes
        uses: ad-m/github-push-action@master
        if: (steps.publish-pdf.outcome != 'Skipped' || steps.publish-json.outcome != 'Skipped') && success()
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: ${{ github.ref }}


Combine Dependabot PRs

name: "Combine Dependabot PRs"
on:
  workflow_dispatch:
    inputs:
      target_branch:
        description: Target branch to create a release/PR to
        type: string
        required: true
        default: develop
      branch_name:
        description: Name of the branch to combine PRs into
        type: string
        required: true
        default: combined-prs-branch

jobs:
  combine-prs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3.6.0
        with:
          fetch-depth: 0
          token: "${{ secrets.RENKUBOT_GITHUB_TOKEN }}"
          ref: develop
      - name: Setup local branch and merge tool
        run: |
          git checkout -b "${{ inputs.branch_name }}" || ( git checkout "${{ inputs.branch_name }}" && git branch -u "origin/${{ inputs.branch_name }}" && git pull && git merge develop)
          echo "poetry.lock merge=ours" >> .git/info/attributes
          git config merge.ours.driver true
          git merge --no-edit "origin/${{ inputs.target_branch }}"
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: 3.11
      - name: Install dependencies
        env:
          POETRY_VIRTUALENVS_CREATE: "false"
        shell: bash
        run: |
          python -m pip install --upgrade pip
          python -m pip install poetry
      - name: Set Git config
        shell: bash
        run: |
          git config --global --add user.name "Renku Bot"
          git config --global --add user.email "renku@datascience.ch"
      - name: Merge PRs
        id: merge_prs
        env:
          POETRY_VIRTUALENVS_CREATE: "false"
        run: |
          repo="https://api.github.com/repos/${{ github.repository }}"
          dependabot_pulls=($(curl -s "$repo/pulls?state=open" | jq -r ".[] | select((.head.ref | test(\"dependabot/\"))) | \"\(.head.ref),\(.number)\""))

          pr_body="# Combined PRs\n✅ The following pull requests have been successfully combined on this PR:\n"
          failed=()

          for branch in "${dependabot_pulls[@]}"; do
            branch_arr=(${branch//,/ })
            echo "Merging branch ${branch_arr[0]}"

            if git merge --no-edit "origin/${branch_arr[0]}"; then
              pr_body="$pr_body\n- #${branch_arr[1]}"
            else
              git merge --abort
              git clean -fdx
              git reset --hard
              failed+=(${branch_arr[1]})
            fi
          done

          if (( ${#failed[@]} )); then
            pr_body="$pr_body\n\nFailed to combine:\n"

            for fail in "${failed[@]}"; do
              pr_body="$pr_body\n- #$fail"
            done
          fi
          poetry lock
          git add -A
          git commit -m "update lock file" --no-verify
          git status
          git push origin "${{ inputs.branch_name }}"
          echo "pr_body=$pr_body" >> $GITHUB_OUTPUT
      - name: Create Pull Request
        uses: actions/github-script@v6
        with:
          token: ${{ secrets.RENKUBOT_GITHUB_TOKEN }}
          script: |
            const { repo, owner } = context.repo;
            const result = await github.rest.pulls.create({
              title: 'chore: combined dependency update',
              owner,
              repo,
              head: '${{ inputs.branch_name }}',
              base: '${{ inputs.target_branch }}',
              body: '${{ steps.merge_prs.outputs.pr_body }}'
            });
            github.rest.issues.addLabels({
              owner,
              repo,
              issue_number: result.data.number,
              labels: ['dependencies', 'automated pr']
            });


Add PR and Issues to project

name: Add PR and Issues to project
on:
  pull_request:
    types:
      - opened
  issues:
    types:
      - opened
jobs:
  add_to_project:
    runs-on: ubuntu-latest
    steps:
      - name: Add to renku-python
        uses: actions/add-to-project@main
        with:
          project-url: https://github.com/orgs/SwissDataScienceCenter/projects/45
          github-token: ${{ secrets.RENKUBOT_GITHUB_TOKEN  }}
      - name: Add to triage board
        uses: actions/add-to-project@main
        with:
          project-url: https://github.com/orgs/SwissDataScienceCenter/projects/51
          github-token: ${{ secrets.RENKUBOT_GITHUB_TOKEN  }}
          labeled: status/triage


Label PR as needs documentation

name: "Label PR as needs documentation"
on:
  pull_request:
    types: [opened]

jobs:
  triage:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/labeler@v5.0.0
      with:
        repo-token: "${{ secrets.GITHUB_TOKEN }}"


Publish SHACL shape for current version

name: Publish SHACL shape for current version

on:
  push:
    branches:
      - master
    paths:
      - 'renku/data/shacl_shape.json'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3.6.0
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install-linux
        with:
          python-version: "3.10"
      - name: Publish shape
        run: |
          git clone https://${{secrets.RENKUBOT_GITHUB_TOKEN}}@github.com/SwissDataScienceCenter/renku-ontology.git shacl_publish
          cd shacl_publish
          mkdir $(renku --version)
          cp ../renku/data/shacl_shape.json ./$(renku --version)/
          git config --local user.email "renku@datascience.ch"
          git config --local user.name "RenkuBot"
          git add $(renku --version)/shacl_shape.json
          git commit -m "Publish SHACL shape for $(renku --version)"
          git push


Publish final pypi release

name: "Publish final pypi release"
on:
  workflow_dispatch:
    inputs:
      version:
        description: New release version (e.g. '2.3.1')
        type: string
        required: true

jobs:
  finalize-release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3.6.0
        with:
          fetch-depth: 0
          token: "${{ secrets.RENKUBOT_GITHUB_TOKEN }}"
          ref: 'refs/tags/v${{ github.event.inputs.version }}'
      - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/*
      - name: Set up Python 3.9
        uses: actions/setup-python@v5
        with:
          python-version: "3.9"
      - name: Install dependencies
        env:
          POETRY_VIRTUALENVS_CREATE: false
        run: |
          python -m pip install --upgrade pip
          python -m pip install poetry poetry-lock-package twine
          poetry self add "poetry-dynamic-versioning[plugin]"
          poetry install -E service --with dev,tests,docs
          git config --global --add user.name "Renku Bot"
          git config --global --add user.email "renku@datascience.ch"
      - name: Build and publish to pypi
        env:
          POETRY_VIRTUALENVS_CREATE: false
          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
        run: make publish-package
      - name: Build and publish lock package
        env:
          POETRY_VIRTUALENVS_CREATE: false
          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
        run: make publish-lock-package


Create Release PR

name: "Create Release PR"
on:
  workflow_dispatch:
    inputs:
      version:
        description: New release version (e.g. 2.3.1)
        type: string
        required: true
      ref:
        description: Source branch/ref to create a release from
        type: string
        required: true
        default: develop
      target_branch:
        description: Target branch to create a release/PR to
        type: string
        required: true
        default: master

jobs:
  create-release-pr:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3.6.0
        with:
          fetch-depth: 0
          token: "${{ secrets.RENKUBOT_GITHUB_TOKEN }}"
          ref: '${{ github.event.inputs.ref }}'
      - uses: actions/setup-node@v3
      - name: Create Branch
        run: git checkout -b "release/v${{ github.event.inputs.version }}"
      - name: Merge Target
        run: git merge "origin/${{ github.event.inputs.target_branch }}"
      - name: Install dependencies
        run: |
          sudo add-apt-repository -y ppa:rmescandon/yq
          sudo apt-get update -y
          sudo apt-get install -y pandoc yq
          npm install -g conventional-changelog-cli
      - name: Update changelog
        id: changelog
        run: |
          echo '{"version": "${{ github.event.inputs.version }}"}' > context.json
          conventional-changelog -r 1 -p angular -c context.json | sed -e '1,${s/^# /## /}' | pandoc --from markdown --to rst | sed -e '/=======/r /dev/stdin' -e '0,/=======/ s/=======/=======\n/' -i CHANGES.rst
          conventional-changelog -r 1 -p angular -c context.json | sed -e '1,${s/^# /## /}' > release_body.md
          rm context.json
      - name: Update Chart version
        run: |
          yq eval -i '.version = "${{ github.event.inputs.version }}"' helm-chart/renku-core/Chart.yaml
      - name: Update Values file version
        run: |
          yq eval -i '.versions.latest.image.tag = "v${{ github.event.inputs.version }}"' helm-chart/renku-core/values.yaml
      - name: commit changes
        run: |
          git config --global --add user.name "Renku Bot"
          git config --global --add user.email "renku@datascience.ch"
          git config push.autoSetupRemote true
          git add helm-chart/renku-core/values.yaml helm-chart/renku-core/Chart.yaml renku/version.py CHANGES.rst
          git commit -m "chore: release v${{ github.event.inputs.version }}"
          git push
      - uses: ncipollo/release-action@v1
        with:
          token: ${{ secrets.RENKUBOT_GITHUB_TOKEN }}
          draft: true
          bodyFile: "release_body.md"
          tag: "v${{ github.event.inputs.version }}"
          name: "v${{ github.event.inputs.version }}"
          commit: "${{ github.event.inputs.target_branch }}"
      - name: Create Pull Request
        uses: actions/github-script@v6
        with:
          token: ${{ secrets.RENKUBOT_GITHUB_TOKEN }}
          script: |
            const { repo, owner } = context.repo;
            const result = await github.rest.pulls.create({
              title: 'chore: release v${{ github.event.inputs.version }}',
              owner,
              repo,
              head: 'release/v${{ github.event.inputs.version }}',
              base: '${{ github.event.inputs.target_branch }}',
              body: [
                'This PR is auto-generated by',
                '[actions/github-script](https://github.com/actions/github-script).'
              ].join('\n')
            });
            github.rest.issues.addLabels({
              owner,
              repo,
              issue_number: result.data.number,
              labels: ['feature', 'automated pr']
            });


Conventional Commit PR Message

name: "Conventional Commit PR Message"
on:
  pull_request:
    types:
      - opened
      - edited
      - synchronize

jobs:
  main:
    runs-on: ubuntu-latest
    steps:
      - uses: amannn/action-semantic-pull-request@v5.4.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}