github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/terraform/resource_block.go (about) 1 package terraform 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 "text/template" 8 ) 9 10 type PlanReference struct { 11 Value interface{} 12 } 13 14 type PlanBlock struct { 15 Type string 16 Name string 17 BlockType string 18 Blocks map[string]map[string]interface{} 19 Attributes map[string]interface{} 20 } 21 22 func NewPlanBlock(blockType, resourceType, resourceName string) *PlanBlock { 23 if blockType == "managed" { 24 blockType = "resource" 25 } 26 27 return &PlanBlock{ 28 Type: resourceType, 29 Name: resourceName, 30 BlockType: blockType, 31 Blocks: make(map[string]map[string]interface{}), 32 Attributes: make(map[string]interface{}), 33 } 34 } 35 36 func (rb *PlanBlock) HasAttribute(attribute string) bool { 37 for k := range rb.Attributes { 38 if k == attribute { 39 return true 40 } 41 } 42 return false 43 } 44 45 func (rb *PlanBlock) ToHCL() string { 46 47 resourceTmpl, err := template.New("resource").Funcs(template.FuncMap{ 48 "RenderValue": renderTemplateValue, 49 "RenderPrimitive": renderPrimitive, 50 }).Parse(resourceTemplate) 51 if err != nil { 52 panic(err) 53 } 54 55 var res bytes.Buffer 56 if err := resourceTmpl.Execute(&res, map[string]interface{}{ 57 "BlockType": rb.BlockType, 58 "Type": rb.Type, 59 "Name": rb.Name, 60 "Attributes": rb.Attributes, 61 "Blocks": rb.Blocks, 62 }); err != nil { 63 return "" 64 } 65 return res.String() 66 } 67 68 var resourceTemplate = `{{ .BlockType }} "{{ .Type }}" "{{ .Name }}" { 69 {{ range $name, $value := .Attributes }}{{ if $value }}{{ $name }} {{ RenderValue $value }} 70 {{end}}{{ end }}{{ range $name, $block := .Blocks }}{{ $name }} { 71 {{ range $name, $value := $block }}{{ if $value }}{{ $name }} {{ RenderValue $value }} 72 {{end}}{{ end }}} 73 {{end}}}` 74 75 func renderTemplateValue(val interface{}) string { 76 switch t := val.(type) { 77 case map[string]interface{}: 78 return fmt.Sprintf("= %s", renderMap(t)) 79 case []interface{}: 80 return renderSlice(t) 81 default: 82 return fmt.Sprintf("= %s", renderPrimitive(val)) 83 } 84 } 85 86 func renderPrimitive(val interface{}) string { 87 switch t := val.(type) { 88 case PlanReference: 89 return fmt.Sprintf("%v", t.Value) 90 case string: 91 if strings.Contains(t, "\n") { 92 return fmt.Sprintf(`<<EOF 93 %s 94 EOF 95 `, t) 96 } 97 return fmt.Sprintf("%q", t) 98 case []interface{}: 99 return renderSlice(t) 100 default: 101 return fmt.Sprintf("%#v", t) 102 } 103 104 } 105 106 func renderSlice(vals []interface{}) string { 107 if len(vals) == 0 { 108 return "[]" 109 } 110 111 val := vals[0] 112 113 switch t := val.(type) { 114 // if vals[0] is a map[string]interface this is a block, so render it as a map 115 case map[string]interface{}: 116 return renderMap(t) 117 // otherwise its going to be just a list of primitives 118 default: 119 result := " = [\n" 120 for _, v := range vals { 121 result = fmt.Sprintf("%s\t%v,\n", result, renderPrimitive(v)) 122 } 123 result = fmt.Sprintf("%s]", result) 124 return result 125 } 126 } 127 128 func renderMap(val map[string]interface{}) string { 129 if len(val) == 0 { 130 return "{}" 131 } 132 133 result := "{\n" 134 for k, v := range val { 135 if v == nil { 136 continue 137 } 138 result = fmt.Sprintf("%s\t%s = %s\n", result, k, renderPrimitive(v)) 139 } 140 result = fmt.Sprintf("%s}", result) 141 return result 142 }