github.com/Azure/tflint-ruleset-azurerm-ext@v0.6.0/rules/resource_block.go (about) 1 package rules 2 3 import ( 4 "fmt" 5 "github.com/hashicorp/go-multierror" 6 "github.com/hashicorp/hcl/v2" 7 "github.com/hashicorp/hcl/v2/hclsyntax" 8 "github.com/hashicorp/hcl/v2/hclwrite" 9 tfjson "github.com/hashicorp/terraform-json" 10 "sort" 11 "strings" 12 ) 13 14 // Block is an interface offering general APIs on resource/nested block 15 type Block interface { 16 // CheckBlock checks the resourceBlock/nestedBlock recursively to find the block not in order, 17 // and invoke the emit function on that block 18 CheckBlock() error 19 20 // ToString prints the sorted block 21 ToString() string 22 23 // DefRange gets the definition range of the block 24 DefRange() hcl.Range 25 } 26 27 // ResourceBlock is the wrapper of a resource block 28 type ResourceBlock struct { 29 File *hcl.File 30 Block *hclsyntax.Block 31 HeadMetaArgs *HeadMetaArgs 32 RequiredArgs *Args 33 OptionalArgs *Args 34 RequiredNestedBlocks *NestedBlocks 35 OptionalNestedBlocks *NestedBlocks 36 TailMetaArgs *Args 37 TailMetaNestedBlocks *NestedBlocks 38 ParentBlockNames []string 39 emit func(block Block) error 40 } 41 42 // CheckBlock checks the resource block and nested block recursively to find the block not in order, 43 // and invoke the emit function on that block 44 func (b *ResourceBlock) CheckBlock() error { 45 if !b.CheckOrder() { 46 return b.emit(b) 47 } 48 var err error 49 for _, nb := range b.nestedBlocks() { 50 if subErr := nb.CheckBlock(); subErr != nil { 51 err = multierror.Append(err, subErr) 52 } 53 } 54 return err 55 } 56 57 // DefRange gets the definition range of the resource block 58 func (b *ResourceBlock) DefRange() hcl.Range { 59 return b.Block.DefRange() 60 } 61 62 // BuildResourceBlock Build the root block wrapper using hclsyntax.Block 63 func BuildResourceBlock(block *hclsyntax.Block, file *hcl.File, 64 emitter func(block Block) error) *ResourceBlock { 65 b := &ResourceBlock{ 66 File: file, 67 Block: block, 68 ParentBlockNames: []string{block.Type, block.Labels[0]}, 69 emit: emitter, 70 } 71 b.buildArgs(block.Body.Attributes) 72 b.buildNestedBlocks(block.Body.Blocks) 73 return b 74 } 75 76 // CheckOrder checks whether the resourceBlock is sorted 77 func (b *ResourceBlock) CheckOrder() bool { 78 return b.sorted() && b.gaped() 79 } 80 81 // ToString prints the sorted resource block 82 func (b *ResourceBlock) ToString() string { 83 headMetaTxt := toString(b.HeadMetaArgs) 84 argTxt := toString(b.RequiredArgs, b.OptionalArgs) 85 nbTxt := toString(b.RequiredNestedBlocks, b.OptionalNestedBlocks) 86 tailMetaArgTxt := toString(b.TailMetaArgs) 87 tailMetaNbTxt := toString(b.TailMetaNestedBlocks) 88 var txts []string 89 for _, subTxt := range []string{ 90 headMetaTxt, 91 argTxt, 92 nbTxt, 93 tailMetaArgTxt, 94 tailMetaNbTxt} { 95 if subTxt != "" { 96 txts = append(txts, subTxt) 97 } 98 } 99 txt := strings.Join(txts, "\n\n") 100 blockHead := string(b.Block.DefRange().SliceBytes(b.File.Bytes)) 101 if strings.TrimSpace(txt) == "" { 102 txt = fmt.Sprintf("%s {}", blockHead) 103 } else { 104 txt = fmt.Sprintf("%s {\n%s\n}", blockHead, txt) 105 } 106 return string(hclwrite.Format([]byte(txt))) 107 } 108 109 func (b *ResourceBlock) nestedBlocks() []*NestedBlock { 110 var nbs []*NestedBlock 111 for _, nb := range []*NestedBlocks{ 112 b.RequiredNestedBlocks, 113 b.OptionalNestedBlocks, 114 b.TailMetaNestedBlocks} { 115 if nb != nil { 116 nbs = append(nbs, nb.Blocks...) 117 } 118 } 119 return nbs 120 } 121 122 func (b *ResourceBlock) buildArgs(attributes hclsyntax.Attributes) { 123 resourceBlock := queryBlockSchema(b.ParentBlockNames) 124 for _, attr := range attributesByLines(attributes) { 125 attrName := attr.Name 126 arg := buildAttrArg(attr, b.File) 127 if IsHeadMeta(attrName) { 128 b.addHeadMetaArg(arg) 129 continue 130 } 131 if IsTailMeta(attrName) { 132 b.addTailMetaArg(arg) 133 continue 134 } 135 if resourceBlock == nil { 136 b.addOptionalAttr(arg) 137 continue 138 } 139 attrSchema, isAzAttr := resourceBlock.Attributes[attrName] 140 if isAzAttr && attrSchema.Required { 141 b.addRequiredAttr(arg) 142 } else { 143 b.addOptionalAttr(arg) 144 } 145 } 146 } 147 148 func attributesByLines(attributes hclsyntax.Attributes) []*hclsyntax.Attribute { 149 var attrs []*hclsyntax.Attribute 150 for _, attr := range attributes { 151 attrs = append(attrs, attr) 152 } 153 sort.Slice(attrs, func(i, j int) bool { 154 return attrs[i].Range().Start.Line < attrs[j].Range().Start.Line 155 }) 156 return attrs 157 } 158 159 func (b *ResourceBlock) buildNestedBlock(nestedBlock *hclsyntax.Block) *NestedBlock { 160 nestedBlockName := nestedBlock.Type 161 sortField := nestedBlock.Type 162 if nestedBlock.Type == "dynamic" { 163 nestedBlockName = nestedBlock.Labels[0] 164 sortField = strings.Join(nestedBlock.Labels, "") 165 } 166 parentBlockNames := append(b.ParentBlockNames, nestedBlockName) 167 if b.Block.Type == "dynamic" && nestedBlockName == "content" { 168 parentBlockNames = b.ParentBlockNames 169 } 170 nb := &NestedBlock{ 171 Name: nestedBlockName, 172 SortField: sortField, 173 Range: nestedBlock.Range(), 174 Block: nestedBlock, 175 ParentBlockNames: parentBlockNames, 176 File: b.File, 177 emit: b.emit, 178 } 179 nb.buildAttributes(nestedBlock.Body.Attributes) 180 nb.buildNestedBlocks(nestedBlock.Body.Blocks) 181 return nb 182 } 183 184 func (b *ResourceBlock) buildNestedBlocks(nestedBlocks hclsyntax.Blocks) { 185 blockSchema := queryBlockSchema(b.ParentBlockNames) 186 for _, nestedBlock := range nestedBlocks { 187 nb := b.buildNestedBlock(nestedBlock) 188 if IsTailMeta(nb.Name) { 189 b.addTailMetaNestedBlock(nb) 190 continue 191 } 192 if metaArgOrUnknownBlock(blockSchema) { 193 b.addOptionalNestedBlock(nb) 194 continue 195 } 196 blockSchema, isAzNestedBlock := blockSchema.NestedBlocks[nb.Name] 197 if isAzNestedBlock && blockSchema.MinItems > 0 { 198 b.addRequiredNestedBlock(nb) 199 } else { 200 b.addOptionalNestedBlock(nb) 201 } 202 } 203 } 204 205 func metaArgOrUnknownBlock(blockSchema *tfjson.SchemaBlock) bool { 206 return blockSchema == nil || blockSchema.NestedBlocks == nil 207 } 208 209 func (b *ResourceBlock) sorted() bool { 210 sections := []Section{ 211 b.HeadMetaArgs, 212 b.RequiredArgs, 213 b.OptionalArgs, 214 b.RequiredNestedBlocks, 215 b.OptionalNestedBlocks, 216 b.TailMetaArgs, 217 b.TailMetaNestedBlocks, 218 } 219 lastEndLine := -1 220 for _, s := range sections { 221 if !s.CheckOrder() { 222 return false 223 } 224 r := s.GetRange() 225 if r == nil { 226 continue 227 } 228 if r.Start.Line <= lastEndLine { 229 return false 230 } 231 lastEndLine = r.End.Line 232 } 233 return true 234 } 235 236 func (b *ResourceBlock) gaped() bool { 237 ranges := []*hcl.Range{ 238 b.HeadMetaArgs.GetRange(), 239 mergeRange(b.RequiredArgs, b.OptionalArgs), 240 mergeRange(b.RequiredNestedBlocks, b.OptionalNestedBlocks), 241 b.TailMetaArgs.GetRange(), 242 b.TailMetaNestedBlocks.GetRange(), 243 } 244 lastEndLine := -2 245 for _, r := range ranges { 246 if r == nil { 247 continue 248 } 249 if r.Start.Line-lastEndLine < 2 { 250 return false 251 } 252 lastEndLine = r.End.Line 253 } 254 return true 255 } 256 257 func (b *ResourceBlock) addHeadMetaArg(arg *Arg) { 258 if b.HeadMetaArgs == nil { 259 b.HeadMetaArgs = &HeadMetaArgs{} 260 } 261 b.HeadMetaArgs.add(arg) 262 } 263 264 func (b *ResourceBlock) addTailMetaArg(arg *Arg) { 265 if b.TailMetaArgs == nil { 266 b.TailMetaArgs = &Args{} 267 } 268 b.TailMetaArgs.add(arg) 269 } 270 271 func (b *ResourceBlock) addRequiredAttr(arg *Arg) { 272 if b.RequiredArgs == nil { 273 b.RequiredArgs = &Args{} 274 } 275 b.RequiredArgs.add(arg) 276 } 277 278 func (b *ResourceBlock) addOptionalAttr(arg *Arg) { 279 if b.OptionalArgs == nil { 280 b.OptionalArgs = &Args{} 281 } 282 b.OptionalArgs.add(arg) 283 } 284 285 func (b *ResourceBlock) addTailMetaNestedBlock(nb *NestedBlock) { 286 if b.TailMetaNestedBlocks == nil { 287 b.TailMetaNestedBlocks = &NestedBlocks{} 288 } 289 b.TailMetaNestedBlocks.add(nb) 290 } 291 292 func (b *ResourceBlock) addRequiredNestedBlock(nb *NestedBlock) { 293 if b.RequiredNestedBlocks == nil { 294 b.RequiredNestedBlocks = &NestedBlocks{} 295 } 296 b.RequiredNestedBlocks.add(nb) 297 } 298 299 func (b *ResourceBlock) addOptionalNestedBlock(nb *NestedBlock) { 300 if b.OptionalNestedBlocks == nil { 301 b.OptionalNestedBlocks = &NestedBlocks{} 302 } 303 b.OptionalNestedBlocks.add(nb) 304 }