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
110 changes: 107 additions & 3 deletions .github/workflows/published-worker-metrics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ name: Published worker metrics

on:
pull_request:
paths: ['.github/workflows/published-worker-metrics.yml', 'tests/smoke/worker-metrics.php', 'tests/smoke/update-outcome.php', 'tests/smoke/workflow-completion-limits.php', 'tests/smoke/query-results.php', 'tests/ci/test_published_query_tuple.py']
paths: ['.github/workflows/published-worker-metrics.yml', 'tests/smoke/worker-metrics.php', 'tests/smoke/update-outcome.php', 'tests/smoke/workflow-completion-limits.php', 'tests/smoke/query-results.php', 'tests/ci/test_published_query_tuple.py', 'tests/ci/check_published_laravel.py', 'tests/ci/test_published_laravel.py']
push:
branches: [main]
paths: ['.github/workflows/published-worker-metrics.yml', 'tests/smoke/worker-metrics.php', 'tests/smoke/update-outcome.php', 'tests/smoke/workflow-completion-limits.php', 'tests/smoke/query-results.php', 'tests/ci/test_published_query_tuple.py']
paths: ['.github/workflows/published-worker-metrics.yml', 'tests/smoke/worker-metrics.php', 'tests/smoke/update-outcome.php', 'tests/smoke/workflow-completion-limits.php', 'tests/smoke/query-results.php', 'tests/ci/test_published_query_tuple.py', 'tests/ci/check_published_laravel.py', 'tests/ci/test_published_laravel.py']
workflow_dispatch:
inputs:
sdk_version:
Expand All @@ -20,6 +20,14 @@ on:
description: Qualify binary and JSON query results with the installed Worker
type: boolean
default: false
laravel_bridges:
description: Qualify registry SDK installation on both patched Laravel floors
type: boolean
default: false
expected_sdk_reference:
description: Exact published SDK source reference required by Laravel qualification
type: string
default: ''
candidate_source:
description: Optional exact SDK commit for prepublication dist qualification only
type: string
Expand All @@ -46,7 +54,9 @@ jobs:
with:
persist-credentials: false
- name: Require the exact published query tuple selectors
run: python3 -I -S -B tests/ci/test_published_query_tuple.py
run: |
python3 -I -S -B tests/ci/test_published_query_tuple.py
python3 -S -B -m unittest discover -s tests/ci -p test_published_laravel.py
- name: Create isolated Composer state
run: |
state="$(mktemp -d "$RUNNER_TEMP/worker-metrics.XXXXXX")"
Expand Down Expand Up @@ -180,3 +190,97 @@ jobs:
- name: Remove the isolated Server and its volumes
if: always()
run: docker rm --force --volumes sdk-php-metrics-runtime

