| author | Alan Dipert
<alan@tailrecursion.com> 2026-11-11 18:09:24 UTC |
| committer | Alan Dipert
<alan@tailrecursion.com> 2026-11-11 18:09:24 UTC |
| parent | c05cd69ca1b454c5e0736ab4f59a9f9fb587125a |
| tests/test_deploy.py | +16 | -8 |
| tools/deploy.py | +8 | -2 |
diff --git a/tests/test_deploy.py b/tests/test_deploy.py index c983618..6ed9e4d 100644 --- a/tests/test_deploy.py +++ b/tests/test_deploy.py @@ -12,12 +12,15 @@ class DeployTests(unittest.TestCase): def setUp(self): self.enterContext(patch.dict(os.environ, { 'GITHUB_REPOSITORY': 'alan/homepage', 'GITHUB_SHA': 'a' * 40, - 'GITHUB_REF': 'refs/heads/master', 'GITHUB_EVENT_NAME': 'workflow_dispatch', + 'GITHUB_REF': 'refs/heads/master', 'GITHUB_EVENT_NAME': 'workflow_call', + 'GITHUB_RUN_ID': '99', 'CHECK_TOKEN': 'check-fixture', 'PUBLISH_TOKEN': 'publish-fixture', 'SITE_DEPLOY_KEY': 'key-fixture'})) + self.invocation = {'workflow_id': 'deploy.yml', 'trigger_event': 'workflow_dispatch', + 'commit_sha': 'a' * 40, 'prettyref': 'master'} self.ci_run = {'id': 42, 'status': 'success', 'commit_sha': 'a' * 40, - 'prettyref': 'master', 'workflow_id': 'ci.yml', - 'event': '', 'trigger_event': 'workflow_dispatch'} + 'prettyref': 'master', 'workflow_id': 'ci.yml', + 'event': '', 'trigger_event': 'workflow_dispatch'} def test_requires_the_latest_successful_master_check(self): for change in ({}, {'status': 'failure'}, {'status': 'running'}, {'status': 'waiting'}, @@ -25,7 +28,7 @@ class DeployTests(unittest.TestCase): {'status': 'unknown'}, {'commit_sha': 'b' * 40}, {'prettyref': 'branch'}, {'workflow_id': 'deploy.yml'}, {'trigger_event': 'pull_request'}): with self.subTest(change=change), patch('deploy.api', side_effect=[ - {'commit': {'id': 'a' * 40}}, + {'commit': {'id': 'a' * 40}}, self.invocation, {'workflow_runs': [self.ci_run | change, self.ci_run]}]): if change: with self.assertRaises(ValueError): @@ -33,15 +36,20 @@ class DeployTests(unittest.TestCase): else: self.assertEqual(deploy.checked_source(), 42) for responses in ([{'commit': {'id': 'b' * 40}}], - [{'commit': {'id': 'a' * 40}}, {'workflow_runs': []}]): + [{'commit': {'id': 'a' * 40}}, self.invocation, {'workflow_runs': []}]): with self.subTest(responses=responses), patch('deploy.api', side_effect=responses): with self.assertRaises(ValueError): deploy.checked_source() - for change in ({'GITHUB_REF': 'refs/heads/branch'}, {'GITHUB_EVENT_NAME': 'push'}): - with patch.dict(os.environ, change), patch('deploy.api') as api: + for change in ({'trigger_event': 'push'}, {'workflow_id': 'ci.yml'}, + {'commit_sha': 'b' * 40}, {'prettyref': 'branch'}): + with self.subTest(invocation=change), patch('deploy.api', side_effect=[ + {'commit': {'id': 'a' * 40}}, self.invocation | change]): with self.assertRaises(ValueError): deploy.checked_source() - api.assert_not_called() + with patch.dict(os.environ, {'GITHUB_REF': 'refs/heads/branch'}), patch('deploy.api') as api: + with self.assertRaises(ValueError): + deploy.checked_source() + api.assert_not_called() def test_dry_run_and_deploy_keep_credentials_private_and_order_effects(self): for dry_run in (True, False): diff --git a/tools/deploy.py b/tools/deploy.py index 3915f70..07073e3 100644 --- a/tools/deploy.py +++ b/tools/deploy.py @@ -23,11 +23,17 @@ def api(path, token, body=None): def checked_source(): repository, commit, ref = (os.environ[key] for key in ('GITHUB_REPOSITORY', 'GITHUB_SHA', 'GITHUB_REF')) - if ref != 'refs/heads/master' or os.environ['GITHUB_EVENT_NAME'] != 'workflow_dispatch': - raise ValueError('Deploy must be run manually on master') + if ref != 'refs/heads/master': + raise ValueError('Deploy must run on master') token = os.environ['CHECK_TOKEN'] if api(repository + '/branches/master', token)['commit']['id'] != commit: raise ValueError('master moved; start a new deployment') + # A reused job reports workflow_call; the API retains the caller's trigger. + invocation = api(repository + '/actions/runs/' + os.environ['GITHUB_RUN_ID'], token) + if ((invocation['workflow_id'], invocation['trigger_event'], + invocation['commit_sha'], invocation['prettyref']) != + ('deploy.yml', 'workflow_dispatch', commit, 'master')): + raise ValueError('Deploy must be run manually on master') query = urllib.parse.urlencode({'head_sha': commit, 'ref': ref, 'workflow_id': 'ci.yml', 'limit': 1}) runs = api(repository + '/actions/runs?' + query, token)['workflow_runs']