Skip to content
Closed
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 93 additions & 0 deletions .github/workflows/dependency-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Dependency Update

on:
schedule:
# Every Monday at 03:00 UTC
- cron: "0 3 * * 1"
workflow_dispatch:
workflow_call:

jobs:
dependency-update:
name: Dependency Update
runs-on: "ubuntu-24.04"
permissions:
contents: write
pull-requests: write

steps:
- name: Check out Repository
id: check-out-repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python & Poetry Environment
id: set-up-python-and-poetry-environment
uses: exasol/python-toolbox/.github/actions/python-environment@v6
with:
python-version: "3.10"
poetry-version: "2.3.0"

- name: Audit Dependencies
id: audit-dependencies
run: |
poetry run -- nox -s dependency:audit | tee vulnerabilities.json
LENGTH=$(jq 'length' vulnerabilities.json)
echo "count=$LENGTH" >> "$GITHUB_OUTPUT"

- name: Update Dependencies
id: update-dependencies
if: steps.audit-dependencies.outputs.count > 0
run: poetry update

- name: Check for poetry.lock Changes
id: check-for-poetry-lock-changes
if: steps.audit-dependencies.outputs.count > 0
run: |
if git diff --quiet -- poetry.lock; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Configure git
id: configure-git
if: steps.check-for-poetry-lock-changes.outputs.changed == 'true'
run: |
git config --global user.email "opensource@exasol.com"
git config --global user.name "Automatic Dependency Updater"

- name: Create branch
id: create-branch
if: steps.check-for-poetry-lock-changes.outputs.changed == 'true'
run: |
branch_name="dependency-update/$(date "+%Y%m%d%H%M%S")"
echo "Creating branch $branch_name"
git checkout -b "$branch_name"

- name: Commit changes & push
id: publish-branch
if: steps.check-for-poetry-lock-changes.outputs.changed == 'true'
run: |
branch_name=$(git rev-parse --abbrev-ref HEAD)
git add poetry.lock
git commit --message "Update poetry.lock"
git push --set-upstream origin "$branch_name"

- name: Create pull request
id: create-pr
if: steps.check-for-poetry-lock-changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |-
BASE_BRANCH=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name)

gh pr create \
--base "$BASE_BRANCH" \
--title "Update poetry.lock" \
--body "Automated dependency update for \`poetry.lock\`.

