11
11
import subprocess
12
12
import tempfile
13
13
import uuid
14
+ from collections import defaultdict
14
15
from dataclasses import dataclass , field
15
16
from pathlib import Path
16
17
from shutil import copyfile
17
- from typing import Dict , List , Optional , Tuple , Union
18
+ from typing import Dict , List , Optional , Tuple , Union , Any
18
19
from urllib .request import urlretrieve
19
20
20
21
import boto3
@@ -79,6 +80,12 @@ class Context:
79
80
# Generates a version_id to use if one is not present
80
81
stored_version_id : str = str (uuid .uuid4 ())
81
82
83
+ # stage_outputs is a dictionary of dictionaries. First dict
84
+ # has a key corresponding to the name of the stage, the dict
85
+ # you get from it is key/value (str, Any) with the values
86
+ # stored by given stage.
87
+ stage_outputs : Dict [str , List [Dict [str , Any ]]] = field (default_factory = dict )
88
+
82
89
# pylint: disable=C0103
83
90
def I (self , string ):
84
91
"""
@@ -101,6 +108,16 @@ def version_id(self):
101
108
return os .environ .get ("version_id" , self .stored_version_id )
102
109
103
110
111
+ def append_output_in_context (ctx : Context , stage_name : str , values : Dict ) -> None :
112
+ """Stores a value as the output of the stage, so it can be consumed by future stages."""
113
+ if stage_name not in ctx .stage_outputs .keys ():
114
+ # adds a new empty dictionary to this stage
115
+ # if there isn't one yet.
116
+ ctx .stage_outputs [stage_name ] = list ()
117
+
118
+ ctx .stage_outputs [stage_name ].append (values )
119
+
120
+
104
121
def find_inventory (inventory : Optional [str ] = None ):
105
122
"""
106
123
Finds the inventory file, and return it as a yaml object.
@@ -124,14 +141,6 @@ def find_image(image_name: str, inventory: str):
124
141
raise ValueError ("Image {} not found" .format (image_name ))
125
142
126
143
127
- def find_variables_to_interpolate (string ) -> List [str ]:
128
- """
129
- Returns a list of variables in the string that need to be interpolated.
130
- """
131
- var_finder_re = r"\$\(inputs\.params\.(?P<var>\w+)\)"
132
- return re .findall (var_finder_re , string , re .UNICODE )
133
-
134
-
135
144
def find_variable_replacement (ctx : Context , variable : str , stage = None ) -> str :
136
145
"""
137
146
Returns the variable *value* for this varable.
@@ -187,6 +196,38 @@ def find_variable_replacements(
187
196
return replacements
188
197
189
198
199
+ def execute_interpolatable_function (name : str ) -> str :
200
+ if name == "tempfile" :
201
+ tmp = tempfile .mkstemp ()
202
+ # mkstemp returns a tuple, with the second element of it being
203
+ # the absolute path to the file.
204
+ return tmp [1 ]
205
+
206
+ raise ValueError ("Only supported function is 'tempfile'" )
207
+
208
+
209
+ def find_variables_to_interpolate_from_stage (string : str ) -> List [Any ]:
210
+ """Finds a $(stage['stage-name'].outputs[])"""
211
+ var_finder_re = r"\$\(stages\[\'(?P<stage_name>[\w-]+)\'\]\.outputs\[(?P<index>\d+)\]\.(?P<key>\w+)"
212
+
213
+ return re .findall (var_finder_re , string , re .UNICODE )
214
+
215
+
216
+ def find_variables_to_interpolate (string ) -> List [str ]:
217
+ """
218
+ Returns a list of variables in the string that need to be interpolated.
219
+ """
220
+ var_finder_re = r"\$\(inputs\.params\.(?P<var>\w+)\)"
221
+ return re .findall (var_finder_re , string , re .UNICODE )
222
+
223
+
224
+ def find_functions_to_interpolate (string : str ) -> List [Any ]:
225
+ """Find functions to be interpolated."""
226
+ var_finder_re = r"\$\(functions\.(?P<var>\w+)\)"
227
+
228
+ return re .findall (var_finder_re , string , re .UNICODE )
229
+
230
+
190
231
def interpolate_vars (ctx : Context , string : str , stage = None ) -> str :
191
232
"""
192
233
For each variable to interpolate in string, finds its *value* and
@@ -200,8 +241,23 @@ def interpolate_vars(ctx: Context, string: str, stage=None) -> str:
200
241
"$(inputs.params.{})" .format (variable ), replacements [variable ]
201
242
)
202
243
203
- return string
244
+ variables = find_variables_to_interpolate_from_stage (string )
245
+ for stage , index , key in variables :
246
+ value = ctx .stage_outputs [stage ][int (index )][key ]
247
+ string = string .replace (
248
+ "$(stages['{}'].outputs[{}].{})" .format (stage , index , key ),
249
+ value
250
+ )
204
251
252
+ functions = find_functions_to_interpolate (string )
253
+ for name in functions :
254
+ value = execute_interpolatable_function (name )
255
+ string = string .replace (
256
+ "$(functions.{})" .format (name ),
257
+ value
258
+ )
259
+
260
+ return string
205
261
206
262
def build_add_statement (ctx , block ) -> str :
207
263
"""
@@ -334,6 +390,11 @@ def task_tag_image(ctx: Context):
334
390
else :
335
391
raise
336
392
393
+ append_output_in_context (ctx , ctx .stage ["name" ], {
394
+ "registry" : registry ,
395
+ "tag" : tag
396
+ })
397
+
337
398
338
399
def get_rendering_params (ctx : Context ) -> Dict [str , str ]:
339
400
"""
@@ -532,6 +593,11 @@ def task_docker_build(ctx: Context):
532
593
else :
533
594
raise
534
595
596
+ append_output_in_context (ctx , ctx .stage ["name" ], {
597
+ "registry" : registry ,
598
+ "tag" : tag ,
599
+ })
600
+
535
601
if sign :
536
602
clear_signing_environment (signing_key_name )
537
603
@@ -568,7 +634,13 @@ def task_dockerfile_template(ctx: Context):
568
634
except KeyError :
569
635
pass
570
636
571
- dockerfile = run_dockerfile_template (ctx , template_context , ctx .stage .get ("distro" ))
637
+
638
+ template_file_extension = ctx .stage .get ("template_file_extension" )
639
+ if template_file_extension is None :
640
+ # Use distro as compatibility with pre 0.11
641
+ template_file_extension = ctx .stage .get ("distro" )
642
+
643
+ dockerfile = run_dockerfile_template (ctx , template_context , template_file_extension )
572
644
573
645
for output in ctx .stage ["output" ]:
574
646
if "dockerfile" in output :
@@ -577,6 +649,10 @@ def task_dockerfile_template(ctx: Context):
577
649
578
650
echo (ctx , "dockerfile-save-location" , output_dockerfile )
579
651
652
+ append_output_in_context (ctx , ctx .stage ["name" ], {
653
+ "dockerfile" : output_dockerfile
654
+ })
655
+
580
656
581
657
def find_skip_tags (params : Optional [Dict [str , str ]] = None ) -> List [str ]:
582
658
"""Returns a list of tags passed in params that should be excluded from the build."""
0 commit comments