installed-laravel-bridges:
if: github.event_name == 'workflow_dispatch' && inputs.laravel_bridges
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
laravel: ['12.72.0', '13.41.0']
defaults:
run:
shell: bash --noprofile --norc -e -o pipefail {0}
env:
SDK_VERSION: ${{ inputs.sdk_version }}
SDK_REFERENCE: ${{ inputs.expected_sdk_reference }}
LARAVEL_VERSION: ${{ matrix.laravel }}
steps:
- name: Require a registry-only main invocation
env:
CANDIDATE_SOURCE: ${{ inputs.candidate_source }}
run: |
[[ "$GITHUB_REF" = refs/heads/main && -z "$CANDIDATE_SOURCE" ]]
[[ "$SDK_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
[[ "$SDK_REFERENCE" =~ ^[0-9a-f]{40}$ ]]
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- name: Select Ubuntu's Python 3.12
run: |
/usr/bin/python3 -I -S -c 'import sys; assert sys.version_info[:2] == (3, 12)'
echo /usr/bin >> "$GITHUB_PATH"
- name: Create the clean registry consumer
run: |
state="$(mktemp -d "$RUNNER_TEMP/published-laravel.XXXXXX")"
mkdir -m 700 "$state/home" "$state/cache" "$state/tmp" "$state/consumer"
{
echo "COMPOSER_HOME=$state/home"
echo "COMPOSER_CACHE_DIR=$state/cache"
echo "TMPDIR=$state/tmp"
echo "LARAVEL_CONSUMER=$state/consumer"
} >> "$GITHUB_ENV"
python3 -I -S -B - "$state/consumer/composer.json" <<'PY'
import json,os,pathlib,sys
pathlib.Path(sys.argv[1]).write_text(json.dumps({
'name':'durable-workflow/published-laravel',
'require':{'durable-workflow/sdk':os.environ['SDK_VERSION'],'laravel/framework':os.environ['LARAVEL_VERSION']},
'config':{'allow-plugins':{}}
})+'\n')
PY
- name: Select authenticated dependency trust
uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 # clean-trust
with:
clients: python,composer,curl,git
- name: Set up the qualified PHP profile
uses: durable-workflow/.github/.github/actions/setup-php-linux@411a14ac07d32073eed6ab4977f2825eba4739a7 # bound-linux-php
with:
php-version: '8.3'
ini-file: production
tools: composer:2.10.3,phpunit:11.5.57
coverage: none
- name: Verify Composer trust before registry installation
uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 # clean-trust
with:
working-directory: ${{ env.LARAVEL_CONSUMER }}
- name: Install the exact registry SDK and Laravel floor
run: composer install --working-dir="$LARAVEL_CONSUMER" --no-scripts --no-plugins --no-interaction --prefer-dist --no-progress
- name: Verify Composer trust before the locked audit
uses: durable-workflow/.github/.github/actions/verify-composer-trust@242c2a7fb23459db2e1ada5f84dbee6d61d9b1e5 # clean-trust
with:
working-directory: ${{ env.LARAVEL_CONSUMER }}
- name: Audit the actual installed graph
run: composer audit --working-dir="$LARAVEL_CONSUMER" --locked --format=json > laravel-audit.json
- name: Exercise the installed Laravel bridge and cached roles
env:
DURABLE_WORKFLOW_CLIENT_TOKEN: client-secret
DURABLE_WORKFLOW_WORKER_TOKEN: worker-secret
run: |
php scripts/ci/assert-installed-version.php "$LARAVEL_CONSUMER" durable-workflow/sdk "$SDK_VERSION"
php scripts/ci/assert-installed-version.php "$LARAVEL_CONSUMER" laravel/framework "$LARAVEL_VERSION"
python3 -I -S -B tests/ci/check_published_laravel.py "$LARAVEL_CONSUMER" "$SDK_VERSION" "$SDK_REFERENCE" "$LARAVEL_VERSION" > laravel-tuple.json
php tests/compat/laravel.php "$LARAVEL_CONSUMER/vendor/autoload.php"
php tests/compat/laravel-config-cache.php "$LARAVEL_CONSUMER/vendor/autoload.php"
- name: Retain the registry tuple and installed graph
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: published-laravel-${{ matrix.laravel }}
path: |
laravel-tuple.json
laravel-audit.json
${{ env.LARAVEL_CONSUMER }}/composer.json
${{ env.LARAVEL_CONSUMER }}/composer.lock
${{ env.LARAVEL_CONSUMER }}/vendor/composer/installed.json
retention-days: 14
58 changes: 58 additions & 0 deletions tests/ci/check_published_laravel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Bind Laravel bridge observations to the requested registry distributions."""

import json
from pathlib import Path
import re
import sys


FRAMEWORKS = {
"12.72.0": "b089d3020e508f49b3625726ab4a2c897f1b78d5",
"13.41.0": "95934d2868c90c1aa8e711791ea33d1e990e1719",
}


def package(rows, name):
matches = [row for row in rows if row.get("name") == name]
if len(matches) != 1:
raise ValueError(f"{name} must occur exactly once")
return matches[0]


def check(root, sdk_version, sdk_reference, laravel_version):
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", sdk_version):
raise ValueError("An exact stable SDK version is required")
if not re.fullmatch(r"[0-9a-f]{40}", sdk_reference):
raise ValueError("An exact SDK source reference is required")
framework_reference = FRAMEWORKS[laravel_version]
manifest = json.loads((root / "composer.json").read_text())
if "repositories" in manifest:
raise ValueError("Registry qualification cannot override repositories")
lock = json.loads((root / "composer.lock").read_text())
installed = json.loads((root / "vendor/composer/installed.json").read_text())
for name, version, reference in [
("durable-workflow/sdk", sdk_version, sdk_reference),
("laravel/framework", laravel_version, framework_reference),
]:
if manifest["require"].get(name) != version:
raise ValueError(f"{name} must have an exact root requirement")
for rows in [lock["packages"], installed["packages"]]:
row = package(rows, name)
if row["version"].removeprefix("v") != version:
raise ValueError(f"{name} version mismatch")
if row["source"]["reference"] != reference:
raise ValueError(f"{name} source mismatch")
if row["dist"]["reference"] != reference or row["dist"]["type"] != "zip":
raise ValueError(f"{name} distribution mismatch")
if package(installed["packages"], name).get("installation-source") != "dist":
raise ValueError(f"{name} was not installed from its distribution")
sdk = json.loads((root / "vendor/durable-workflow/sdk/composer.json").read_text())
if sdk["extra"]["durable-workflow"]["product-train"] != sdk_version:
raise ValueError("Installed SDK product train mismatch")
return {"sdk_version": sdk_version, "sdk_reference": sdk_reference,
"laravel_version": laravel_version, "laravel_reference": framework_reference,
"source_fallback": False, "repository_override": False}


if __name__ == "__main__":
print(json.dumps(check(Path(sys.argv[1]), *sys.argv[2:5])))
62 changes: 62 additions & 0 deletions tests/ci/test_published_laravel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import copy
import json
from pathlib import Path
import tempfile
import unittest

from check_published_laravel import check, FRAMEWORKS


class PublishedLaravelTest(unittest.TestCase):
def setUp(self):
self.directory = tempfile.TemporaryDirectory()
self.addCleanup(self.directory.cleanup)
self.root = Path(self.directory.name)
self.version = "3.18.3"
self.reference = "a" * 40
self.manifest = {"require": {"durable-workflow/sdk": self.version, "laravel/framework": "12.72.0"}}
self.rows = []
for name, version, reference in [("durable-workflow/sdk", self.version, self.reference),
("laravel/framework", "v12.72.0", FRAMEWORKS["12.72.0"])]:
self.rows.append({"name": name, "version": version, "source": {"reference": reference},
"dist": {"reference": reference, "type": "zip"}, "installation-source": "dist"})

def write(self, rows=None):
documents = {"composer.json": self.manifest, "composer.lock": {"packages": self.rows},
"vendor/composer/installed.json": {"packages": rows if rows is not None else self.rows},
"vendor/durable-workflow/sdk/composer.json": {"extra": {"durable-workflow": {"product-train": self.version}}}}
for name, document in documents.items():
path = self.root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(document))

def test_matching_registry_install(self):
self.write()
self.assertFalse(check(self.root, self.version, self.reference, "12.72.0")["source_fallback"])

def test_installed_metadata_cannot_borrow_the_lock_identity(self):
for name in ["durable-workflow/sdk", "laravel/framework"]:
for mutation in ["version", "source", "dist", "installation-source"]:
with self.subTest(name=name, mutation=mutation):
rows = copy.deepcopy(self.rows)
row = next(row for row in rows if row["name"] == name)
if mutation in ["source", "dist"]:
row[mutation]["reference"] = "b" * 40
else:
row[mutation] = "source" if mutation == "installation-source" else "99.0.0"
self.write(rows)
with self.assertRaises(ValueError):
check(self.root, self.version, self.reference, "12.72.0")

def test_duplicate_and_repository_override_refuse(self):
self.write(self.rows + [self.rows[0]])
with self.assertRaises(ValueError):
check(self.root, self.version, self.reference, "12.72.0")
self.manifest["repositories"] = [{"type": "path", "url": "/synthetic"}]
self.write()
with self.assertRaises(ValueError):
check(self.root, self.version, self.reference, "12.72.0")


if __name__ == "__main__":
unittest.main()
Loading