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 }