Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions .github/workflows/bump-homebrew.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,48 @@ jobs:
run: |
set -euo pipefail

brew bump-formula-pr \
--write-only --no-audit --no-browse \
--url="${SDIST_URL}" \
--sha256="${SDIST_SHA}" \
FORMULA_FILE="tap/Formula/${FORMULA##*/}.rb"
FORMULA_FILE="${FORMULA_FILE}" \
SDIST_URL="${SDIST_URL}" \
SDIST_SHA="${SDIST_SHA}" \
VERSION="${VERSION}" \
python - <<'PY'
import os
import re
from pathlib import Path
from urllib.parse import unquote, urlparse

formula_file = Path(os.environ["FORMULA_FILE"])
url = os.environ["SDIST_URL"]
sha256 = os.environ["SDIST_SHA"]
version = os.environ["VERSION"]
expected_filename = f"cisco_sccfm_devkit-{version}.tar.gz"
parsed_url = urlparse(url)
if parsed_url.scheme != "https" or parsed_url.netloc != "files.pythonhosted.org":
raise SystemExit("PyPI sdist URL is not hosted by files.pythonhosted.org")
if Path(unquote(parsed_url.path)).name != expected_filename:
raise SystemExit("PyPI sdist URL does not match the release version")
if re.fullmatch(r"[0-9a-f]{64}", sha256) is None:
raise SystemExit("PyPI sdist SHA-256 is invalid")

text = formula_file.read_text(encoding="utf-8")
url_match = re.search(r'(?m)^ url "([^"]+)"$', text)
sha_match = re.search(r'(?m)^ sha256 "([^"]+)"$', text)
if url_match is None or sha_match is None:
raise SystemExit("formula stable URL or SHA-256 is missing")
if not url_match.group(1).startswith("https://files.pythonhosted.org/"):
raise SystemExit("formula stable URL is not hosted by files.pythonhosted.org")

updated = text[:url_match.start(1)] + url + text[url_match.end(1):]
sha_offset = sha_match.start(1) + (len(updated) - len(text))
sha_end = sha_match.end(1) + (len(updated) - len(text))
updated = updated[:sha_offset] + sha256 + updated[sha_end:]
formula_file.write_text(updated, encoding="utf-8")
PY

brew update-python-resources \
--ignore-main-package-cooldown \
--version="${VERSION}" \
"${FORMULA}"


Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ jobs:
github.event_name == 'push' &&
steps.version.outputs.bumped == 'true' &&
steps.version.outputs.resume != 'true'
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.source.outputs.bundle_name }}
path: |
Expand Down Expand Up @@ -686,7 +686,7 @@ jobs:
python-version: "3.12"

- name: Download exact release bundle
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: ${{ needs.prepare-release.outputs.bundle_name }}
path: ${{ runner.temp }}/release-bundle
Expand Down
44 changes: 33 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ jobs:
echo "bundle_name=${BUNDLE_NAME}" >> "$GITHUB_OUTPUT"

- name: Preserve verified release bundle
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ steps.bundle.outputs.bundle_name }}
path: |
Expand Down Expand Up @@ -167,7 +167,7 @@ jobs:
python -m pip install twine==6.2.0

- name: Download verified release bundle
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: ${{ needs.validate-release.outputs.bundle_name }}
path: ${{ runner.temp }}/release-bundle
Expand Down Expand Up @@ -277,10 +277,21 @@ jobs:

INSTALL_ROOT="$(mktemp -d "${RUNNER_TEMP}/sccfm-pypi-install.XXXXXX")"
python -m venv "${INSTALL_ROOT}/venv"
"${INSTALL_ROOT}/venv/bin/python" -I -m pip install \
--no-cache-dir \
--index-url https://pypi.org/simple \
"cisco-sccfm-devkit==${RELEASE_VERSION}"
for attempt in $(seq 1 60); do
if "${INSTALL_ROOT}/venv/bin/python" -I -m pip install \
--no-cache-dir \
--index-url https://pypi.org/simple \
"cisco-sccfm-devkit==${RELEASE_VERSION}"; then
break
fi
if [[ "${attempt}" = "60" ]]; then
echo "::error::PyPI Simple API did not expose ${RELEASE_VERSION} in time"
exit 1
fi
echo "PyPI Simple API has not exposed ${RELEASE_VERSION} yet" \
"(attempt ${attempt}); waiting 5s..."
sleep 5
done
"${INSTALL_ROOT}/venv/bin/python" -I -m pip check
"${INSTALL_ROOT}/venv/bin/sccfm-cli" --help >/dev/null
"${INSTALL_ROOT}/venv/bin/sccfm-cli-interactive" --help >/dev/null
Expand Down Expand Up @@ -313,7 +324,7 @@ jobs:
python -m pip install "ansible-core>=2.20,<2.22"

