39
39
from functools import partial
40
40
from pathlib import Path
41
41
from typing import List
42
+ import pickle
43
+ from hashlib import sha1
44
+ import inspect
42
45
43
46
__version__ = '1.7.3'
44
47
@@ -307,9 +310,12 @@ class TerraformTest(object):
307
310
directory above the one this module lives in.
308
311
binary: path to the Terraform command.
309
312
env: a dict with custom environment variables to pass to terraform.
313
+ enable_cache: Determines if the caching enabled for specific methods
314
+ cache_dir: optional base directory to use for caching, defaults to
315
+ the directory of the python file that instantiates this class
310
316
"""
311
317
312
- def __init__ (self , tfdir , basedir = None , binary = 'terraform' , env = None ):
318
+ def __init__ (self , tfdir , basedir = None , binary = 'terraform' , env = None , enable_cache = False , cache_dir = None ):
313
319
"""Set Terraform folder to operate on, and optional base directory."""
314
320
self ._basedir = basedir or os .getcwd ()
315
321
self .binary = binary
@@ -318,6 +324,12 @@ def __init__(self, tfdir, basedir=None, binary='terraform', env=None):
318
324
self .tg_run_all = False
319
325
self ._plan_formatter = lambda out : TerraformPlanOutput (json .loads (out ))
320
326
self ._output_formatter = lambda out : TerraformValueDict (json .loads (out ))
327
+ self .enable_cache = enable_cache
328
+ if not cache_dir :
329
+ self .cache_dir = Path (os .path .dirname (
330
+ inspect .stack ()[1 ].filename )) / ".tftest-cache"
331
+ else :
332
+ self .cache_dir = Path (cache_dir )
321
333
if env is not None :
322
334
self .env .update (env )
323
335
@@ -363,9 +375,74 @@ def _abspath(self, path):
363
375
"""Make relative path absolute from base dir."""
364
376
return path if os .path .isabs (path ) else os .path .join (self ._basedir , path )
365
377
378
+ def _cache (func ):
379
+ def cache (self , ** kwargs ):
380
+ """
381
+ Runs the tftest instance method or retreives the cache value if it exists
382
+
383
+ Args:
384
+ kwargs: Keyword argument that are passed to the decorated method
385
+ Returns:
386
+ Output of the tftest instance method
387
+ """
388
+ _LOGGER .info ("Cache decorated method: %s" , func .__name__ )
389
+
390
+ if not self .enable_cache :
391
+ return func (self , ** kwargs )
392
+ elif not kwargs .get ("use_cache" , False ):
393
+ return func (self , ** kwargs )
394
+
395
+ cache_dir = self .cache_dir / \
396
+ Path (self .tfdir .strip ("/" )) / Path (func .__name__ )
397
+ # creates cache dir if not exists
398
+ cache_dir .mkdir (parents = True , exist_ok = True )
399
+
400
+ params = {
401
+ ** {
402
+ k : v
403
+ for k , v in self .__dict__ .items ()
404
+ # only uses instance attributes that are involved in the results of
405
+ # the decorated method
406
+ if k in ["binary" , "_basedir" , "tfdir" , "env" ]
407
+ },
408
+ ** kwargs ,
409
+ }
410
+
411
+ hash_filename = sha1 (
412
+ json .dumps (params , sort_keys = True , default = str ).encode ("cp037" )
413
+ ).hexdigest () + ".pickle"
414
+
415
+ cache_key = cache_dir / hash_filename
416
+ _LOGGER .debug ("Cache key: %s" , cache_key )
417
+
418
+ try :
419
+ f = cache_key .open ("rb" )
420
+ except OSError :
421
+ _LOGGER .debug ("Could not read cache path" )
422
+ else :
423
+ _LOGGER .info ("Getting output from cache" )
424
+ return pickle .load (f )
425
+
426
+ _LOGGER .info ("Running command" )
427
+ out = func (self , ** kwargs )
428
+
429
+ if out :
430
+ _LOGGER .info ("Writing command to cache" )
431
+ try :
432
+ f = cache_key .open ("wb" )
433
+ except OSError as e :
434
+ _LOGGER .error ("Cache could not write path" )
435
+ else :
436
+ with f :
437
+ pickle .dump (out , f , pickle .HIGHEST_PROTOCOL )
438
+
439
+ return out
440
+ return cache
441
+
442
+ @_cache
366
443
def setup (self , extra_files = None , plugin_dir = None , init_vars = None ,
367
444
backend = True , cleanup_on_exit = True , disable_prevent_destroy = False ,
368
- workspace_name = None , ** kw ):
445
+ workspace_name = None , use_cache = False , ** kw ):
369
446
"""Setup method to use in test fixtures.
370
447
371
448
This method prepares a new Terraform environment for testing the module
@@ -437,13 +514,14 @@ def setup(self, extra_files=None, plugin_dir=None, init_vars=None,
437
514
filenames , deep = cleanup_on_exit ,
438
515
restore_files = disable_prevent_destroy )
439
516
setup_output = self .init (plugin_dir = plugin_dir , init_vars = init_vars ,
440
- backend = backend , ** kw )
517
+ backend = backend , use_cache = use_cache , ** kw )
441
518
if workspace_name :
442
519
setup_output += self .workspace (name = workspace_name )
443
520
return setup_output
444
521
522
+ @_cache
445
523
def init (self , input = False , color = False , force_copy = False , plugin_dir = None ,
446
- init_vars = None , backend = True , ** kw ):
524
+ init_vars = None , backend = True , use_cache = False , ** kw ):
447
525
"""Run Terraform init command."""
448
526
cmd_args = parse_args (input = input , color = color , backend = backend ,
449
527
force_copy = force_copy , plugin_dir = plugin_dir ,
@@ -462,8 +540,9 @@ def workspace(self, name=None):
462
540
cmd_args = ['new' , name ]
463
541
return self .execute_command ('workspace' , * cmd_args ).out
464
542
543
+ @_cache
465
544
def plan (self , input = False , color = False , refresh = True , tf_vars = None ,
466
- targets = None , output = False , tf_var_file = None , ** kw ):
545
+ targets = None , output = False , tf_var_file = None , use_cache = False , ** kw ):
467
546
"""
468
547
Run Terraform plan command, optionally returning parsed plan output.
469
548
@@ -496,8 +575,9 @@ def plan(self, input=False, color=False, refresh=True, tf_vars=None,
496
575
except json .JSONDecodeError as e :
497
576
raise TerraformTestError ('Error decoding plan output: {}' .format (e ))
498
577
578
+ @_cache
499
579
def apply (self , input = False , color = False , auto_approve = True , tf_vars = None ,
500
- targets = None , tf_var_file = None , ** kw ):
580
+ targets = None , tf_var_file = None , use_cache = False , ** kw ):
501
581
"""
502
582
Run Terraform apply command.
503
583
@@ -515,7 +595,8 @@ def apply(self, input=False, color=False, auto_approve=True, tf_vars=None,
515
595
tf_var_file = tf_var_file , ** kw )
516
596
return self .execute_command ('apply' , * cmd_args ).out
517
597
518
- def output (self , name = None , color = False , json_format = True , ** kw ):
598
+ @_cache
599
+ def output (self , name = None , color = False , json_format = True , use_cache = False , ** kw ):
519
600
"""Run Terraform output command."""
520
601
cmd_args = []
521
602
if name :
@@ -530,8 +611,9 @@ def output(self, name=None, color=False, json_format=True, **kw):
530
611
_LOGGER .warning ('error decoding output: {}' .format (e ))
531
612
return output
532
613
614
+ @_cache
533
615
def destroy (self , color = False , auto_approve = True , tf_vars = None , targets = None ,
534
- tf_var_file = None , ** kw ):
616
+ tf_var_file = None , use_cache = False , ** kw ):
535
617
"""Run Terraform destroy command."""
536
618
cmd_args = parse_args (color = color , auto_approve = auto_approve ,
537
619
tf_vars = tf_vars , targets = targets ,
@@ -612,7 +694,7 @@ def _parse_run_all_out(output: str, formatter: TerraformJSONBase) -> str:
612
694
class TerragruntTest (TerraformTest ):
613
695
614
696
def __init__ (self , tfdir , basedir = None , binary = 'terragrunt' , env = None ,
615
- tg_run_all = False ):
697
+ tg_run_all = False , enable_cache = False , cache_dir = None ):
616
698
"""A helper class that could be used for testing terragrunt
617
699
618
700
Most operations that apply to :func:`~TerraformTest` also apply to this class.
@@ -629,8 +711,12 @@ def __init__(self, tfdir, basedir=None, binary='terragrunt', env=None,
629
711
binary: (Optional) path to terragrunt command.
630
712
env: a dict with custom environment variables to pass to terraform.
631
713
tg_run_all: whether the test is for terragrunt run-all, default to False
714
+ enable_cache: Determines if the caching enabled for specific methods
715
+ cache_dir: optional base directory to use for caching, defaults to
716
+ the directory of the python file that instantiates this class
632
717
"""
633
- TerraformTest .__init__ (self , tfdir , basedir , binary , env )
718
+ TerraformTest .__init__ (self , tfdir , basedir , binary ,
719
+ env , enable_cache , cache_dir )
634
720
self .tg_run_all = tg_run_all
635
721
if self .tg_run_all :
636
722
self ._plan_formatter = partial (_parse_run_all_out ,
0 commit comments