pkg/mptf_config.go (209 lines of code) (raw):

package pkg import ( "context" "encoding/json" "fmt" "path/filepath" "strings" "github.com/Azure/golden" filesystem "github.com/Azure/mapotf/pkg/fs" "github.com/Azure/mapotf/pkg/terraform" "github.com/hashicorp/go-multierror" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclwrite" "github.com/spf13/afero" "github.com/zclconf/go-cty/cty/function" ) var _ golden.Config = &MetaProgrammingTFConfig{} type MetaProgrammingTFConfig struct { *golden.BaseConfig resourceBlocks map[string]*terraform.RootBlock dataBlocks map[string]*terraform.RootBlock variableBlocks map[string]*terraform.RootBlock localBlocks map[string]*terraform.RootBlock outputBlocks map[string]*terraform.RootBlock moduleBlocks map[string]*terraform.RootBlock terraformBlock *terraform.RootBlock allRootBlocks []*terraform.RootBlock module *terraform.Module } func NewMetaProgrammingTFConfig(m *TerraformModuleRef, varConfigDir *string, hclBlocks []*golden.HclBlock, cliFlagAssignedVars []golden.CliFlagAssignedVariables, ctx context.Context) (*MetaProgrammingTFConfig, error) { baseConfig := golden.NewBasicConfigFromArgs(golden.NewBaseConfigArgs{ Basedir: m.AbsDir, DslFullName: "mapotf", DslAbbreviation: "mptf", VarConfigDir: varConfigDir, CliFlagAssignedVariables: cliFlagAssignedVars, Ctx: ctx, IgnoreUnknownVariables: true, }) baseConfig.OverrideFunctions = map[string]function.Function{ "tohcl": ToHclFunc, } cfg := &MetaProgrammingTFConfig{ BaseConfig: baseConfig, } if err := cfg.reloadTerraformModule(m); err != nil { return nil, err } return cfg, golden.InitConfig(cfg, hclBlocks) } func (c *MetaProgrammingTFConfig) reloadTerraformModule(m *TerraformModuleRef) error { module, err := terraform.LoadModule(m.toTerraformPkgType()) if err != nil { return err } c.resourceBlocks = groupByAddress(module.ResourceBlocks) c.dataBlocks = groupByAddress(module.DataBlocks) c.moduleBlocks = groupByAddress(module.ModuleBlocks) c.variableBlocks = groupByAddress(module.Variables) c.outputBlocks = groupByAddress(module.Outputs) c.localBlocks = groupByAddress(module.Locals) if len(module.TerraformBlocks) > 0 { c.terraformBlock = module.TerraformBlocks[0] } c.allRootBlocks = module.Blocks() c.module = module return nil } func (c *MetaProgrammingTFConfig) Init(hclBlocks []*golden.HclBlock) error { return golden.InitConfig(c, hclBlocks) } func (c *MetaProgrammingTFConfig) ResourceBlocks() []*terraform.RootBlock { return c.slice(c.resourceBlocks) } func (c *MetaProgrammingTFConfig) DataBlocks() []*terraform.RootBlock { return c.slice(c.dataBlocks) } func (c *MetaProgrammingTFConfig) VariableBlocks() []*terraform.RootBlock { return c.slice(c.variableBlocks) } func (c *MetaProgrammingTFConfig) OutputBlocks() []*terraform.RootBlock { return c.slice(c.outputBlocks) } func (c *MetaProgrammingTFConfig) LocalBlocks() []*terraform.RootBlock { return c.slice(c.localBlocks) } func (c *MetaProgrammingTFConfig) ModuleBlocks() []*terraform.RootBlock { return c.slice(c.moduleBlocks) } func (c *MetaProgrammingTFConfig) TerraformBlock() *terraform.RootBlock { return c.terraformBlock } func (c *MetaProgrammingTFConfig) RootBlock(address string) *terraform.RootBlock { if strings.HasPrefix(address, "resource.") { return c.resourceBlocks[address] } if strings.HasPrefix(address, "data.") { return c.dataBlocks[address] } if strings.HasPrefix(address, "variable.") { return c.variableBlocks[address] } if strings.HasPrefix(address, "local.") { return c.localBlocks[address] } if strings.HasPrefix(address, "output.") { return c.outputBlocks[address] } if strings.HasPrefix(address, "module.") { return c.moduleBlocks[address] } if address == "terraform" { return c.terraformBlock } return nil } func LoadMPTFHclBlocks(ignoreUnsupportedBlock bool, dir string) ([]*golden.HclBlock, error) { fs := filesystem.Fs matches, err := afero.Glob(fs, filepath.Join(dir, "*.mptf.hcl")) if err != nil { return nil, err } if len(matches) == 0 { return nil, fmt.Errorf("no `.mptf.hcl` file found at %s", dir) } var blocks []*golden.HclBlock for _, filename := range matches { content, fsErr := afero.ReadFile(fs, filename) if fsErr != nil { err = multierror.Append(err, fsErr) continue } readFile, diag := hclsyntax.ParseConfig(content, filename, hcl.InitialPos) if diag.HasErrors() { err = multierror.Append(err, diag.Errs()...) continue } writeFile, _ := hclwrite.ParseConfig(content, filename, hcl.InitialPos) readBlocks := readFile.Body.(*hclsyntax.Body).Blocks writeBlocks := writeFile.Body().Blocks() blocks = append(blocks, golden.AsHclBlocks(readBlocks, writeBlocks)...) } if err != nil { return nil, err } var r []*golden.HclBlock for _, b := range blocks { if golden.IsBlockTypeWanted(b.Type) { r = append(r, b) continue } if !ignoreUnsupportedBlock { err = multierror.Append(err, fmt.Errorf("invalid block type: %s %s", b.Type, b.Range().String())) } } return r, err } func (c *MetaProgrammingTFConfig) SaveToDisk() error { return c.module.SaveToDisk() } func ModuleRefs(tfDir string) ([]*TerraformModuleRef, error) { moduleManifest := filepath.Join(tfDir, ".terraform", "modules", "modules.json") exist, err := afero.Exists(filesystem.Fs, moduleManifest) if err != nil { return nil, fmt.Errorf("cannot check `modules.json` at %s: %+v", moduleManifest, err) } if !exist { mod, err := NewTerraformRootModuleRef(tfDir) if err != nil { return nil, err } return []*TerraformModuleRef{mod}, nil } var modules = struct { Modules []*TerraformModuleRef `json:"Modules"` }{} manifestJson, err := afero.ReadFile(filesystem.Fs, moduleManifest) if err != nil { return nil, fmt.Errorf("cannot read `modules.json` at %s: %+v", moduleManifest, err) } if err = json.Unmarshal(manifestJson, &modules); err != nil { return nil, fmt.Errorf("cannot unmarshal `modules.json` at %s: %+v", moduleManifest, err) } for i, m := range modules.Modules { if err := m.Load(); err != nil { return nil, fmt.Errorf("cannot load info for %s: %+v", m.Dir, err) } modules.Modules[i] = m } return modules.Modules, nil } func (c *MetaProgrammingTFConfig) slice(blocks map[string]*terraform.RootBlock) []*terraform.RootBlock { var r []*terraform.RootBlock for _, b := range blocks { r = append(r, b) } return r } func (c *MetaProgrammingTFConfig) AddBlock(filename string, block *hclwrite.Block) { c.module.AddBlock(filename, block) } func groupByAddress(blocks []*terraform.RootBlock) map[string]*terraform.RootBlock { r := make(map[string]*terraform.RootBlock) for _, b := range blocks { r[b.Address] = b } return r }