github.com/diggerhq/digger/libs@v0.0.0-20240604170430-9d61cdf01cc5/digger_config/terragrunt/atlantis/parse_locals.go (about) 1 package atlantis 2 3 // Terragrunt doesn't give us an easy way to access all of the Locals from a module 4 // in an easy to digest way. This file is mostly just follows along how Terragrunt 5 // parses the `locals` blocks and evaluates their contents. 6 7 import ( 8 "github.com/gruntwork-io/go-commons/errors" 9 "github.com/gruntwork-io/terragrunt/config" 10 "github.com/gruntwork-io/terragrunt/options" 11 "github.com/gruntwork-io/terragrunt/util" 12 "github.com/hashicorp/hcl/v2" 13 "github.com/hashicorp/hcl/v2/hclparse" 14 "github.com/zclconf/go-cty/cty" 15 16 "path/filepath" 17 ) 18 19 // ResolvedLocals are the parsed result of local values this module cares about 20 type ResolvedLocals struct { 21 // The Atlantis workflow to use for some project 22 AtlantisWorkflow string 23 24 // Apply requirements to override the global `--apply-requirements` flag 25 ApplyRequirements []string 26 27 // Extra dependencies that can be hardcoded in digger_config 28 ExtraAtlantisDependencies []string 29 30 // If set, a single module will have autoplan turned to this setting 31 AutoPlan *bool 32 33 // If set to true, the module will not be included in the output 34 Skip *bool 35 36 // Terraform version to use just for this project 37 TerraformVersion string 38 39 // If set to true, create Atlantis project 40 markedProject *bool 41 } 42 43 // parseHcl uses the HCL2 parser to parse the given string into an HCL file body. 44 func parseHcl(parser *hclparse.Parser, hcl string, filename string) (file *hcl.File, err error) { 45 // The HCL2 parser and especially cty conversions will panic in many types of errors, so we have to recover from 46 // those panics here and convert them to normal errors 47 defer func() { 48 if recovered := recover(); recovered != nil { 49 err = errors.WithStackTrace(config.PanicWhileParsingConfig{RecoveredValue: recovered, ConfigFile: filename}) 50 } 51 }() 52 53 if filepath.Ext(filename) == ".json" { 54 file, parseDiagnostics := parser.ParseJSON([]byte(hcl), filename) 55 if parseDiagnostics != nil && parseDiagnostics.HasErrors() { 56 return nil, parseDiagnostics 57 } 58 59 return file, nil 60 } 61 62 file, parseDiagnostics := parser.ParseHCL([]byte(hcl), filename) 63 if parseDiagnostics != nil && parseDiagnostics.HasErrors() { 64 return nil, parseDiagnostics 65 } 66 67 return file, nil 68 } 69 70 // Merges in values from a child into a parent set of `local` values 71 func mergeResolvedLocals(parent ResolvedLocals, child ResolvedLocals) ResolvedLocals { 72 if child.AtlantisWorkflow != "" { 73 parent.AtlantisWorkflow = child.AtlantisWorkflow 74 } 75 76 if child.TerraformVersion != "" { 77 parent.TerraformVersion = child.TerraformVersion 78 } 79 80 if child.AutoPlan != nil { 81 parent.AutoPlan = child.AutoPlan 82 } 83 84 if child.Skip != nil { 85 parent.Skip = child.Skip 86 } 87 88 if child.markedProject != nil { 89 parent.markedProject = child.markedProject 90 } 91 92 if child.ApplyRequirements != nil || len(child.ApplyRequirements) > 0 { 93 parent.ApplyRequirements = child.ApplyRequirements 94 } 95 96 parent.ExtraAtlantisDependencies = append(parent.ExtraAtlantisDependencies, child.ExtraAtlantisDependencies...) 97 98 return parent 99 } 100 101 // Parses a given file, returning a map of all it's `local` values 102 func parseLocals(path string, terragruntOptions *options.TerragruntOptions, includeFromChild *config.IncludeConfig) (ResolvedLocals, error) { 103 configString, err := util.ReadFileAsString(path) 104 if err != nil { 105 return ResolvedLocals{}, err 106 } 107 108 // Parse the HCL string into an AST body 109 parser := hclparse.NewParser() 110 file, err := parseHcl(parser, configString, path) 111 if err != nil { 112 return ResolvedLocals{}, err 113 } 114 115 // Decode just the Base blocks. See the function docs for DecodeBaseBlocks for more info on what base blocks are. 116 extensions, err := config.DecodeBaseBlocks(terragruntOptions, parser, file, path, includeFromChild, nil) 117 if err != nil { 118 return ResolvedLocals{}, err 119 } 120 localsAsCty := extensions.Locals 121 trackInclude := extensions.TrackInclude 122 123 // Recurse on the parent to merge in the locals from that file 124 mergedParentLocals := ResolvedLocals{} 125 if trackInclude != nil && includeFromChild == nil { 126 for _, includeConfig := range trackInclude.CurrentList { 127 parentLocals, _ := parseLocals(includeConfig.Path, terragruntOptions, &includeConfig) 128 mergedParentLocals = mergeResolvedLocals(mergedParentLocals, parentLocals) 129 } 130 } 131 childLocals := resolveLocals(*localsAsCty) 132 133 return mergeResolvedLocals(mergedParentLocals, childLocals), nil 134 } 135 136 func resolveLocals(localsAsCty cty.Value) ResolvedLocals { 137 resolved := ResolvedLocals{} 138 139 // Return an empty set of locals if no `locals` block was present 140 if localsAsCty == cty.NilVal { 141 return resolved 142 } 143 rawLocals := localsAsCty.AsValueMap() 144 145 workflowValue, ok := rawLocals["atlantis_workflow"] 146 if ok { 147 resolved.AtlantisWorkflow = workflowValue.AsString() 148 } 149 150 versionValue, ok := rawLocals["atlantis_terraform_version"] 151 if ok { 152 resolved.TerraformVersion = versionValue.AsString() 153 } 154 155 autoPlanValue, ok := rawLocals["atlantis_autoplan"] 156 if ok { 157 hasValue := autoPlanValue.True() 158 resolved.AutoPlan = &hasValue 159 } 160 161 skipValue, ok := rawLocals["atlantis_skip"] 162 if ok { 163 hasValue := skipValue.True() 164 resolved.Skip = &hasValue 165 } 166 167 applyReqs, ok := rawLocals["atlantis_apply_requirements"] 168 if ok { 169 resolved.ApplyRequirements = []string{} 170 it := applyReqs.ElementIterator() 171 for it.Next() { 172 _, val := it.Element() 173 resolved.ApplyRequirements = append(resolved.ApplyRequirements, val.AsString()) 174 } 175 } 176 177 markedProject, ok := rawLocals["atlantis_project"] 178 if ok { 179 hasValue := markedProject.True() 180 resolved.markedProject = &hasValue 181 } 182 183 extraDependenciesAsCty, ok := rawLocals["extra_atlantis_dependencies"] 184 if ok { 185 it := extraDependenciesAsCty.ElementIterator() 186 for it.Next() { 187 _, val := it.Element() 188 resolved.ExtraAtlantisDependencies = append( 189 resolved.ExtraAtlantisDependencies, 190 filepath.ToSlash(val.AsString()), 191 ) 192 } 193 } 194 195 return resolved 196 }