Skip to content
This repository was archived by the owner on Aug 26, 2025. It is now read-only.

Commit 414be8b

Browse files
leighpascoeludoo
andauthored
Log output while Popen is in progress (#45)
* Parallel logging and execute command Currently the `execute_command` function will only log the output after the command has completed, considering that the resources being created may take a long time, getting feedback during execution would be very valuable. * fixed tabbing * fixed another tab tab * Update tftest.py * revised based on comments - fixed formatting - used list instead of string concat - add encoding and ignore error params to popen Co-authored-by: Ludovico Magnocavallo <[email protected]>
1 parent f4001b9 commit 414be8b

File tree

2 files changed

+42
-14
lines changed

2 files changed

+42
-14
lines changed

test/test_plan.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,9 @@ def test_plan_with_no_resources_succeeds(fixtures_dir):
8080
result = tf.plan(output=True)
8181

8282
assert result.outputs['just_an_output'] == 'Hello, plan!'
83+
84+
85+
def test_plan_stdout(fixtures_dir):
86+
tf = tftest.TerraformTest('plan_no_resource_changes', fixtures_dir)
87+
result = tf.plan(output=False)
88+
assert 'just_an_output = "Hello, plan!"' in result

tftest.py

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ def parse_args(init_vars=None, tf_vars=None, targets=None, **kw):
102102
for arg in _TG_BOOL_ARGS if kw.get(f"tg_{arg}")]
103103
for arg in _TG_KV_ARGS:
104104
if kw.get(f"tg_{arg}"):
105-
cmd_args += [f'--terragrunt-{arg.replace("_", "-")}', kw[f"tg_{arg}"]]
105+
cmd_args += [f'--terragrunt-{arg.replace("_", "-")}',
106+
kw[f"tg_{arg}"]]
106107
if kw.get('tg_parallelism'):
107108
cmd_args.append(f'--terragrunt-parallelism {kw["tg_parallelism"]}')
108109
if isinstance(kw.get('tg_override_attr'), dict):
@@ -296,7 +297,8 @@ def __init__(self, tfdir, basedir=None, binary='terraform', env=None):
296297
self.env = os.environ.copy()
297298
self.tg_run_all = False
298299
self._plan_formatter = lambda out: TerraformPlanOutput(json.loads(out))
299-
self._output_formatter = lambda out: TerraformValueDict(json.loads(out))
300+
self._output_formatter = lambda out: TerraformValueDict(
301+
json.loads(out))
300302
if env is not None:
301303
self.env.update(env)
302304

@@ -324,11 +326,13 @@ def remove_readonly(func, path, excinfo):
324326
for tg_dir in glob.glob(path, recursive=True):
325327
if os.path.isdir(tg_dir):
326328
shutil.rmtree(tg_dir, onerror=remove_readonly)
327-
_LOGGER.debug('Restoring original TF files after prevent destroy changes')
329+
_LOGGER.debug(
330+
'Restoring original TF files after prevent destroy changes')
328331
if restore_files:
329332
for bkp_file in Path(tfdir).rglob('*.bkp'):
330333
try:
331-
shutil.copy(str(bkp_file), f'{str(bkp_file).strip(".bkp")}')
334+
shutil.copy(str(bkp_file),
335+
f'{str(bkp_file).strip(".bkp")}')
332336
except (IOError, OSError):
333337
_LOGGER.exception(
334338
f'Unable to restore terraform file {bkp_file.resolve()}')
@@ -378,8 +382,9 @@ def setup(self, extra_files=None, plugin_dir=None, init_vars=None,
378382
with open(tf_file, 'r') as src:
379383
terraform = src.read()
380384
with open(tf_file, 'w') as src:
381-
terraform = re.sub(r'prevent_destroy\s+=\s+true',
382-
'prevent_destroy = false', terraform)
385+
386+
terraform = re.sub(
387+
r'prevent_destroy\s+=\s+true', 'prevent_destroy = false', terraform)
383388
src.write(terraform)
384389
except (OSError, IOError):
385390
_LOGGER.exception(
@@ -450,7 +455,8 @@ def plan(self, input=False, color=False, refresh=True, tf_vars=None,
450455
try:
451456
return self._plan_formatter(result.out)
452457
except json.JSONDecodeError as e:
453-
raise TerraformTestError('Error decoding plan output: {}'.format(e))
458+
raise TerraformTestError(
459+
'Error decoding plan output: {}'.format(e))
454460

455461
def apply(self, input=False, color=False, auto_approve=True,
456462
tf_vars=None, targets=None, tf_var_file=None, **kw):
@@ -514,21 +520,37 @@ def execute_command(self, cmd, *cmd_args):
514520
cmdline = [self.binary, *self._tg_ra(), cmd]
515521
cmdline += cmd_args
516522
_LOGGER.info(cmdline)
523+
retcode = None
524+
full_output_lines = []
517525
try:
518-
p = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
519-
stderr=subprocess.PIPE, cwd=self.tfdir, env=self.env)
526+
p = subprocess.Popen(cmdline,
527+
stdout=subprocess.PIPE,
528+
stderr=subprocess.PIPE,
529+
cwd=self.tfdir,
530+
env=self.env,
531+
universal_newlines=True,
532+
encoding='utf-8',
533+
errors='ignore')
534+
while True:
535+
output = p.stdout.readline()
536+
if output == '' and p.poll() is not None:
537+
break
538+
if output:
539+
_LOGGER.info(output.strip())
540+
full_output_lines.append(output)
541+
retcode = p.poll()
542+
p.stdout.close()
543+
p.wait()
520544
except FileNotFoundError as e:
521545
raise TerraformTestError('Terraform executable not found: %s' % e)
522546
out, err = p.communicate()
523-
out = out.decode('utf-8', errors='ignore')
524-
err = err.decode('utf-8', errors='ignore')
525-
retcode = p.returncode
547+
full_output = "".join(full_output_lines)
526548
if retcode == 1:
527549
message = 'Error running command {command}: {retcode} {out} {err}'.format(
528-
command=cmd, retcode=retcode, out=out, err=err)
550+
command=cmd, retcode=retcode, out=full_output, err=err)
529551
_LOGGER.critical(message)
530552
raise TerraformTestError(message)
531-
return TerraformCommandOutput(retcode, out, err)
553+
return TerraformCommandOutput(retcode, full_output, err)
532554

533555
def _tg_ra(self) -> List[str]:
534556
"""if run_all return ['run-all'] else [] """

0 commit comments

Comments
 (0)