mirror of
https://github.com/turbot/steampipe.git
synced 2026-02-22 14:00:14 -05:00
219 lines
8.2 KiB
Go
219 lines
8.2 KiB
Go
package steampipeconfig
|
|
|
|
import (
|
|
"context"
|
|
"golang.org/x/exp/maps"
|
|
"log"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/spf13/viper"
|
|
"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
|
|
"github.com/turbot/steampipe/pkg/constants"
|
|
"github.com/turbot/steampipe/pkg/error_helpers"
|
|
"github.com/turbot/steampipe/pkg/steampipeconfig/inputvars"
|
|
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
|
|
"github.com/turbot/steampipe/pkg/steampipeconfig/parse"
|
|
"github.com/turbot/steampipe/pkg/steampipeconfig/versionmap"
|
|
"github.com/turbot/steampipe/pkg/utils"
|
|
"github.com/turbot/terraform-components/tfdiags"
|
|
)
|
|
|
|
func LoadVariableDefinitions(ctx context.Context, variablePath string, parseCtx *parse.ModParseContext) (*modconfig.ModVariableMap, error) {
|
|
// only load mod and variables blocks
|
|
parseCtx.BlockTypes = []string{modconfig.BlockTypeVariable}
|
|
mod, errAndWarnings := LoadMod(ctx, variablePath, parseCtx)
|
|
if errAndWarnings.GetError() != nil {
|
|
return nil, errAndWarnings.GetError()
|
|
}
|
|
|
|
variableMap := modconfig.NewModVariableMap(mod)
|
|
|
|
return variableMap, nil
|
|
}
|
|
|
|
func GetVariableValues(parseCtx *parse.ModParseContext, variableMap *modconfig.ModVariableMap, validate bool) (*modconfig.ModVariableMap, error_helpers.ErrorAndWarnings) {
|
|
log.Printf("[INFO] GetVariableValues")
|
|
// now resolve all input variables
|
|
inputValues, errorsAndWarnings := getInputVariables(parseCtx, variableMap, validate)
|
|
if errorsAndWarnings.Error == nil {
|
|
// now update the variables map with the input values
|
|
inputValues.SetVariableValues(variableMap)
|
|
}
|
|
|
|
return variableMap, errorsAndWarnings
|
|
}
|
|
|
|
func getInputVariables(parseCtx *parse.ModParseContext, variableMap *modconfig.ModVariableMap, validate bool) (inputvars.InputValues, error_helpers.ErrorAndWarnings) {
|
|
variableFileArgs := viper.GetStringSlice(constants.ArgVarFile)
|
|
variableArgs := viper.GetStringSlice(constants.ArgVariable)
|
|
|
|
// get mod and mod path from run context
|
|
mod := parseCtx.CurrentMod
|
|
path := mod.ModPath
|
|
|
|
log.Printf("[INFO] getInputVariables, variableFileArgs: %s, variableArgs: %s", variableFileArgs, variableArgs)
|
|
|
|
var inputValuesUnparsed, err = inputvars.CollectVariableValues(path, variableFileArgs, variableArgs, parseCtx.CurrentMod)
|
|
if err != nil {
|
|
log.Printf("[WARN] CollectVariableValues failed: %s", err.Error())
|
|
|
|
return nil, error_helpers.NewErrorsAndWarning(err)
|
|
}
|
|
|
|
log.Printf("[INFO] collected unparsed input values for vars: %s", strings.Join(maps.Keys(inputValuesUnparsed), ","))
|
|
|
|
if validate {
|
|
if err := identifyAllMissingVariables(parseCtx, variableMap, inputValuesUnparsed); err != nil {
|
|
log.Printf("[INFO] identifyAllMissingVariables returned a validation error: %s", err.Error())
|
|
|
|
return nil, error_helpers.NewErrorsAndWarning(err)
|
|
}
|
|
}
|
|
|
|
// only parse values for public variables
|
|
parsedValues, diags := inputvars.ParseVariableValues(inputValuesUnparsed, variableMap, validate)
|
|
if diags.HasErrors() {
|
|
log.Printf("[INFO] ParseVariableValues returned error: %s", diags.Err())
|
|
} else {
|
|
log.Printf("[INFO] parsed values for public variables: %s", strings.Join(maps.Keys(parsedValues), ","))
|
|
}
|
|
|
|
if validate {
|
|
moreDiags := inputvars.CheckInputVariables(variableMap.PublicVariables, parsedValues)
|
|
diags = append(diags, moreDiags...)
|
|
}
|
|
|
|
return parsedValues, newVariableValidationResult(diags)
|
|
}
|
|
|
|
func newVariableValidationResult(diags tfdiags.Diagnostics) error_helpers.ErrorAndWarnings {
|
|
warnings := plugin.DiagsToWarnings(diags.ToHCL())
|
|
var err error
|
|
if diags.HasErrors() {
|
|
err = newVariableValidationFailedError(diags)
|
|
}
|
|
return error_helpers.NewErrorsAndWarning(err, warnings...)
|
|
}
|
|
|
|
func identifyAllMissingVariables(parseCtx *parse.ModParseContext, variableMap *modconfig.ModVariableMap, variableValues map[string]inputvars.UnparsedVariableValue) error {
|
|
// convert variableValues into a lookup
|
|
var variableValueLookup = utils.SliceToLookup(maps.Keys(variableValues))
|
|
missingVarsMap, err := identifyMissingVariablesForDependencies(parseCtx.WorkspaceLock, variableMap, variableValueLookup, nil)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(missingVarsMap) == 0 {
|
|
// all good
|
|
return nil
|
|
}
|
|
|
|
// build a MissingVariableError
|
|
missingVarErr := NewMissingVarsError(parseCtx.CurrentMod)
|
|
|
|
// build a lookup with the dependency path of the root mod and all top level dependencies
|
|
rootName := variableMap.Mod.ShortName
|
|
topLevelModLookup := map[DependencyPathKey]struct{}{DependencyPathKey(rootName): {}}
|
|
for dep := range parseCtx.WorkspaceLock.InstallCache {
|
|
depPathKey := newDependencyPathKey(rootName, dep)
|
|
topLevelModLookup[depPathKey] = struct{}{}
|
|
}
|
|
for depPath, missingVars := range missingVarsMap {
|
|
if _, isTopLevel := topLevelModLookup[depPath]; isTopLevel {
|
|
missingVarErr.MissingVariables = append(missingVarErr.MissingVariables, missingVars...)
|
|
} else {
|
|
missingVarErr.MissingTransitiveVariables[depPath] = missingVars
|
|
}
|
|
}
|
|
|
|
return missingVarErr
|
|
}
|
|
|
|
func identifyMissingVariablesForDependencies(workspaceLock *versionmap.WorkspaceLock, variableMap *modconfig.ModVariableMap, parentVariableValuesLookup map[string]struct{}, dependencyPath []string) (map[DependencyPathKey][]*modconfig.Variable, error) {
|
|
// return a map of missing variables, keyed by dependency path
|
|
res := make(map[DependencyPathKey][]*modconfig.Variable)
|
|
|
|
// update the path to this dependency
|
|
dependencyPath = append(dependencyPath, variableMap.Mod.GetInstallCacheKey())
|
|
|
|
// clone variableValuesLookup so we can mutate it with depdency specific args overrides
|
|
var variableValueLookup = make(map[string]struct{}, len(parentVariableValuesLookup))
|
|
for k := range parentVariableValuesLookup {
|
|
// convert the variable name to the short name if it is fully qualified and belongs to the current mod
|
|
k = getVariableValueMapKey(k, variableMap)
|
|
|
|
variableValueLookup[k] = struct{}{}
|
|
}
|
|
|
|
// first get any args specified in the mod requires
|
|
// note the actual value of these may be unknown as we have not yet resolved
|
|
depModArgs, err := inputvars.CollectVariableValuesFromModRequire(variableMap.Mod, workspaceLock)
|
|
for varName := range depModArgs {
|
|
// convert the variable name to the short name if it is fully qualified and belongs to the current mod
|
|
varName = getVariableValueMapKey(varName, variableMap)
|
|
|
|
variableValueLookup[varName] = struct{}{}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// handle root variables
|
|
missingVariables := identifyMissingVariables(variableMap.RootVariables, variableValueLookup, variableMap.Mod.ShortName)
|
|
if len(missingVariables) > 0 {
|
|
res[newDependencyPathKey(dependencyPath...)] = missingVariables
|
|
}
|
|
|
|
// now iterate through all the dependency variable maps
|
|
for _, dependencyVariableMap := range variableMap.DependencyVariables {
|
|
childMissingMap, err := identifyMissingVariablesForDependencies(workspaceLock, dependencyVariableMap, variableValueLookup, dependencyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// add results into map
|
|
for k, v := range childMissingMap {
|
|
res[k] = v
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
// getVariableValueMapKey checks whether the variable is fully qualified and belongs to the current mod,
|
|
// if so use the short name
|
|
func getVariableValueMapKey(k string, variableMap *modconfig.ModVariableMap) string {
|
|
// attempt to parse the variable name.
|
|
// Note: if the variable is not fully qualified (e.g. "var_name"), ParseResourceName will return an error
|
|
// in which case we add it to our map unchanged
|
|
parsedName, err := modconfig.ParseResourceName(k)
|
|
// if this IS a dependency variable, the parse will success
|
|
// if the mod name is the same as the current mod (variableMap.Mod)
|
|
// then add a map entry with the variable short name
|
|
// this will allow us to match the variable value to a variable defined in this mod
|
|
if err == nil && parsedName.Mod == variableMap.Mod.ShortName {
|
|
k = parsedName.Name
|
|
}
|
|
return k
|
|
}
|
|
|
|
func identifyMissingVariables(variableMap map[string]*modconfig.Variable, variableValuesLookup map[string]struct{}, modName string) []*modconfig.Variable {
|
|
|
|
var needed []*modconfig.Variable
|
|
|
|
for shortName, v := range variableMap {
|
|
if !v.Required() {
|
|
continue // We only prompt for required variables
|
|
}
|
|
_, unparsedValExists := variableValuesLookup[shortName]
|
|
|
|
if !unparsedValExists {
|
|
needed = append(needed, v)
|
|
}
|
|
}
|
|
sort.SliceStable(needed, func(i, j int) bool {
|
|
return needed[i].Name() < needed[j].Name()
|
|
})
|
|
return needed
|
|
|
|
}
|