22
33import argparse
44import json
5+ import shlex
6+ import subprocess
57import sys
68from pathlib import Path
9+ from typing import Any
710
811from . import (
912 instantiate_effects ,
1619from .options import EffectsOptions
1720
1821
22+ def resolve_flake (flake_ref : str , * , debug : bool = False ) -> dict [str , Any ]:
23+ """Run `nix flake metadata --json` and return the parsed JSON."""
24+ cmd = [
25+ "nix" ,
26+ "--extra-experimental-features" ,
27+ "nix-command flakes" ,
28+ "flake" ,
29+ "metadata" ,
30+ "--json" ,
31+ flake_ref ,
32+ ]
33+ if debug :
34+ print ("$" , shlex .join (cmd ), file = sys .stderr )
35+ proc = subprocess .run (cmd , check = True , text = True , capture_output = True )
36+ return json .loads (proc .stdout )
37+
38+
39+ def options_from_flake_ref (flake_ref : str , base : EffectsOptions ) -> EffectsOptions :
40+ """Resolve a flake reference to EffectsOptions via nix flake metadata."""
41+ meta = resolve_flake (flake_ref , debug = base .debug )
42+ locked = meta .get ("locked" , {})
43+ return EffectsOptions (
44+ secrets = base .secrets ,
45+ path = Path (meta .get ("path" , "" )),
46+ repo = "" ,
47+ rev = locked .get ("rev" ),
48+ branch = locked .get ("ref" ),
49+ url = meta .get ("resolvedUrl" , meta .get ("url" , "" )),
50+ locked_url = meta .get ("lockedUrl" , "" ),
51+ debug = base .debug ,
52+ )
53+
54+
1955def list_command (_args : argparse .Namespace , options : EffectsOptions ) -> None :
56+ if _args .flake_ref :
57+ options = options_from_flake_ref (_args .flake_ref , options )
2058 json .dump (list_effects (options ), fp = sys .stdout , indent = 2 )
2159
2260
2361def run_command (args : argparse .Namespace , options : EffectsOptions ) -> None :
2462 effect = args .effect
63+
64+ # Support flakeref#effect syntax: github:org/repo/branch#my-effect
65+ if "#" in effect :
66+ flake_ref , _ , effect = effect .partition ("#" )
67+ options = options_from_flake_ref (flake_ref , options )
68+
2569 drv_path = instantiate_effects (effect , options )
2670 if drv_path == "" :
2771 print (f"Effect { effect } not found or not runnable for { options } " )
@@ -41,17 +85,26 @@ def run_all_command(_args: argparse.Namespace, _options: EffectsOptions) -> None
4185
4286def list_schedules_command (_args : argparse .Namespace , options : EffectsOptions ) -> None :
4387 """List all scheduled effects defined in the flake."""
88+ if _args .flake_ref :
89+ options = options_from_flake_ref (_args .flake_ref , options )
4490 json .dump (list_scheduled_effects (options ), fp = sys .stdout , indent = 2 )
4591
4692
4793def run_scheduled_command (args : argparse .Namespace , options : EffectsOptions ) -> None :
4894 """Run a specific effect from a schedule."""
4995 schedule_name = args .schedule_name
5096 effect = args .effect
97+
98+ # Support flakeref#schedule syntax: github:org/repo/branch#my-schedule
99+ if "#" in schedule_name :
100+ flake_ref , _ , schedule_name = schedule_name .partition ("#" )
101+ options = options_from_flake_ref (flake_ref , options )
102+
51103 drv_path = instantiate_scheduled_effect (schedule_name , effect , options )
52104 if drv_path == "" :
53105 print (
54- f"Scheduled effect { schedule_name } /{ effect } not found or not runnable for { options } "
106+ f"Scheduled effect { schedule_name } /{ effect } not found or not runnable"
107+ f" for { options } "
55108 )
56109 return
57110 drvs = parse_derivation (drv_path )
@@ -64,7 +117,18 @@ def run_scheduled_command(args: argparse.Namespace, options: EffectsOptions) ->
64117
65118
66119def parse_args () -> tuple [argparse .Namespace , EffectsOptions ]:
67- parser = argparse .ArgumentParser (description = "Run effects from a hercules-ci flake" )
120+ parser = argparse .ArgumentParser (
121+ description = "Run effects from a hercules-ci flake" ,
122+ epilog = (
123+ "Flake reference syntax:\n "
124+ " Commands accept flake references to operate on remote repositories\n "
125+ " without requiring a local checkout:\n \n "
126+ " buildbot-effects run github:org/repo/branch#my-effect\n "
127+ " buildbot-effects list github:org/repo/branch\n "
128+ " buildbot-effects run-scheduled github:org/repo#schedule effect\n "
129+ ),
130+ formatter_class = argparse .RawDescriptionHelpFormatter ,
131+ )
68132 parser .add_argument (
69133 "--secrets" ,
70134 type = Path ,
@@ -104,17 +168,23 @@ def parse_args() -> tuple[argparse.Namespace, EffectsOptions]:
104168 )
105169 list_parser = subparser .add_parser (
106170 "list" ,
107- help = "List available effects" ,
171+ help = "List available effects (optionally from a flake reference) " ,
108172 )
109173 list_parser .set_defaults (command = list_command )
174+ list_parser .add_argument (
175+ "flake_ref" ,
176+ nargs = "?" ,
177+ help = "Flake reference (e.g. github:org/repo/branch)" ,
178+ )
179+
110180 run_parser = subparser .add_parser (
111181 "run" ,
112- help = "Run an effect" ,
182+ help = "Run an effect (supports flakeref#effect syntax) " ,
113183 )
114184 run_parser .set_defaults (command = run_command )
115185 run_parser .add_argument (
116186 "effect" ,
117- help = "Effect to run" ,
187+ help = "Effect to run, or flakeref#effect (e.g. github:org/repo/branch#deploy) " ,
118188 )
119189 run_all_parser = subparser .add_parser (
120190 "run-all" ,
@@ -124,9 +194,14 @@ def parse_args() -> tuple[argparse.Namespace, EffectsOptions]:
124194
125195 list_schedules_parser = subparser .add_parser (
126196 "list-schedules" ,
127- help = "List all scheduled effects defined in the flake" ,
197+ help = "List all scheduled effects (optionally from a flake reference) " ,
128198 )
129199 list_schedules_parser .set_defaults (command = list_schedules_command )
200+ list_schedules_parser .add_argument (
201+ "flake_ref" ,
202+ nargs = "?" ,
203+ help = "Flake reference (e.g. github:org/repo/branch)" ,
204+ )
130205
131206 run_scheduled_parser = subparser .add_parser (
132207 "run-scheduled" ,
@@ -135,7 +210,7 @@ def parse_args() -> tuple[argparse.Namespace, EffectsOptions]:
135210 run_scheduled_parser .set_defaults (command = run_scheduled_command )
136211 run_scheduled_parser .add_argument (
137212 "schedule_name" ,
138- help = "Name of the schedule (from onSchedule.<name> )" ,
213+ help = "Schedule name, or flakeref# schedule (e.g. github:org/repo#my-schedule )" ,
139214 )
140215 run_scheduled_parser .add_argument (
141216 "effect" ,
0 commit comments