1111ROOT = Path (__file__ ).resolve ().parents [2 ]
1212CODEMAGIC = ROOT / "codemagic.yaml"
1313QUALIFIER = ROOT / ".github/workflows/desktop_qualify_beta.yml"
14+ BETA_PROMOTION = ROOT / ".github/workflows/desktop_promote_beta.yml"
1415
1516
1617def validate (root : Path ) -> list [str ]:
1718 codemagic = (root / "codemagic.yaml" ).read_text (encoding = "utf-8" )
1819 qualifier = (root / ".github/workflows/desktop_qualify_beta.yml" ).read_text (encoding = "utf-8" )
20+ beta_promotion = (root / ".github/workflows/desktop_promote_beta.yml" ).read_text (encoding = "utf-8" )
1921 errors : list [str ] = []
2022 dispatch = codemagic .split ("gh workflow run desktop_qualify_beta.yml" , 1 )[1 ].split ("then" , 1 )[0 ]
2123 if '--ref "$CM_TAG"' not in dispatch :
@@ -26,27 +28,110 @@ def validate(root: Path) -> list[str]:
2628 errors .append ("qualification must not check out mutable main" )
2729 if "CHECKOUT_SHA=$(git rev-parse HEAD)" not in qualifier or "EVENT_SHA: ${{ github.sha }}" not in qualifier :
2830 errors .append ("candidate validation must compare the fetched candidate tag against the checked-out event SHA" )
31+ if "gh workflow run desktop_promote_beta.yml" in qualifier :
32+ errors .append ("qualification must not dispatch beta promotion before its run has completed" )
33+
34+ for fragment , message in (
35+ (
36+ ' workflow_run:\n workflows: ["Qualify Desktop Beta Candidate"]\n types: [completed]' ,
37+ "automatic beta promotion must be triggered only by completed qualification runs" ,
38+ ),
39+ (
40+ "github.event.workflow_run.conclusion == 'success'" ,
41+ "automatic beta promotion must require a successful qualification conclusion" ,
42+ ),
43+ (
44+ "github.event.workflow_run.event == 'workflow_dispatch'" ,
45+ "automatic beta promotion must require the trusted workflow_dispatch qualifier" ,
46+ ),
47+ (
48+ "github.event.workflow_run.repository.full_name == github.repository" ,
49+ "automatic beta promotion must require a same-repository workflow run" ,
50+ ),
51+ (
52+ "github.event.workflow_run.head_repository.full_name == github.repository" ,
53+ "automatic beta promotion must reject fork-sourced qualification" ,
54+ ),
55+ (
56+ "github.event.workflow_run.path == '.github/workflows/desktop_qualify_beta.yml'" ,
57+ "automatic beta promotion must require the desktop qualification workflow path" ,
58+ ),
59+ (
60+ "RELEASE_TAG: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || inputs.release_tag }}" ,
61+ "automatic beta promotion must derive its release tag from the completed qualifier" ,
62+ ),
63+ (
64+ "QUALIFICATION_RUN_ID: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || inputs.qualification_run_id }}" ,
65+ "automatic beta promotion must use the completed qualifier run ID" ,
66+ ),
67+ (
68+ 'test "$(jq -r .head_branch <<<"$run")" = "$RELEASE_TAG"' ,
69+ "automatic beta promotion must bind qualification evidence to the release tag" ,
70+ ),
71+ (
72+ 'test "$(jq -r .head_sha <<<"$run")" = "$TARGET_SHA"' ,
73+ "automatic beta promotion must bind qualification evidence to the exact tag SHA" ,
74+ ),
75+ (
76+ 'test "$QUALIFICATION_RUN_ID" = "$QUALIFIER_EVENT_RUN_ID"' ,
77+ "automatic beta promotion must use the triggering completed qualification run" ,
78+ ),
79+ (
80+ 'test "$QUALIFIER_EVENT_HEAD_SHA" = "$TARGET_SHA"' ,
81+ "automatic beta promotion must bind the triggering qualifier SHA to the release tag" ,
82+ ),
83+ ):
84+ if fragment not in beta_promotion :
85+ errors .append (message )
2986 return errors
3087
3188
3289class DesktopQualificationDispatchTests (unittest .TestCase ):
33- def test_current_configuration_uses_the_candidate_tag_and_event_sha (self ) -> None :
90+ def test_current_configuration_uses_the_candidate_tag_and_completed_qualifier (self ) -> None :
3491 self .assertEqual (validate (ROOT ), [])
3592
3693 def test_mutations_drop_tag_ref_or_reintroduce_mutable_checkout (self ) -> None :
3794 codemagic = CODEMAGIC .read_text (encoding = "utf-8" )
3895 qualifier = QUALIFIER .read_text (encoding = "utf-8" )
96+ beta_promotion = BETA_PROMOTION .read_text (encoding = "utf-8" )
3997 mutations = (
40- (codemagic .replace ('--ref "$CM_TAG" \\ \n ' , "" , 1 ), qualifier ),
41- (codemagic , qualifier .replace ("ref: ${{ github.sha }}" , "ref: main" , 1 )),
42- (codemagic , qualifier .replace ("CHECKOUT_SHA=$(git rev-parse HEAD)" , "CHECKOUT_SHA=$(git rev-parse \" $RELEASE_TAG\" )" , 1 )),
98+ (codemagic .replace ('--ref "$CM_TAG" \\ \n ' , "" , 1 ), qualifier , beta_promotion ),
99+ (codemagic , qualifier .replace ("ref: ${{ github.sha }}" , "ref: main" , 1 ), beta_promotion ),
100+ (
101+ codemagic ,
102+ qualifier .replace ("CHECKOUT_SHA=$(git rev-parse HEAD)" , "CHECKOUT_SHA=$(git rev-parse \" $RELEASE_TAG\" )" , 1 ),
103+ beta_promotion ,
104+ ),
105+ (codemagic , qualifier + '\n gh workflow run desktop_promote_beta.yml\n ' , beta_promotion ),
106+ (
107+ codemagic ,
108+ qualifier ,
109+ beta_promotion .replace ("github.event.workflow_run.conclusion == 'success'" , "github.event.workflow_run.conclusion == 'failure'" , 1 ),
110+ ),
111+ (
112+ codemagic ,
113+ qualifier ,
114+ beta_promotion .replace (
115+ "github.event.workflow_run.head_repository.full_name == github.repository" ,
116+ "github.event.workflow_run.head_repository.full_name == 'attacker/omi'" ,
117+ 1 ,
118+ ),
119+ ),
120+ (
121+ codemagic ,
122+ qualifier ,
123+ beta_promotion .replace ('test "$(jq -r .head_sha <<<"$run")" = "$TARGET_SHA"' , 'test "$(jq -r .head_sha <<<"$run")" = main' , 1 ),
124+ ),
43125 )
44- for changed_codemagic , changed_qualifier in mutations :
126+ for changed_codemagic , changed_qualifier , changed_beta_promotion in mutations :
45127 with self .subTest (mutation = changed_codemagic != codemagic ), tempfile .TemporaryDirectory () as directory :
46128 root = Path (directory )
47129 (root / ".github/workflows" ).mkdir (parents = True )
48130 (root / "codemagic.yaml" ).write_text (changed_codemagic , encoding = "utf-8" )
49131 (root / ".github/workflows/desktop_qualify_beta.yml" ).write_text (changed_qualifier , encoding = "utf-8" )
132+ (root / ".github/workflows/desktop_promote_beta.yml" ).write_text (
133+ changed_beta_promotion , encoding = "utf-8"
134+ )
50135 self .assertTrue (validate (root ))
51136
52137
0 commit comments