pkg/terraform/module.go (185 lines of code) (raw):
package terraform
import (
"path/filepath"
"strings"
"sync"
"github.com/Azure/mapotf/pkg/backup"
"github.com/Azure/mapotf/pkg/fs"
"github.com/ahmetb/go-linq/v3"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/hcl/v2/hclwrite"
"github.com/spf13/afero"
)
var wantedTypes = map[string]func(module *Module) *[]*RootBlock{
"resource": func(m *Module) *[]*RootBlock {
return &m.ResourceBlocks
},
"data": func(m *Module) *[]*RootBlock {
return &m.DataBlocks
},
"module": func(m *Module) *[]*RootBlock {
return &m.ModuleBlocks
},
"terraform": func(m *Module) *[]*RootBlock {
return &m.TerraformBlocks
},
"variable": func(m *Module) *[]*RootBlock { return &m.Variables },
"output": func(m *Module) *[]*RootBlock { return &m.Outputs },
}
type Module struct {
Dir string
AbsDir string
writeFiles map[string]*hclwrite.File
lock *sync.Mutex
ResourceBlocks []*RootBlock
DataBlocks []*RootBlock
ModuleBlocks []*RootBlock
TerraformBlocks []*RootBlock
Variables []*RootBlock
Outputs []*RootBlock
Locals []*RootBlock
Key string
Source string
Version string
GitHash string
}
func (m *Module) loadConfig(cfg, filename string) error {
writeFile, diag := hclwrite.ParseConfig([]byte(cfg), filename, hcl.InitialPos)
if diag.HasErrors() {
return diag
}
readFile, diag := hclsyntax.ParseConfig([]byte(cfg), filename, hcl.InitialPos)
if diag.HasErrors() {
return diag
}
m.writeFiles[filename] = writeFile
readBlocks := readFile.Body.(*hclsyntax.Body).Blocks
writeBlocks := writeFile.Body().Blocks()
for i, rb := range readBlocks {
if rb.Type == "locals" {
m.loadLocals(rb, writeBlocks[i])
continue
}
getter, want := wantedTypes[rb.Type]
if !want {
continue
}
hclBlock := NewBlock(m, rb, writeBlocks[i])
blocks := getter(m)
*blocks = append(*blocks, hclBlock)
}
return nil
}
type ModuleRef struct {
Key string `json:"Key"`
Source string `json:"Source"`
Dir string `json:"Dir"`
AbsDir string
Version string `json:"Version"`
GitHash string
}
func LoadModule(mr ModuleRef) (*Module, error) {
files, err := afero.ReadDir(fs.Fs, mr.AbsDir)
if err != nil {
return nil, err
}
m := &Module{
Dir: mr.Dir,
AbsDir: mr.AbsDir,
writeFiles: make(map[string]*hclwrite.File),
lock: &sync.Mutex{},
Key: mr.Key,
Source: mr.Source,
Version: mr.Version,
GitHash: mr.GitHash,
}
for _, f := range files {
if f.IsDir() {
continue
}
if !strings.HasSuffix(f.Name(), ".tf") || f.Name() == "override.tf" || strings.HasSuffix(f.Name(), "_override.tf") {
continue
}
n := filepath.Join(mr.AbsDir, f.Name())
content, err := afero.ReadFile(fs.Fs, n)
if err != nil {
return nil, err
}
if err = m.loadConfig(string(content), f.Name()); err != nil {
return nil, err
}
}
return m, nil
}
func (m *Module) SaveToDisk() error {
m.lock.Lock()
defer m.lock.Unlock()
for fn, wf := range m.writeFiles {
absPath := filepath.Join(m.Dir, fn)
exist, err := afero.Exists(fs.Fs, absPath)
if err != nil {
return err
}
if !exist {
absNewFilePath := absPath + backup.NewFileExtension
err = afero.WriteFile(fs.Fs, absNewFilePath, []byte{}, 0644)
if err != nil {
return err
}
}
content := wf.Bytes()
err = afero.WriteFile(fs.Fs, absPath, hclwrite.Format(content), 0644)
if err != nil {
return err
}
}
return nil
}
func (m *Module) AddBlock(fileName string, block *hclwrite.Block) {
func() {
m.lock.Lock()
defer m.lock.Unlock()
if _, ok := m.writeFiles[fileName]; !ok {
m.writeFiles[fileName] = hclwrite.NewFile()
}
}()
writeFile := m.writeFiles[fileName]
lock.Lock(fileName)
defer lock.Unlock(fileName)
tokens := writeFile.Body().BuildTokens(nil)
if len(tokens) > 1 && tokens[len(tokens)-1].Type != hclsyntax.TokenNewline {
writeFile.Body().AppendNewline()
}
writeFile.Body().AppendBlock(block)
writeFile.Body().AppendNewline()
}
func (m *Module) loadLocals(rb *hclsyntax.Block, wb *hclwrite.Block) {
for attrName, attr := range rb.Body.Attributes {
rootBlock := NewBlock(m, &hclsyntax.Block{
Type: "locals",
Labels: []string{},
Body: &hclsyntax.Body{
Attributes: map[string]*hclsyntax.Attribute{
attrName: attr,
},
Blocks: []*hclsyntax.Block{},
SrcRange: rb.TypeRange,
EndRange: rb.CloseBraceRange,
},
TypeRange: rb.TypeRange,
LabelRanges: rb.LabelRanges,
OpenBraceRange: rb.OpenBraceRange,
CloseBraceRange: rb.CloseBraceRange,
}, wb)
m.Locals = append(m.Locals, rootBlock)
}
}
func (m *Module) Blocks() []*RootBlock {
var blocks []*RootBlock
linq.From(m.TerraformBlocks).Concat(linq.From(m.Locals)).
Concat(linq.From(m.Outputs)).Concat(linq.From(m.Variables)).
Concat(linq.From(m.DataBlocks)).Concat(linq.From(m.ResourceBlocks)).
Concat(linq.From(m.ModuleBlocks)).ToSlice(&blocks)
return blocks
}