base_config.go (317 lines of code) (raw):

package golden import ( "context" "fmt" "github.com/zclconf/go-cty/cty/function" "path/filepath" "sort" "sync" "github.com/hashicorp/hcl/v2" "github.com/lonegunmanb/hclfuncs" "github.com/spf13/afero" "github.com/zclconf/go-cty/cty" ) var configFs = afero.NewOsFs() type NewBaseConfigArgs struct { Basedir string VarConfigDir *string Ctx context.Context DslAbbreviation string DslFullName string IgnoreUnknownVariables bool CliFlagAssignedVariables []CliFlagAssignedVariables } type BaseConfig struct { ctx context.Context basedir string varConfigDir *string d *Dag rawBlockAddresses map[string]struct{} dslFullName string dslAbbreviation string cliFlagAssignedVariables []CliFlagAssignedVariables inputVariables map[string]VariableValueRead inputVariableReadsLoader *sync.Once ignoreUnknownVariables bool OverrideFunctions map[string]function.Function } func (c *BaseConfig) Context() context.Context { return c.ctx } func (c *BaseConfig) DslFullName() string { return c.dslFullName } func (c *BaseConfig) DslAbbreviation() string { return c.dslAbbreviation } func (c *BaseConfig) EmptyEvalContext() *hcl.EvalContext { return &hcl.EvalContext{ Functions: merge(hclfuncs.Functions(c.basedir), c.OverrideFunctions), Variables: make(map[string]cty.Value), } } func (c *BaseConfig) EvalContext() *hcl.EvalContext { ctx := c.EmptyEvalContext() for bt, bs := range c.blocksByTypes() { sample := bs[0] if s, ok := sample.(BlockCustomizedRefType); ok { bt = s.CustomizedRefType() } if _, ok := sample.(SingleValueBlock); ok { ctx.Variables[bt] = SingleValues(castBlock[SingleValueBlock](bs)) continue } ctx.Variables[bt] = Values(bs) } return ctx } func NewBasicConfigFromArgs(a NewBaseConfigArgs) *BaseConfig { c := NewBasicConfig(a.Basedir, a.DslFullName, a.DslAbbreviation, a.VarConfigDir, a.CliFlagAssignedVariables, a.Ctx) c.ignoreUnknownVariables = a.IgnoreUnknownVariables return c } func NewBasicConfig(basedir, dslFullName, dslAbbreviation string, varConfigDir *string, cliFlagAssignedVariables []CliFlagAssignedVariables, ctx context.Context) *BaseConfig { if ctx == nil { ctx = context.Background() } c := &BaseConfig{ basedir: basedir, varConfigDir: varConfigDir, ctx: ctx, dslAbbreviation: dslAbbreviation, dslFullName: dslFullName, cliFlagAssignedVariables: cliFlagAssignedVariables, d: newDag(), inputVariableReadsLoader: &sync.Once{}, rawBlockAddresses: make(map[string]struct{}), } return c } func (c *BaseConfig) RunPrePlan() error { return c.runDag(prePlan) } func (c *BaseConfig) RunPlan() error { return c.runDag(dagPlan) } func (c *BaseConfig) GetVertices() map[string]interface{} { if c.d == nil { return nil } return c.d.GetVertices() } func (c *BaseConfig) GetAncestors(id string) (map[string]interface{}, error) { return c.d.GetAncestors(id) } func (c *BaseConfig) GetChildren(id string) (map[string]interface{}, error) { return c.d.GetChildren(id) } func (c *BaseConfig) ValidBlockAddress(address string) bool { if v, err := c.d.GetVertex(address); v != nil && err == nil { return true } if _, ok := c.rawBlockAddresses[address]; ok { return true } return false } func (c *BaseConfig) readInputVariables() (map[string]VariableValueRead, error) { if c.inputVariables != nil { return c.inputVariables, nil } var readErr error c.inputVariableReadsLoader.Do(func() { envVars := c.readVariablesFromEnv() defaultFileVars, err := c.readVariablesFromDefaultVarFiles() if err != nil { readErr = err return } autoFileVars, err := c.readVariablesFromAutoVarFiles() if err != nil { readErr = err return } cliAssignedVariables, err := c.readCliAssignedVariables() if err != nil { readErr = err return } c.inputVariables = merge(envVars, defaultFileVars, autoFileVars, cliAssignedVariables) }) return c.inputVariables, readErr } func (c *BaseConfig) readVariablesFromEnv() map[string]VariableValueRead { valuesFromEnv := make(map[string]VariableValueRead) variables := Blocks[*VariableBlock](c) for _, vb := range variables { valuesFromEnv[vb.Name()] = vb.readValueFromEnv() } return valuesFromEnv } func (c *BaseConfig) readCliAssignedVariables() (map[string]VariableValueRead, error) { r := make(map[string]VariableValueRead) for _, assignedVariables := range c.cliFlagAssignedVariables { reads, err := assignedVariables.Variables(c) if err != nil { return nil, err } r = merge(r, reads) } return r, nil } func (c *BaseConfig) readVariablesFromAutoVarFiles() (map[string]VariableValueRead, error) { autoHclVarFilePattern := fmt.Sprintf("*.auto.%svars", c.dslAbbreviation) autoJsonVarFilePattern := autoHclVarFilePattern + ".json" hclMatches, err := afero.Glob(configFs, filepath.Join(c.variableConfigFilesDir(), autoHclVarFilePattern)) if err != nil { return nil, fmt.Errorf("cannot list auto var files at %s: %+v", c.variableConfigFilesDir(), err) } jsonMatches, err := afero.Glob(configFs, filepath.Join(c.variableConfigFilesDir(), autoJsonVarFilePattern)) if err != nil { return nil, fmt.Errorf("cannot list auto var files at %s: %+v", c.variableConfigFilesDir(), err) } matches := append(hclMatches, jsonMatches...) sort.Strings(matches) return c.readVariablesFromVarFiles(matches) } func (c *BaseConfig) readVariablesFromDefaultVarFiles() (map[string]VariableValueRead, error) { defaultHclVarFilePath := filepath.Join(c.variableConfigFilesDir(), fmt.Sprintf("%s.%svars", c.dslFullName, c.dslAbbreviation)) defaultJsonVarFilePath := defaultHclVarFilePath + ".json" return c.readVariablesFromVarFiles([]string{defaultHclVarFilePath, defaultJsonVarFilePath}) } func (c *BaseConfig) readVariablesFromVarFiles(paths []string) (map[string]VariableValueRead, error) { r := make(map[string]VariableValueRead) for _, path := range paths { vars, err := c.readVariablesFromVarFile(path) if err != nil { return nil, err } r = merge(r, vars) } return r, nil } func (c *BaseConfig) readVariablesFromVarFile(fileName string) (map[string]VariableValueRead, error) { var m map[string]VariableValueRead exist, err := afero.Exists(configFs, fileName) if err != nil { return nil, fmt.Errorf("cannot check existance of %s: %+v", fileName, err) } if !exist { return nil, nil } content, err := afero.ReadFile(configFs, fileName) if err != nil { return nil, fmt.Errorf("cannot open %s: %+v", fileName, err) } m, err = c.ReadVariablesFromSingleVarFile(content, fileName) if err != nil { return nil, fmt.Errorf("cannot parse %s: %+v", fileName, err) } return m, nil } func (c *BaseConfig) ReadVariablesFromSingleVarFile(fileContent []byte, fileName string) (map[string]VariableValueRead, error) { parser := &varFileParserImpl{dslAbbreviation: c.dslAbbreviation} file, err := parser.ParseFile(fileContent, filepath.Base(fileName)) if err != nil { return nil, err } attributes, diag := file.Body.JustAttributes() if diag.HasErrors() { return nil, diag } reads := make(map[string]VariableValueRead) for _, attr := range attributes { value, diag := attr.Expr.Value(nil) var err error if diag.HasErrors() { err = diag } reads[attr.Name] = NewVariableValueRead(attr.Name, &value, err) } return reads, nil } func (c *BaseConfig) variableConfigFilesDir() string { if c.varConfigDir != nil { return *c.varConfigDir } return c.basedir } func (c *BaseConfig) blocksByTypes() map[string][]Block { r := make(map[string][]Block) for _, b := range blocks(c) { bt := b.BlockType() r[bt] = append(r[bt], b) } return r } func (c *BaseConfig) buildDag(blocks []Block) error { for _, b := range blocks { c.rawBlockAddresses[b.Address()] = struct{}{} } return c.d.buildDag(blocks) } func (c *BaseConfig) runDag(onReady func(Block) error) error { return c.d.runDag(c, onReady) } func (c *BaseConfig) expandBlock(b Block) ([]Block, error) { var expandedBlocks []Block hclBlock := b.HclBlock() attr, ok := hclBlock.Body.Attributes["for_each"] if !ok || b.getForEach() != nil { return nil, nil } forEachValue, diag := attr.Expr.Value(c.EvalContext()) if diag.HasErrors() { return nil, diag } if !forEachValue.CanIterateElements() { return nil, fmt.Errorf("invalid `for_each`, except set or map: %s", attr.Range().String()) } address := b.Address() upstreams, err := c.d.GetAncestors(address) if err != nil { return nil, err } downstreams, err := c.d.GetChildren(address) if err != nil { return nil, err } iterator := forEachValue.ElementIterator() for iterator.Next() { key, value := iterator.Element() newBlock := NewHclBlock(hclBlock.Block, hclBlock.wb, NewForEach(key, value)) nb, err := wrapBlock(b.Config(), newBlock) if err != nil { return nil, err } nb.markExpanded() expandedAddress := blockAddress(newBlock) expandedBlocks = append(expandedBlocks, nb) err = c.d.AddVertexByID(expandedAddress, nb) if err != nil { return nil, err } for upstreamAddress := range upstreams { err := c.d.addEdge(upstreamAddress, expandedAddress) if err != nil { return nil, err } } for downstreamAddress := range downstreams { err := c.d.addEdge(expandedAddress, downstreamAddress) if err != nil { return nil, err } } } b.markExpanded() return expandedBlocks, c.d.DeleteVertex(address) } func Traverse[T Block](c *BaseConfig, walker func(b T) error) error { return traverse(c.d, walker) } func merge[TK, TV comparable](maps ...map[TK]TV) map[TK]TV { r := make(map[TK]TV) for _, m := range maps { for k, v := range m { r[k] = v } } return r }