github.com/jpreese/tflint@v0.19.2-0.20200908152133-b01686250fb6/tflint/runner_walk.go (about)

     1  package tflint
     2  
     3  import (
     4  	"log"
     5  
     6  	"github.com/hashicorp/terraform/configs"
     7  
     8  	hcl "github.com/hashicorp/hcl/v2"
     9  	"github.com/hashicorp/hcl/v2/hclsyntax"
    10  )
    11  
    12  // WalkResourceAttributes searches for resources and passes the appropriate attributes to the walker function
    13  func (r *Runner) WalkResourceAttributes(resource, attributeName string, walker func(*hcl.Attribute) error) error {
    14  	for _, resource := range r.LookupResourcesByType(resource) {
    15  		ok, err := r.willEvaluateResource(resource)
    16  		if err != nil {
    17  			return err
    18  		}
    19  		if !ok {
    20  			log.Printf("[WARN] Skip walking `%s` because it may not be created", resource.Type+"."+resource.Name)
    21  			continue
    22  		}
    23  
    24  		body, _, diags := resource.Config.PartialContent(&hcl.BodySchema{
    25  			Attributes: []hcl.AttributeSchema{
    26  				{
    27  					Name: attributeName,
    28  				},
    29  			},
    30  		})
    31  		if diags.HasErrors() {
    32  			return diags
    33  		}
    34  
    35  		if attribute, ok := body.Attributes[attributeName]; ok {
    36  			log.Printf("[DEBUG] Walk `%s` attribute", resource.Type+"."+resource.Name+"."+attributeName)
    37  			err := r.WithExpressionContext(attribute.Expr, func() error {
    38  				return walker(attribute)
    39  			})
    40  			if err != nil {
    41  				return err
    42  			}
    43  		}
    44  	}
    45  
    46  	return nil
    47  }
    48  
    49  // WalkResourceBlocks walks all blocks of the passed resource and invokes the passed function
    50  func (r *Runner) WalkResourceBlocks(resource, blockType string, walker func(*hcl.Block) error) error {
    51  	for _, resource := range r.LookupResourcesByType(resource) {
    52  		ok, err := r.willEvaluateResource(resource)
    53  		if err != nil {
    54  			return err
    55  		}
    56  		if !ok {
    57  			log.Printf("[WARN] Skip walking `%s` because it may not be created", resource.Type+"."+resource.Name)
    58  			continue
    59  		}
    60  
    61  		body, _, diags := resource.Config.PartialContent(&hcl.BodySchema{
    62  			Blocks: []hcl.BlockHeaderSchema{
    63  				{
    64  					Type: blockType,
    65  				},
    66  			},
    67  		})
    68  		if diags.HasErrors() {
    69  			return diags
    70  		}
    71  
    72  		for _, block := range body.Blocks {
    73  			log.Printf("[DEBUG] Walk `%s` block", resource.Type+"."+resource.Name+"."+blockType)
    74  			err := walker(block)
    75  			if err != nil {
    76  				return err
    77  			}
    78  		}
    79  
    80  		// Walk in the same way for dynamic blocks. Note that we are not expanding blocks.
    81  		// Therefore, expressions that use iterator are unevaluable.
    82  		dynBody, _, diags := resource.Config.PartialContent(&hcl.BodySchema{
    83  			Blocks: []hcl.BlockHeaderSchema{
    84  				{
    85  					Type:       "dynamic",
    86  					LabelNames: []string{"name"},
    87  				},
    88  			},
    89  		})
    90  		if diags.HasErrors() {
    91  			return diags
    92  		}
    93  
    94  		for _, block := range dynBody.Blocks {
    95  			if len(block.Labels) == 1 && block.Labels[0] == blockType {
    96  				body, _, diags = block.Body.PartialContent(&hcl.BodySchema{
    97  					Blocks: []hcl.BlockHeaderSchema{
    98  						{
    99  							Type: "content",
   100  						},
   101  					},
   102  				})
   103  				if diags.HasErrors() {
   104  					return diags
   105  				}
   106  
   107  				for _, block := range body.Blocks {
   108  					log.Printf("[DEBUG] Walk dynamic `%s` block", resource.Type+"."+resource.Name+"."+blockType)
   109  					err := walker(block)
   110  					if err != nil {
   111  						return err
   112  					}
   113  				}
   114  			}
   115  		}
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // WalkResources walks all blocks of the passed resource and invokes the passed function
   122  func (r *Runner) WalkResources(resource string, walker func(*configs.Resource) error) error {
   123  
   124  	for _, resource := range r.LookupResourcesByType(resource) {
   125  		err := walker(resource)
   126  		if err != nil {
   127  			return err
   128  		}
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  // WalkModuleCalls walks all module calls and invokes the passed function
   135  func (r *Runner) WalkModuleCalls(walker func(*configs.ModuleCall) error) error {
   136  	for _, call := range r.TFConfig.Module.ModuleCalls {
   137  		if err := walker(call); err != nil {
   138  			return err
   139  		}
   140  	}
   141  
   142  	return nil
   143  }
   144  
   145  // WalkExpressions visits all blocks that can contain expressions:
   146  // resource, data, module, provider, locals, and output. It calls the walker
   147  // function with every expression it encounters and halts if the walker
   148  // returns an error.
   149  func (r *Runner) WalkExpressions(walker func(hcl.Expression) error) error {
   150  	visit := func(expr hcl.Expression) error {
   151  		return r.WithExpressionContext(expr, func() error {
   152  			return walker(expr)
   153  		})
   154  	}
   155  
   156  	for _, resource := range r.TFConfig.Module.ManagedResources {
   157  		if err := r.walkBody(resource.Config, visit); err != nil {
   158  			return err
   159  		}
   160  	}
   161  	for _, resource := range r.TFConfig.Module.DataResources {
   162  		if err := r.walkBody(resource.Config, visit); err != nil {
   163  			return err
   164  		}
   165  	}
   166  	for _, module := range r.TFConfig.Module.ModuleCalls {
   167  		if err := r.walkBody(module.Config, visit); err != nil {
   168  			return err
   169  		}
   170  	}
   171  	for _, provider := range r.TFConfig.Module.ProviderConfigs {
   172  		if err := r.walkBody(provider.Config, visit); err != nil {
   173  			return err
   174  		}
   175  	}
   176  	for _, local := range r.TFConfig.Module.Locals {
   177  		if err := visit(local.Expr); err != nil {
   178  			return err
   179  		}
   180  	}
   181  	for _, output := range r.TFConfig.Module.Outputs {
   182  		if err := visit(output.Expr); err != nil {
   183  			return err
   184  		}
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  // walkBody visits all attributes and passes their expressions to the walker function.
   191  // It recurses on nested blocks.
   192  func (r *Runner) walkBody(b hcl.Body, walker func(hcl.Expression) error) error {
   193  	body, ok := b.(*hclsyntax.Body)
   194  	if !ok {
   195  		return r.walkAttributes(b, walker)
   196  	}
   197  
   198  	for _, attr := range body.Attributes {
   199  		if err := walker(attr.Expr); err != nil {
   200  			return err
   201  		}
   202  	}
   203  
   204  	for _, block := range body.Blocks {
   205  		if err := r.walkBody(block.Body, walker); err != nil {
   206  			return err
   207  		}
   208  	}
   209  
   210  	return nil
   211  }
   212  
   213  // walkAttributes visits all attributes and passes their expressions to the walker function.
   214  // It should be used only for non-HCL bodies (JSON) when distinguishing a block from an attribute
   215  // is not possible without a schema.
   216  func (r *Runner) walkAttributes(b hcl.Body, walker func(hcl.Expression) error) error {
   217  	attrs, diags := b.JustAttributes()
   218  	if diags.HasErrors() {
   219  		return diags
   220  	}
   221  
   222  	for _, attr := range attrs {
   223  		if err := walker(attr.Expr); err != nil {
   224  			return err
   225  		}
   226  	}
   227  
   228  	return nil
   229  }