github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/pkg/steampipeconfig/parse/mod_parse_context.go (about) 1 package parse 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/hashicorp/hcl/v2/hclsyntax" 9 filehelpers "github.com/turbot/go-kit/files" 10 "github.com/turbot/go-kit/helpers" 11 "github.com/turbot/pipe-fittings/hclhelpers" 12 "github.com/turbot/steampipe/pkg/steampipeconfig/inputvars" 13 "github.com/turbot/steampipe/pkg/steampipeconfig/modconfig" 14 "github.com/turbot/steampipe/pkg/steampipeconfig/versionmap" 15 "github.com/turbot/steampipe/pkg/utils" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 const rootDependencyNode = "rootDependencyNode" 20 21 type ParseModFlag uint32 22 23 const ( 24 CreateDefaultMod ParseModFlag = 1 << iota 25 CreatePseudoResources 26 ) 27 28 /* 29 ReferenceTypeValueMap is the raw data used to build the evaluation context 30 31 When resolving hcl references like : 32 - query.q1 33 - var.v1 34 - mod1.query.my_query.sql 35 36 ReferenceTypeValueMap is keyed by resource type, then by resource name 37 */ 38 type ReferenceTypeValueMap map[string]map[string]cty.Value 39 40 type ModParseContext struct { 41 ParseContext 42 // the mod which is currently being parsed 43 CurrentMod *modconfig.Mod 44 // the workspace lock data 45 WorkspaceLock *versionmap.WorkspaceLock 46 47 Flags ParseModFlag 48 ListOptions *filehelpers.ListOptions 49 50 // Variables are populated in an initial parse pass top we store them on the run context 51 // so we can set them on the mod when we do the main parse 52 53 // Variables is a tree of maps of the variables in the current mod and child dependency mods 54 Variables *modconfig.ModVariableMap 55 56 ParentParseCtx *ModParseContext 57 58 // stack of parent resources for the currently parsed block 59 // (unqualified name) 60 parents []string 61 62 // map of resource children, keyed by parent unqualified name 63 blockChildMap map[string][]string 64 65 // map of top level blocks, for easy checking 66 topLevelBlocks map[*hcl.Block]struct{} 67 // map of block names, keyed by a hash of the blopck 68 blockNameMap map[string]string 69 // map of ReferenceTypeValueMaps keyed by mod name 70 // NOTE: all values from root mod are keyed with "local" 71 referenceValues map[string]ReferenceTypeValueMap 72 73 // a map of just the top level dependencies of the CurrentMod, keyed my full mod DependencyName (with no version) 74 topLevelDependencyMods modconfig.ModMap 75 // if we are loading dependency mod, this contains the details 76 DependencyConfig *ModDependencyConfig 77 } 78 79 func NewModParseContext(workspaceLock *versionmap.WorkspaceLock, rootEvalPath string, flags ParseModFlag, listOptions *filehelpers.ListOptions) *ModParseContext { 80 parseContext := NewParseContext(rootEvalPath) 81 c := &ModParseContext{ 82 ParseContext: parseContext, 83 Flags: flags, 84 WorkspaceLock: workspaceLock, 85 ListOptions: listOptions, 86 87 topLevelDependencyMods: make(modconfig.ModMap), 88 blockChildMap: make(map[string][]string), 89 blockNameMap: make(map[string]string), 90 // initialise reference maps - even though we later overwrite them 91 referenceValues: map[string]ReferenceTypeValueMap{ 92 "local": make(ReferenceTypeValueMap), 93 }, 94 } 95 // add root node - this will depend on all other nodes 96 c.dependencyGraph = c.newDependencyGraph() 97 c.buildEvalContext() 98 99 return c 100 } 101 102 func NewChildModParseContext(parent *ModParseContext, modVersion *versionmap.ResolvedVersionConstraint, rootEvalPath string) *ModParseContext { 103 // create a child run context 104 child := NewModParseContext( 105 parent.WorkspaceLock, 106 rootEvalPath, 107 parent.Flags, 108 parent.ListOptions) 109 // copy our block tpyes 110 child.BlockTypes = parent.BlockTypes 111 // set the child's parent 112 child.ParentParseCtx = parent 113 // set the dependency config 114 child.DependencyConfig = NewDependencyConfig(modVersion) 115 // set variables if parent has any 116 if parent.Variables != nil { 117 childVars, ok := parent.Variables.DependencyVariables[modVersion.Name] 118 if ok { 119 child.Variables = childVars 120 child.Variables.PopulatePublicVariables() 121 child.AddVariablesToEvalContext() 122 } 123 } 124 125 return child 126 } 127 128 func (m *ModParseContext) EnsureWorkspaceLock(mod *modconfig.Mod) error { 129 // if the mod has dependencies, there must a workspace lock object in the run context 130 // (mod MUST be the workspace mod, not a dependency, as we would hit this error as soon as we parse it) 131 if mod.HasDependentMods() && (m.WorkspaceLock.Empty() || m.WorkspaceLock.Incomplete()) { 132 return fmt.Errorf("not all dependencies are installed - run 'steampipe mod install'") 133 } 134 135 return nil 136 } 137 138 func (m *ModParseContext) PushParent(parent modconfig.ModTreeItem) { 139 m.parents = append(m.parents, parent.GetUnqualifiedName()) 140 } 141 142 func (m *ModParseContext) PopParent() string { 143 n := len(m.parents) - 1 144 res := m.parents[n] 145 m.parents = m.parents[:n] 146 return res 147 } 148 149 func (m *ModParseContext) PeekParent() string { 150 if len(m.parents) == 0 { 151 return m.CurrentMod.Name() 152 } 153 return m.parents[len(m.parents)-1] 154 } 155 156 // VariableValueCtyMap converts a map of variables to a map of the underlying cty value 157 func VariableValueCtyMap(variables map[string]*modconfig.Variable) map[string]cty.Value { 158 ret := make(map[string]cty.Value, len(variables)) 159 for k, v := range variables { 160 ret[k] = v.Value 161 } 162 return ret 163 } 164 165 // AddInputVariableValues adds evaluated variables to the run context. 166 // This function is called for the root run context after loading all input variables 167 func (m *ModParseContext) AddInputVariableValues(inputVariables *modconfig.ModVariableMap) { 168 // store the variables 169 m.Variables = inputVariables 170 171 // now add variables into eval context 172 m.AddVariablesToEvalContext() 173 } 174 175 func (m *ModParseContext) AddVariablesToEvalContext() { 176 m.addRootVariablesToReferenceMap() 177 m.addDependencyVariablesToReferenceMap() 178 m.buildEvalContext() 179 } 180 181 // addRootVariablesToReferenceMap sets the Variables property 182 // and adds the variables to the referenceValues map (used to build the eval context) 183 func (m *ModParseContext) addRootVariablesToReferenceMap() { 184 185 variables := m.Variables.RootVariables 186 // write local variables directly into referenceValues map 187 // NOTE: we add with the name "var" not "variable" as that is how variables are referenced 188 m.referenceValues["local"]["var"] = VariableValueCtyMap(variables) 189 } 190 191 // addDependencyVariablesToReferenceMap adds the dependency variables to the referenceValues map 192 // (used to build the eval context) 193 func (m *ModParseContext) addDependencyVariablesToReferenceMap() { 194 // retrieve the resolved dependency versions for the parent mod 195 resolvedVersions := m.WorkspaceLock.InstallCache[m.Variables.Mod.GetInstallCacheKey()] 196 197 for depModName, depVars := range m.Variables.DependencyVariables { 198 alias := resolvedVersions[depModName].Alias 199 if m.referenceValues[alias] == nil { 200 m.referenceValues[alias] = make(ReferenceTypeValueMap) 201 } 202 m.referenceValues[alias]["var"] = VariableValueCtyMap(depVars.RootVariables) 203 } 204 } 205 206 // AddModResources is used to add mod resources to the eval context 207 func (m *ModParseContext) AddModResources(mod *modconfig.Mod) hcl.Diagnostics { 208 if len(m.UnresolvedBlocks) > 0 { 209 // should never happen 210 panic("calling AddModResources on ModParseContext but there are unresolved blocks from a previous parse") 211 } 212 213 var diags hcl.Diagnostics 214 215 moreDiags := m.storeResourceInReferenceValueMap(mod) 216 diags = append(diags, moreDiags...) 217 218 // do not add variables (as they have already been added) 219 // if the resource is for a dependency mod, do not add locals 220 shouldAdd := func(item modconfig.HclResource) bool { 221 if item.BlockType() == modconfig.BlockTypeVariable || 222 item.BlockType() == modconfig.BlockTypeLocals && item.(modconfig.ModTreeItem).GetMod().ShortName != m.CurrentMod.ShortName { 223 return false 224 } 225 return true 226 } 227 228 resourceFunc := func(item modconfig.HclResource) (bool, error) { 229 // add all mod resources (except those excluded) into cty map 230 if shouldAdd(item) { 231 moreDiags := m.storeResourceInReferenceValueMap(item) 232 diags = append(diags, moreDiags...) 233 } 234 // continue walking 235 return true, nil 236 } 237 mod.WalkResources(resourceFunc) 238 239 // rebuild the eval context 240 m.buildEvalContext() 241 return diags 242 } 243 244 func (m *ModParseContext) SetDecodeContent(content *hcl.BodyContent, fileData map[string][]byte) { 245 // put blocks into map as well 246 m.topLevelBlocks = make(map[*hcl.Block]struct{}, len(m.blocks)) 247 for _, b := range content.Blocks { 248 m.topLevelBlocks[b] = struct{}{} 249 } 250 m.ParseContext.SetDecodeContent(content, fileData) 251 } 252 253 // AddDependencies :: the block could not be resolved as it has dependencies 254 // 1) store block as unresolved 255 // 2) add dependencies to our tree of dependencies 256 func (m *ModParseContext) AddDependencies(block *hcl.Block, name string, dependencies map[string]*modconfig.ResourceDependency) hcl.Diagnostics { 257 // TACTICAL if this is NOT a top level block, add a suffix to the block name 258 // this is needed to avoid circular dependency errors if a nested block references 259 // a top level block with the same name 260 if !m.IsTopLevelBlock(block) { 261 name = "nested." + name 262 } 263 return m.ParseContext.AddDependencies(block, name, dependencies) 264 } 265 266 // ShouldCreateDefaultMod returns whether the flag is set to create a default mod if no mod definition exists 267 func (m *ModParseContext) ShouldCreateDefaultMod() bool { 268 return m.Flags&CreateDefaultMod == CreateDefaultMod 269 } 270 271 // CreatePseudoResources returns whether the flag is set to create pseudo resources 272 func (m *ModParseContext) CreatePseudoResources() bool { 273 return m.Flags&CreatePseudoResources == CreatePseudoResources 274 } 275 276 // AddResource stores this resource as a variable to be added to the eval context. 277 func (m *ModParseContext) AddResource(resource modconfig.HclResource) hcl.Diagnostics { 278 diagnostics := m.storeResourceInReferenceValueMap(resource) 279 if diagnostics.HasErrors() { 280 return diagnostics 281 } 282 283 // rebuild the eval context 284 m.buildEvalContext() 285 286 return nil 287 } 288 289 // GetMod finds the mod with given short name, looking only in first level dependencies 290 // this is used to resolve resource references 291 // specifically when the 'children' property of dashboards and benchmarks refers to resource in a dependency mod 292 func (m *ModParseContext) GetMod(modShortName string) *modconfig.Mod { 293 if modShortName == m.CurrentMod.ShortName { 294 return m.CurrentMod 295 } 296 // we need to iterate through dependency mods of the current mod 297 key := m.CurrentMod.GetInstallCacheKey() 298 deps := m.WorkspaceLock.InstallCache[key] 299 for _, dep := range deps { 300 depMod, ok := m.topLevelDependencyMods[dep.Name] 301 if ok && depMod.ShortName == modShortName { 302 return depMod 303 } 304 } 305 return nil 306 } 307 308 func (m *ModParseContext) GetResourceMaps() *modconfig.ResourceMaps { 309 // use the current mod as the base resource map 310 resourceMap := m.CurrentMod.GetResourceMaps() 311 // get a map of top level loaded dep mods 312 deps := m.GetTopLevelDependencyMods() 313 314 dependencyResourceMaps := make([]*modconfig.ResourceMaps, 0, len(deps)) 315 316 // merge in the top level resources of the dependency mods 317 for _, dep := range deps { 318 dependencyResourceMaps = append(dependencyResourceMaps, dep.GetResourceMaps().TopLevelResources()) 319 } 320 321 resourceMap = resourceMap.Merge(dependencyResourceMaps) 322 return resourceMap 323 } 324 325 func (m *ModParseContext) GetResource(parsedName *modconfig.ParsedResourceName) (resource modconfig.HclResource, found bool) { 326 return m.GetResourceMaps().GetResource(parsedName) 327 } 328 329 // build the eval context from the cached reference values 330 func (m *ModParseContext) buildEvalContext() { 331 // convert reference values to cty objects 332 referenceValues := make(map[string]cty.Value) 333 334 // now for each mod add all the values 335 for mod, modMap := range m.referenceValues { 336 if mod == "local" { 337 for k, v := range modMap { 338 referenceValues[k] = cty.ObjectVal(v) 339 } 340 continue 341 } 342 343 // mod map is map[string]map[string]cty.Value 344 // for each element (i.e. map[string]cty.Value) convert to cty object 345 refTypeMap := make(map[string]cty.Value) 346 if mod == "local" { 347 for k, v := range modMap { 348 referenceValues[k] = cty.ObjectVal(v) 349 } 350 } else { 351 for refType, typeValueMap := range modMap { 352 refTypeMap[refType] = cty.ObjectVal(typeValueMap) 353 } 354 } 355 // now convert the referenceValues itself to a cty object 356 referenceValues[mod] = cty.ObjectVal(refTypeMap) 357 } 358 359 // rebuild the eval context 360 m.ParseContext.buildEvalContext(referenceValues) 361 } 362 363 // store the resource as a cty value in the reference valuemap 364 func (m *ModParseContext) storeResourceInReferenceValueMap(resource modconfig.HclResource) hcl.Diagnostics { 365 // add resource to variable map 366 ctyValue, diags := m.getResourceCtyValue(resource) 367 if diags.HasErrors() { 368 return diags 369 } 370 371 // add into the reference value map 372 if diags := m.addReferenceValue(resource, ctyValue); diags.HasErrors() { 373 return diags 374 } 375 376 // remove this resource from unparsed blocks 377 delete(m.UnresolvedBlocks, resource.Name()) 378 379 return nil 380 } 381 382 // convert a HclResource into a cty value, taking into account nested structs 383 func (m *ModParseContext) getResourceCtyValue(resource modconfig.HclResource) (cty.Value, hcl.Diagnostics) { 384 ctyValue, err := resource.(modconfig.CtyValueProvider).CtyValue() 385 if err != nil { 386 return cty.Zero, m.errToCtyValueDiags(resource, err) 387 } 388 // if this is a value map, merge in the values of base structs 389 // if it is NOT a value map, the resource must have overridden CtyValue so do not merge base structs 390 if ctyValue.Type().FriendlyName() != "object" { 391 return ctyValue, nil 392 } 393 // TODO [node_reuse] fetch nested structs and serialise automatically https://github.com/turbot/steampipe/issues/2924 394 valueMap := ctyValue.AsValueMap() 395 if valueMap == nil { 396 valueMap = make(map[string]cty.Value) 397 } 398 base := resource.GetHclResourceImpl() 399 if err := m.mergeResourceCtyValue(base, valueMap); err != nil { 400 return cty.Zero, m.errToCtyValueDiags(resource, err) 401 } 402 403 if qp, ok := resource.(modconfig.QueryProvider); ok { 404 base := qp.GetQueryProviderImpl() 405 if err := m.mergeResourceCtyValue(base, valueMap); err != nil { 406 return cty.Zero, m.errToCtyValueDiags(resource, err) 407 } 408 } 409 410 if treeItem, ok := resource.(modconfig.ModTreeItem); ok { 411 base := treeItem.GetModTreeItemImpl() 412 if err := m.mergeResourceCtyValue(base, valueMap); err != nil { 413 return cty.Zero, m.errToCtyValueDiags(resource, err) 414 } 415 } 416 return cty.ObjectVal(valueMap), nil 417 } 418 419 // merge the cty value of the given interface into valueMap 420 // (note: this mutates valueMap) 421 func (m *ModParseContext) mergeResourceCtyValue(resource modconfig.CtyValueProvider, valueMap map[string]cty.Value) (err error) { 422 defer func() { 423 if r := recover(); r != nil { 424 err = fmt.Errorf("panic in mergeResourceCtyValue: %s", helpers.ToError(r).Error()) 425 } 426 }() 427 ctyValue, err := resource.CtyValue() 428 if err != nil { 429 return err 430 } 431 if ctyValue == cty.Zero { 432 return nil 433 } 434 // merge results 435 for k, v := range ctyValue.AsValueMap() { 436 valueMap[k] = v 437 } 438 return nil 439 } 440 441 func (m *ModParseContext) errToCtyValueDiags(resource modconfig.HclResource, err error) hcl.Diagnostics { 442 return hcl.Diagnostics{&hcl.Diagnostic{ 443 Severity: hcl.DiagError, 444 Summary: fmt.Sprintf("failed to convert resource '%s' to its cty value", resource.Name()), 445 Detail: err.Error(), 446 Subject: resource.GetDeclRange(), 447 }} 448 } 449 450 func (m *ModParseContext) addReferenceValue(resource modconfig.HclResource, value cty.Value) hcl.Diagnostics { 451 parsedName, err := modconfig.ParseResourceName(resource.Name()) 452 if err != nil { 453 return hcl.Diagnostics{&hcl.Diagnostic{ 454 Severity: hcl.DiagError, 455 Summary: fmt.Sprintf("failed to parse resource name %s", resource.Name()), 456 Detail: err.Error(), 457 Subject: resource.GetDeclRange(), 458 }} 459 } 460 461 // TODO validate mod name clashes 462 // TODO mod reserved names 463 // TODO handle aliases 464 465 key := parsedName.Name 466 typeString := parsedName.ItemType 467 468 // most resources will have a mod property - use this if available 469 var mod *modconfig.Mod 470 if modTreeItem, ok := resource.(modconfig.ModTreeItem); ok { 471 mod = modTreeItem.GetMod() 472 } 473 // fall back to current mod 474 if mod == nil { 475 mod = m.CurrentMod 476 } 477 478 modName := mod.ShortName 479 if mod.ModPath == m.RootEvalPath { 480 modName = "local" 481 } 482 variablesForMod, ok := m.referenceValues[modName] 483 // do we have a map of reference values for this dep mod? 484 if !ok { 485 // no - create one 486 variablesForMod = make(ReferenceTypeValueMap) 487 m.referenceValues[modName] = variablesForMod 488 } 489 // do we have a map of reference values for this type 490 variablesForType, ok := variablesForMod[typeString] 491 if !ok { 492 // no - create one 493 variablesForType = make(map[string]cty.Value) 494 } 495 496 // DO NOT update the cached cty values if the value already exists 497 // this can happen in the case of variables where we initialise the context with values read from file 498 // or passed on the command line, // does this item exist in the map 499 if _, ok := variablesForType[key]; !ok { 500 variablesForType[key] = value 501 variablesForMod[typeString] = variablesForType 502 m.referenceValues[modName] = variablesForMod 503 } 504 505 return nil 506 } 507 508 func (m *ModParseContext) IsTopLevelBlock(block *hcl.Block) bool { 509 _, isTopLevel := m.topLevelBlocks[block] 510 return isTopLevel 511 } 512 513 func (m *ModParseContext) AddLoadedDependencyMod(mod *modconfig.Mod) { 514 m.topLevelDependencyMods[mod.DependencyName] = mod 515 } 516 517 // GetTopLevelDependencyMods build a mod map of top level loaded dependencies, keyed by mod name 518 func (m *ModParseContext) GetTopLevelDependencyMods() modconfig.ModMap { 519 return m.topLevelDependencyMods 520 } 521 522 func (m *ModParseContext) SetCurrentMod(mod *modconfig.Mod) error { 523 m.CurrentMod = mod 524 // now we have the mod, load any arg values from the mod require - these will be passed to dependency mods 525 return m.loadModRequireArgs() 526 } 527 528 // when reloading a mod dependency tree to resolve require args values, this function is called after each mod is loaded 529 // to load the require arg values and update the variable values 530 func (m *ModParseContext) loadModRequireArgs() error { 531 //if we have not loaded variable definitions yet, do not load require args 532 if m.Variables == nil { 533 return nil 534 } 535 536 depModVarValues, err := inputvars.CollectVariableValuesFromModRequire(m.CurrentMod, m.WorkspaceLock) 537 if err != nil { 538 return err 539 } 540 if len(depModVarValues) == 0 { 541 return nil 542 } 543 // if any mod require args have an unknown value, we have failed to resolve them - raise an error 544 if err := m.validateModRequireValues(depModVarValues); err != nil { 545 return err 546 } 547 // now update the variables map with the input values 548 depModVarValues.SetVariableValues(m.Variables) 549 550 // now add overridden variables into eval context - in case the root mod references any dependency variable values 551 m.AddVariablesToEvalContext() 552 553 return nil 554 } 555 556 func (m *ModParseContext) validateModRequireValues(depModVarValues inputvars.InputValues) error { 557 if len(depModVarValues) == 0 { 558 return nil 559 } 560 var missingVarExpressions []string 561 requireBlock := m.getModRequireBlock() 562 if requireBlock == nil { 563 return fmt.Errorf("require args extracted but no require block found for %s", m.CurrentMod.Name()) 564 } 565 566 for k, v := range depModVarValues { 567 // if we successfully resolved this value, continue 568 if v.Value.IsKnown() { 569 continue 570 } 571 parsedVarName, err := modconfig.ParseResourceName(k) 572 if err != nil { 573 return err 574 } 575 576 // re-parse the require block manually to extract the range and unresolved arg value expression 577 var errorString string 578 errorString, err = m.getErrorStringForUnresolvedArg(parsedVarName, requireBlock) 579 if err != nil { 580 // if there was an error retrieving details, return less specific error string 581 errorString = fmt.Sprintf("\"%s\" (%s %s)", k, m.CurrentMod.Name(), m.CurrentMod.GetDeclRange().Filename) 582 } 583 584 missingVarExpressions = append(missingVarExpressions, errorString) 585 } 586 587 if errorCount := len(missingVarExpressions); errorCount > 0 { 588 if errorCount == 1 { 589 return fmt.Errorf("failed to resolve dependency mod argument value: %s", missingVarExpressions[0]) 590 } 591 592 return fmt.Errorf("failed to resolve %d dependency mod arguments %s:\n\t%s", errorCount, utils.Pluralize("value", errorCount), strings.Join(missingVarExpressions, "\n\t")) 593 } 594 return nil 595 } 596 597 func (m *ModParseContext) getErrorStringForUnresolvedArg(parsedVarName *modconfig.ParsedResourceName, requireBlock *hclsyntax.Block) (_ string, err error) { 598 defer func() { 599 if r := recover(); r != nil { 600 err = helpers.ToError(r) 601 } 602 }() 603 // which mod and variable is this is this for 604 modShortName := parsedVarName.Mod 605 varName := parsedVarName.Name 606 var modDependencyName string 607 // determine the mod dependency name as that is how it will be keyed in the require map 608 for depName, modVersion := range m.WorkspaceLock.InstallCache[m.CurrentMod.GetInstallCacheKey()] { 609 if modVersion.Alias == modShortName { 610 modDependencyName = depName 611 break 612 } 613 } 614 615 // iterate through require blocks looking for mod blocks 616 for _, b := range requireBlock.Body.Blocks { 617 // only interested in mod blocks 618 if b.Type != "mod" { 619 continue 620 } 621 // if this is not the mod we're looking for, continue 622 if b.Labels[0] != modDependencyName { 623 continue 624 } 625 // now find the failed arg 626 argsAttr, ok := b.Body.Attributes["args"] 627 if !ok { 628 return "", fmt.Errorf("no args block found for %s", modDependencyName) 629 } 630 // iterate over args looking for the correctly named item 631 for _, a := range argsAttr.Expr.(*hclsyntax.ObjectConsExpr).Items { 632 thisVarName, err := a.KeyExpr.Value(&hcl.EvalContext{}) 633 if err != nil { 634 return "", err 635 } 636 637 // is this the var we are looking for? 638 if thisVarName.AsString() != varName { 639 continue 640 } 641 642 // this is the var, get the value expression 643 expr, ok := a.ValueExpr.(*hclsyntax.ScopeTraversalExpr) 644 if !ok { 645 return "", fmt.Errorf("failed to get args details for %s", parsedVarName.ToResourceName()) 646 } 647 // ok we have the expression - build the error string 648 exprString := hclhelpers.TraversalAsString(expr.Traversal) 649 r := expr.Range() 650 sourceRange := fmt.Sprintf("%s:%d", r.Filename, r.Start.Line) 651 res := fmt.Sprintf("\"%s = %s\" (%s %s)", 652 parsedVarName.ToResourceName(), 653 exprString, 654 m.CurrentMod.Name(), 655 sourceRange) 656 return res, nil 657 658 } 659 } 660 return "", fmt.Errorf("failed to get args details for %s", parsedVarName.ToResourceName()) 661 } 662 663 func (m *ModParseContext) getModRequireBlock() *hclsyntax.Block { 664 for _, b := range m.CurrentMod.ResourceWithMetadataBaseRemain.(*hclsyntax.Body).Blocks { 665 if b.Type == modconfig.BlockTypeRequire { 666 return b 667 } 668 } 669 return nil 670 671 }