- name: Download verified release bundle
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: ${{ needs.validate-release.outputs.bundle_name }}
path: ${{ runner.temp }}/release-bundle
Expand Down Expand Up @@ -433,10 +444,21 @@ jobs:
cmp -s "${COLLECTION_PATH}" "${DOWNLOADED_COLLECTION}"

INSTALL_ROOT="$(mktemp -d "${RUNNER_TEMP}/sccfm-galaxy-install.XXXXXX")"
python -m pip install \
--no-cache-dir \
--index-url https://pypi.org/simple \
"cisco-sccfm-devkit==${RELEASE_VERSION}"
for attempt in $(seq 1 60); do
if python -m pip install \
--no-cache-dir \
--index-url https://pypi.org/simple \
"cisco-sccfm-devkit==${RELEASE_VERSION}"; then
break
fi
if [[ "${attempt}" = "60" ]]; then
echo "::error::PyPI Simple API did not expose ${RELEASE_VERSION} in time"
exit 1
fi
echo "PyPI Simple API has not exposed ${RELEASE_VERSION} yet" \
"(attempt ${attempt}); waiting 5s..."
sleep 5
done
ansible-galaxy collection install \
"${DOWNLOADED_COLLECTION}" \
--collections-path "${INSTALL_ROOT}" \
Expand Down
12 changes: 6 additions & 6 deletions tests/test_release_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def test_workflows_separate_automatic_preparation_from_manual_deployment() -> No
assert "release-manifest-sha256:" in validation
assert "release_artifacts verify" in validation
assert "contents: write" in validation
assert "actions/upload-artifact@v4" in validation
assert "actions/upload-artifact@v7" in validation
assert "bundle_name: ${{ steps.bundle.outputs.bundle_name }}" in validation
assert "name: ${{ steps.bundle.outputs.bundle_name }}" in validation
assert "GITHUB_RUN_ID" in validation
Expand All @@ -262,7 +262,7 @@ def test_workflows_separate_automatic_preparation_from_manual_deployment() -> No
assert "secrets.PYPI_API_TOKEN" not in validation
assert "secrets.GALAXY_API_KEY" not in validation
assert validation.index("release_artifacts verify") < validation.index(
"actions/upload-artifact@v4"
"actions/upload-artifact@v7"
)

prohibited_deploy_commands = (
Expand All @@ -285,18 +285,18 @@ def test_workflows_separate_automatic_preparation_from_manual_deployment() -> No
for job in (pypi, galaxy):
assert "contents: read" in job
assert "contents: write" not in job
assert job.count("actions/download-artifact@v4") == 1
assert job.count("actions/download-artifact@v7") == 1
assert "name: ${{ needs.validate-release.outputs.bundle_name }}" in job
assert 'gh release download "${RELEASE_TAG}"' not in job
assert "GH_TOKEN:" not in job
assert "secrets.GITHUB_TOKEN" not in job
assert "release_artifacts verify" in job
assert "EXPECTED_MANIFEST_SHA256" in job
assert job.index("actions/download-artifact@v4") < job.index("release_artifacts verify")
assert job.index("actions/download-artifact@v7") < job.index("release_artifacts verify")

assert "contents: write" in finalizer
assert release.count("actions/upload-artifact@v4") == 1
assert release.count("actions/download-artifact@v4") == 2
assert release.count("actions/upload-artifact@v7") == 1
assert release.count("actions/download-artifact@v7") == 2

assert "pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33" in pypi
assert "pypa/gh-action-pypi-publish@release/v1" not in pypi
Expand Down