github.com/Yunsang-Jeong/terraforge@v0.0.0-20231003081416-fe4fad2c57e3/internal/configs/paser.go (about) 1 package configs 2 3 import ( 4 "errors" 5 "path/filepath" 6 7 "github.com/Yunsang-Jeong/terraforge/internal/logger" 8 "github.com/Yunsang-Jeong/terraforge/internal/utils" 9 "github.com/hashicorp/hcl/v2" 10 "github.com/hashicorp/hcl/v2/hclparse" 11 "github.com/hashicorp/hcl/v2/hclsyntax" 12 tflang "github.com/hashicorp/terraform/lang" 13 "github.com/spf13/afero" 14 "github.com/zclconf/go-cty/cty" 15 "github.com/zclconf/go-cty/cty/function" 16 ) 17 18 type Parser struct { 19 lg logger.Logger 20 fs afero.Afero 21 p *hclparse.Parser 22 wd string 23 } 24 25 func NewParser(lg logger.Logger, workingDir string, fs afero.Afero) *Parser { 26 return &Parser{ 27 lg: lg, 28 fs: fs, 29 p: hclparse.NewParser(), 30 wd: workingDir, 31 } 32 } 33 34 func (p *Parser) LoadConfigFile(configFile string) (*Config, error) { 35 body, err := p.loadHCLFile(configFile) 36 if err != nil { 37 p.lg.Error("fail to load config file") 38 return nil, nil 39 } 40 41 blocks := map[string][]*hclsyntax.Block{} 42 for _, block := range body.Blocks { 43 blocks[block.Type] = append(blocks[block.Type], block) 44 } 45 46 config := &Config{ 47 Ctx: &hcl.EvalContext{ 48 Variables: map[string]cty.Value{}, 49 Functions: map[string]function.Function{}, 50 }, 51 } 52 53 tfscope := tflang.Scope{ 54 BaseDir: filepath.Dir("."), 55 } 56 for k, v := range tfscope.Functions() { 57 config.Ctx.Functions[k] = v 58 } 59 60 metadata := map[string]cty.Value{} 61 62 if len(blocks["metafile"]) > 1 { 63 p.lg.Error("no more than one metadata block can exist") 64 return nil, errors.New("too many metafile block") 65 } 66 67 for _, block := range blocks["metafile"] { 68 metafileBlock, err := decodeMetafileBlock(block, nil) 69 if err != nil { 70 p.lg.Error("fail to decode metafile block") 71 return nil, err 72 } 73 74 for _, metafile := range metafileBlock.Paths { 75 metafileBody, err := p.loadHCLFile(metafile) 76 if err != nil { 77 p.lg.Error("fail to load metafile") 78 return nil, err 79 } 80 81 for name, attr := range metafileBody.Attributes { 82 value, diags := attr.Expr.Value(nil) 83 if diags.HasErrors() { 84 return nil, errors.Join(diags.Errs()...) 85 } 86 87 metadata[name] = value 88 } 89 } 90 } 91 config.Ctx.Variables["metadata"] = cty.ObjectVal(metadata) 92 93 config.Generates = map[string][]*Generate{} 94 95 for _, block := range blocks["generate"] { 96 generateBlock, err := decodeGenerateBlock(block, config.Ctx) 97 if err != nil { 98 p.lg.Error("fail to decode generate block") 99 return nil, err 100 } 101 102 config.Generates[block.Labels[0]] = append(config.Generates[block.Labels[0]], generateBlock) 103 } 104 105 return config, nil 106 } 107 108 func (p *Parser) loadHCLFile(filename string) (*hclsyntax.Body, error) { 109 path, err := utils.GetSomethingPathInParents(p.wd, filename, true) 110 if err != nil { 111 p.lg.Error("failed to get hcl file path", "err", err, "filename", filename, "wd", p.wd) 112 return nil, err 113 } 114 115 src, err := p.fs.ReadFile(filepath.Join(p.wd, path)) 116 if err != nil { 117 p.lg.Error("failed to get read hcl path", "err", err, "filename", filename) 118 return nil, err 119 } 120 121 file, diag := p.p.ParseHCL(src, filename) 122 if file == nil || file.Body == nil || diag.HasErrors() { 123 p.lg.Error("fail to load config file") 124 return hcl.EmptyBody().(*hclsyntax.Body), errors.Join(diag.Errs()...) 125 } 126 127 return file.Body.(*hclsyntax.Body), nil 128 }