@@ -178,11 +178,12 @@ def github_repo_from_url(repo: str):
178
178
return user .capitalize () + "/" + repo .capitalize ()
179
179
180
180
181
- def labelstr_to_kv ( labelstr : str ) -> Dict [str , str ]:
181
+ def labels_to_kv ( labels : List [ str ] ) -> Dict [str , str ]:
182
182
arg = {}
183
- for part in shlex .split (labelstr ):
184
- split = part .split ("=" , 1 )
185
- arg [split [0 ]] = split [1 ] if len (split ) == 2 else ""
183
+ for label in labels :
184
+ for part in shlex .split (label ):
185
+ split = part .split ("=" , 1 )
186
+ arg [split [0 ]] = split [1 ] if len (split ) == 2 else ""
186
187
return arg
187
188
188
189
@@ -249,27 +250,30 @@ class Config(pydantic.BaseModel, extra=pydantic.Extra.forbid):
249
250
A star '*' causes to get all the repositories available.
250
251
"""
251
252
252
- label_match : str = "nomadtools *(.*)"
253
- """Only execute if github action workflow job runs-on labels matching this regex"""
253
+ runson_match : str = "nomadtools *(.*)"
254
+ """ Only execute if github action workflow job runs-on labels matching this regex. """
254
255
255
256
loop : int = 60
256
- """How many seconds will the loop run"""
257
+ """ How many seconds will the loop run. """
257
258
258
259
template : str = get_package_file ("entry_githubrunner/default.nomad.hcl" )
259
260
"""Jinja2 template for the runner"""
260
261
261
- default_template_context : Any = {
262
+ template_default_settings : Dict [ str , Any ] = {
262
263
"extra_config" : "" ,
263
264
"extra_group" : "" ,
264
265
"extra_task" : "" ,
265
266
"extra_job" : "" ,
267
+ "docker" : "none" ,
266
268
"ephemeral" : True ,
267
- "startscript" : get_package_file ("entry_githubrunner/startscript.sh" ),
269
+ "run_as_root" : False ,
270
+ "cachedir" : "" ,
271
+ "entrypoint" : get_package_file ("entry_githubrunner/entrypoint.sh" ),
268
272
}
269
- """Additional template variables """
273
+ """Default values for SETTINGS template variable """
270
274
271
- template_context : Any = {}
272
- """Additional template variables """
275
+ template_settings : Dict [ str , Any ] = {}
276
+ """ Overwrite settings according to your whim """
273
277
274
278
runner_inactivity_timeout : str = "12h"
275
279
"""How much time a runner will be inactive for it to be removed?"""
@@ -287,7 +291,7 @@ def __post_init__(self):
287
291
288
292
class ParsedConfig :
289
293
def __init__ (self , config : Config ):
290
- self .label_match = re .compile (config .label_match )
294
+ self .label_match = re .compile (config .runson_match )
291
295
self .runner_inactivity_timeout = parse_time (config .runner_inactivity_timeout )
292
296
self .purge_successfull_timeout = parse_time (config .purge_successfull_timeout )
293
297
self .purge_failure_timeout = parse_time (config .purge_failure_timeout )
@@ -397,14 +401,14 @@ def print(self):
397
401
398
402
@dataclass (frozen = True )
399
403
class Desc :
400
- labels : str
404
+ labels : List [ str ]
401
405
repo_url : str
402
406
403
407
def __post_init__ (self ):
404
408
assert self .labels
405
409
assert self .repo_url
406
410
assert "://" in self .repo_url
407
- assert PARSEDCONFIG .label_match .match (self .labels )
411
+ assert any ( PARSEDCONFIG .label_match .match (x ) for x in self .labels )
408
412
409
413
@property
410
414
def repo (self ):
@@ -413,6 +417,10 @@ def repo(self):
413
417
def tostr (self ):
414
418
return f"labels={ self .labels } url={ self .repo_url } "
415
419
420
+ @property
421
+ def labelsstr (self ):
422
+ return "," .join (sorted (self .labels ))
423
+
416
424
417
425
class DescProtocol (ABC ):
418
426
@abstractmethod
@@ -602,17 +610,8 @@ class GithubJob(DescProtocol):
602
610
run : dict
603
611
job : dict
604
612
605
- def labelsstr (self ):
606
- return "\n " .join (sorted (list (set (self .job ["labels" ]))))
607
-
608
- def job_url (self ):
609
- return self .job ["html_url" ]
610
-
611
- def repo_url (self ):
612
- return self .run ["repository" ]["html_url" ]
613
-
614
613
def get_desc (self ):
615
- return Desc (self .labelsstr () , self .repo_url () )
614
+ return Desc (self .job [ "labels" ] , self .job [ "html_url" ] )
616
615
617
616
618
617
def get_gh_repos_one_to_many (repo : str ) -> List [GithubRepo ]:
@@ -681,7 +680,7 @@ def get_gh_state(repos: Set[GithubRepo]) -> list[GithubJob]:
681
680
# desc: str = ", ".join(s.labelsstr() + " for " + s.job_url() for s in reqstate)
682
681
# log.info(f"Found {len(reqstate)} required runners to run: {desc}")
683
682
for idx , s in enumerate (reqstate ):
684
- logging .info (f"GHJOB={ idx } { s .job_url () } { s . labelsstr ()} { s .run ['status' ]} " )
683
+ logging .info (f"GHJOB={ idx } { s .get_desc (). tostr ()} { s .run ['status' ]} " )
685
684
return reqstate
686
685
687
686
@@ -690,7 +689,13 @@ def get_gh_state(repos: Set[GithubRepo]) -> list[GithubJob]:
690
689
691
690
692
691
def get_desc_from_nomadjob (job ):
693
- return Desc (** json .loads ((job ["Meta" ] or {})[CONFIG .nomad .meta ]))
692
+ txt = (job ["Meta" ] or {})[CONFIG .nomad .meta ]
693
+ try :
694
+ data = json .loads (txt )
695
+ except Exception :
696
+ print (txt )
697
+ raise
698
+ return Desc (** data )
694
699
695
700
696
701
@dataclass
@@ -914,7 +919,7 @@ def _generate_job_name(self):
914
919
CONFIG .nomad .jobprefix ,
915
920
self .desc .repo ,
916
921
i ,
917
- self .desc .labels .replace ("nomadtools" , "" ),
922
+ self .desc .labelsstr .replace ("nomadtools" , "" ),
918
923
self .info ,
919
924
]
920
925
name = re .sub (r"[^a-zA-Z0-9_.-]" , "" , "-" .join (str (x ) for x in parts ))
@@ -924,35 +929,29 @@ def _generate_job_name(self):
924
929
return name
925
930
926
931
@staticmethod
927
- def make_example (labelsstr : str ):
932
+ def make_example (labels : List [ str ] ):
928
933
return RunnerGenerator (
929
- Desc (labels = labelsstr , repo_url = "http://example.repo.url/user/repo" ),
934
+ Desc (labels = labels , repo_url = "http://example.repo.url/user/repo" ),
930
935
"This is an information" ,
931
936
TakenNames (),
932
937
)
933
938
934
939
def _to_template_args (self ) -> dict :
935
- arg = labelstr_to_kv (self .desc .labels )
936
- name = self ._generate_job_name ()
937
940
return dict (
938
- param = {
939
- ** arg ,
940
- ** CONFIG .template_context ,
941
- ** CONFIG .default_template_context ,
942
- ** dict (
943
- REPO_URL = self .desc .repo_url ,
944
- RUNNER_NAME = name ,
945
- JOB_NAME = name ,
946
- LABELS = self .desc .labels ,
947
- ),
941
+ SETTINGS = {
942
+ ** CONFIG .template_default_settings ,
943
+ ** CONFIG .template_settings ,
948
944
},
949
- run = asdict (self ),
950
- arg = arg ,
945
+ RUN = dict (
946
+ REPO_URL = self .desc .repo_url ,
947
+ RUNNER_NAME = self ._generate_job_name (),
948
+ LABELS = self .desc .labelsstr .replace ('"' , r"\"" ),
949
+ REPO = self .desc .repo ,
950
+ INFO = self .info ,
951
+ ),
952
+ RUNSON = labels_to_kv (self .desc .labels ),
951
953
CONFIG = CONFIG ,
952
- TEMPLATE = TEMPLATE ,
953
- ARGS = ARGS ,
954
- escape = nomadlib .escape ,
955
- META = json .dumps (asdict (self .desc )),
954
+ nomadlib = nomadlib ,
956
955
)
957
956
958
957
def get_runnertorun (self ) -> NomadJobToRun :
@@ -961,10 +960,9 @@ def get_runnertorun(self) -> NomadJobToRun:
961
960
jobspec : dict = nomad_job_text_to_json (jobtext )
962
961
# Apply default transformations.
963
962
jobspec ["Namespace" ] = CONFIG .nomad .namespace
964
- for key in ["ID" , "Name" ]:
965
- if key in jobspec :
966
- jobspec [key ] = tc ["param" ]["JOB_NAME" ]
967
- jobspec ["Meta" ][CONFIG .nomad .meta ] = tc ["META" ]
963
+ jobspec .setdefault ("Meta" , {})[CONFIG .nomad .meta ] = json .dumps (
964
+ asdict (self .desc )
965
+ )
968
966
#
969
967
nomadjobtorun = NomadJobToRun (nomadlib .Job (jobspec ), jobtext )
970
968
assert nomadjobtorun .get_desc () == self .desc
@@ -1089,7 +1087,7 @@ def loop():
1089
1087
class Args :
1090
1088
dryrun : bool = clickdc .option ("-n" )
1091
1089
parallel : int = clickdc .option ("-P" , default = 4 )
1092
- config : str = clickdc .option (
1090
+ config : Optional [ str ] = clickdc .option (
1093
1091
"-c" ,
1094
1092
shell_complete = click .Path (
1095
1093
exists = True , dir_okay = False , path_type = Path
@@ -1101,7 +1099,15 @@ class Args:
1101
1099
)
1102
1100
1103
1101
1104
- @click .command ("githubrunner" , cls = AliasedGroup )
1102
+ @click .command (
1103
+ "githubrunner" ,
1104
+ cls = AliasedGroup ,
1105
+ help = """
1106
+ Execute Nomad job to run github self-hosted runner for user repositories.
1107
+
1108
+ See documentation in doc/githubrunner.md on github.
1109
+ """ ,
1110
+ )
1105
1111
@clickdc .adddc ("args" , Args )
1106
1112
@help_h_option ()
1107
1113
@common_click .quiet_option ()
@@ -1115,14 +1121,17 @@ def cli(args: Args):
1115
1121
datefmt = "%Y-%m-%dT%H:%M:%S%z" ,
1116
1122
)
1117
1123
#
1118
- if "\n " in args .config :
1119
- configstr = args .config
1120
- else :
1121
- with open (args .config ) as f :
1122
- configstr = f .read ()
1123
- tmp = yaml .safe_load (configstr )
1124
1124
global CONFIG
1125
- CONFIG = Config (** tmp )
1125
+ if args .config :
1126
+ if "\n " in args .config :
1127
+ configstr = args .config
1128
+ else :
1129
+ with open (args .config ) as f :
1130
+ configstr = f .read ()
1131
+ tmp = yaml .safe_load (configstr )
1132
+ CONFIG = Config (** tmp )
1133
+ else :
1134
+ CONFIG = Config ()
1126
1135
global PARSEDCONFIG
1127
1136
PARSEDCONFIG = ParsedConfig (CONFIG )
1128
1137
global GH
@@ -1200,12 +1209,16 @@ def once():
1200
1209
loop ()
1201
1210
1202
1211
1203
- @cli .command ()
1204
- @click .argument ("label" , required = True , nargs = - 1 )
1205
- def rendertemplate (label : Tuple [str , ...]):
1206
- labelsstr : str = "," .join (sorted (list (label )))
1207
- res = RunnerGenerator .make_example (labelsstr ).get_runnertorun ()
1208
- print (res )
1212
+ @cli .command (
1213
+ help = """
1214
+ Render the template from configuration given the labels given on command line.
1215
+ Usefull for testing the job.
1216
+ """
1217
+ )
1218
+ @click .argument ("labels" , required = True , nargs = - 1 )
1219
+ def rendertemplate (labels : Tuple [str , ...]):
1220
+ res = RunnerGenerator .make_example (list (labels )).get_runnertorun ()
1221
+ print (res .hcl )
1209
1222
1210
1223
1211
1224
@cli .command (help = "Main entrypoint - run the loop periodically" )
@@ -1237,7 +1250,7 @@ def listrunners():
1237
1250
x .ID ,
1238
1251
x .state .state .name ,
1239
1252
x .state .since .isoformat () if x .state .since else "None" ,
1240
- x .get_desc ().labels ,
1253
+ x .get_desc ().labelsstr ,
1241
1254
x .get_desc ().repo ,
1242
1255
]
1243
1256
)
0 commit comments