This PR was created by the dependency update workflow after running:
- \`poetry run -- nox -s dependency:audit\`
- \`poetry update\`"
14 changes: 14 additions & 0 deletions doc/user_guide/features/github_workflows/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ Workflows
- Pull request and monthly
- Executes the continuous integration suite by calling ``merge-gate.yml`` and
``report.yml``. See :ref:`ci_yml` for a graph of workflow calls.
* - ``dependency-update.yml``
- Weekly and manual
- Audits project dependencies for known vulnerabilities, updates them with Poetry when needed, and creates a pull request if ``poetry.lock`` changes.
* - ``gh-pages.yml``
- Workflow call
- Builds the documentation and deploys it to GitHub Pages.
Expand Down Expand Up @@ -97,6 +100,17 @@ Workflows
CI Actions
----------

Dependency Update
^^^^^^^^^^^^^^^^^

The ``dependency-update.yml`` workflow helps keep project dependencies up to date.

It can be triggered manually and is also scheduled to run weekly.

The workflow first audits dependencies for known vulnerabilities. If vulnerabilities
are detected, it updates the dependencies using Poetry. When ``poetry.lock`` changes,
it creates a pull request with the update.

.. _ci_yml:

Pull Request
Expand Down
92 changes: 92 additions & 0 deletions exasol/toolbox/templates/github/workflows/dependency-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Dependency Update

on:
schedule:
# Every Monday at 03:00 UTC
- cron: "0 3 * * 1"
workflow_dispatch:

jobs:
dependency-update:
name: Dependency Update
runs-on: "(( os_version ))"
permissions:
contents: write
pull-requests: write

steps:
- name: Check out Repository
id: check-out-repository
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python & Poetry Environment
id: set-up-python-and-poetry-environment
uses: exasol/python-toolbox/.github/actions/python-environment@v6
with:
python-version: "(( minimum_python_version ))"
poetry-version: "(( dependency_manager_version ))"

- name: Audit Dependencies
id: audit-dependencies
run: |
poetry run -- nox -s dependency:audit | tee vulnerabilities.json
LENGTH=$(jq 'length' vulnerabilities.json)
echo "count=$LENGTH" >> "$GITHUB_OUTPUT"

- name: Update Dependencies
id: update-dependencies
if: steps.audit-dependencies.outputs.count > 0
run: poetry update

- name: Check for poetry.lock Changes
id: check-for-poetry-lock-changes
if: steps.audit-dependencies.outputs.count > 0
run: |
if git diff --quiet -- poetry.lock; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Configure git
id: configure-git
if: steps.check-for-poetry-lock-changes.outputs.changed == 'true'
run: |
git config --global user.email "opensource@exasol.com"
git config --global user.name "Automatic Dependency Updater"

- name: Create branch
id: create-branch
if: steps.check-for-poetry-lock-changes.outputs.changed == 'true' && github.ref == 'refs/heads/main'
run: |
branch_name="dependency-update/$(date "+%Y%m%d%H%M%S")"
echo "Creating branch $branch_name"
git checkout -b "$branch_name"

- name: Commit changes & push
id: publish-branch
if: steps.check-for-poetry-lock-changes.outputs.changed == 'true' && startsWith(github.ref, 'refs/heads/')
run: |
branch_name=$(git rev-parse --abbrev-ref HEAD)
git add poetry.lock
git commit --message "Update poetry.lock"
git push --set-upstream origin "$branch_name"

- name: Create pull request
id: create-pr
if: steps.check-for-poetry-lock-changes.outputs.changed == 'true' && github.ref == 'refs/heads/main'
env:
GH_TOKEN: ${{ github.token }}
run: |
BASE_BRANCH=$(gh repo view --json defaultBranchRef -q .defaultBranchRef.name)

gh pr create \
--base "$BASE_BRANCH" \
--title "Update poetry.lock" \
--body "Automated dependency update for \`poetry.lock\`.

This PR was created by the dependency update workflow after running:
- \`poetry run -- nox -s dependency:audit\`
- \`poetry update\`"
2 changes: 1 addition & 1 deletion exasol/toolbox/util/dependencies/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def load_from_pip_audit(cls, working_directory: Path) -> Vulnerabilities:

vulnerabilities = []
for entry in audit_dict["dependencies"]:
for vuln_entry in entry["vulns"]:
for vuln_entry in entry.get("vulns", []):
vulnerabilities.append(
Vulnerability.from_audit_entry(
package_name=entry["name"],
Expand Down
6 changes: 3 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion test/integration/project-template/nox_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,4 @@ def test_install_github_workflows(self, poetry_path, run_command):
assert output.returncode == 0

file_list = run_command(["ls", ".github/workflows"]).stdout.splitlines()
assert len(file_list) == 13
assert len(file_list) == 14
2 changes: 1 addition & 1 deletion test/unit/nox/_workflow_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class TestGenerateWorkflow:
@staticmethod
@pytest.mark.parametrize(
"nox_session_runner_posargs, expected_count",
[(ALL, 13), *[(key, 1) for key in WORKFLOW_TEMPLATE_OPTIONS.keys()]],
[(ALL, 14), *[(key, 1) for key in WORKFLOW_TEMPLATE_OPTIONS.keys()]],
indirect=["nox_session_runner_posargs"],
)
def test_works_as_expected(
Expand Down
8 changes: 7 additions & 1 deletion test/unit/util/dependencies/audit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,13 @@ class TestVulnerabilities:
@staticmethod
def test_with_no_vulnerabilities():
pip_audit_dict = {
"dependencies": [{"name": "alabaster", "version": "0.7.16", "vulns": []}]
"dependencies": [
{
"name": "exasol-toolbox",
"skip_reason": "Dependency not found on PyPI and could not be audited: exasol-toolbox (7.0.0)",
},
{"name": "alabaster", "version": "0.7.16", "vulns": []},
]
}
pip_audit_json = json.dumps(pip_audit_dict)

Expand Down
1 change: 1 addition & 0 deletions test/unit/util/workflows/templates_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def test_get_workflow_templates(project_config):
"check-release-tag",
"checks",
"ci",
"dependency-update",
"gh-pages",
"matrix-all",
"matrix-exasol",
Expand Down