mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-09-19 09:39:59 +00:00
dbf9188792
```bash GO111MODULE=on go get github.com/hashicorp/terraform@pluginsdk-v0.12-early7 GO111MODULE=on go mod vendor GO111MODULE=on go mod tidy ```
326 lines
8.9 KiB
Go
326 lines
8.9 KiB
Go
package tfconfig
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"strings"
|
|
|
|
legacyhcl "github.com/hashicorp/hcl"
|
|
legacyast "github.com/hashicorp/hcl/hcl/ast"
|
|
)
|
|
|
|
func loadModuleLegacyHCL(dir string) (*Module, Diagnostics) {
|
|
// This implementation is intentionally more quick-and-dirty than the
|
|
// main loader. In particular, it doesn't bother to keep careful track
|
|
// of multiple error messages because we always fall back on returning
|
|
// the main parser's error message if our fallback parsing produces
|
|
// an error, and thus the errors here are not seen by the end-caller.
|
|
mod := newModule(dir)
|
|
|
|
primaryPaths, diags := dirFiles(dir)
|
|
if diags.HasErrors() {
|
|
return mod, diagnosticsHCL(diags)
|
|
}
|
|
|
|
for _, filename := range primaryPaths {
|
|
src, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return mod, diagnosticsErrorf("Error reading %s: %s", filename, err)
|
|
}
|
|
|
|
hclRoot, err := legacyhcl.Parse(string(src))
|
|
if err != nil {
|
|
return mod, diagnosticsErrorf("Error parsing %s: %s", filename, err)
|
|
}
|
|
|
|
list, ok := hclRoot.Node.(*legacyast.ObjectList)
|
|
if !ok {
|
|
return mod, diagnosticsErrorf("Error parsing %s: no root object", filename)
|
|
}
|
|
|
|
for _, item := range list.Filter("terraform").Items {
|
|
if len(item.Keys) > 0 {
|
|
item = &legacyast.ObjectItem{
|
|
Val: &legacyast.ObjectType{
|
|
List: &legacyast.ObjectList{
|
|
Items: []*legacyast.ObjectItem{item},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
type TerraformBlock struct {
|
|
RequiredVersion string `hcl:"required_version"`
|
|
}
|
|
var block TerraformBlock
|
|
err = legacyhcl.DecodeObject(&block, item.Val)
|
|
if err != nil {
|
|
return nil, diagnosticsErrorf("terraform block: %s", err)
|
|
}
|
|
|
|
if block.RequiredVersion != "" {
|
|
mod.RequiredCore = append(mod.RequiredCore, block.RequiredVersion)
|
|
}
|
|
}
|
|
|
|
if vars := list.Filter("variable"); len(vars.Items) > 0 {
|
|
vars = vars.Children()
|
|
type VariableBlock struct {
|
|
Type string `hcl:"type"`
|
|
Default interface{}
|
|
Description string
|
|
Fields []string `hcl:",decodedFields"`
|
|
}
|
|
|
|
for _, item := range vars.Items {
|
|
unwrapLegacyHCLObjectKeysFromJSON(item, 1)
|
|
|
|
if len(item.Keys) != 1 {
|
|
return nil, diagnosticsErrorf("variable block at %s has no label", item.Pos())
|
|
}
|
|
|
|
name := item.Keys[0].Token.Value().(string)
|
|
|
|
var block VariableBlock
|
|
err := legacyhcl.DecodeObject(&block, item.Val)
|
|
if err != nil {
|
|
return nil, diagnosticsErrorf("invalid variable block at %s: %s", item.Pos(), err)
|
|
}
|
|
|
|
// Clean up legacy HCL decoding ambiguity by unwrapping list of maps
|
|
if ms, ok := block.Default.([]map[string]interface{}); ok {
|
|
def := make(map[string]interface{})
|
|
for _, m := range ms {
|
|
for k, v := range m {
|
|
def[k] = v
|
|
}
|
|
}
|
|
block.Default = def
|
|
}
|
|
|
|
v := &Variable{
|
|
Name: name,
|
|
Type: block.Type,
|
|
Description: block.Description,
|
|
Default: block.Default,
|
|
Pos: sourcePosLegacyHCL(item.Pos(), filename),
|
|
}
|
|
if _, exists := mod.Variables[name]; exists {
|
|
return nil, diagnosticsErrorf("duplicate variable block for %q", name)
|
|
}
|
|
mod.Variables[name] = v
|
|
|
|
}
|
|
}
|
|
|
|
if outputs := list.Filter("output"); len(outputs.Items) > 0 {
|
|
outputs = outputs.Children()
|
|
type OutputBlock struct {
|
|
Description string
|
|
}
|
|
|
|
for _, item := range outputs.Items {
|
|
unwrapLegacyHCLObjectKeysFromJSON(item, 1)
|
|
|
|
if len(item.Keys) != 1 {
|
|
return nil, diagnosticsErrorf("output block at %s has no label", item.Pos())
|
|
}
|
|
|
|
name := item.Keys[0].Token.Value().(string)
|
|
|
|
var block OutputBlock
|
|
err := legacyhcl.DecodeObject(&block, item.Val)
|
|
if err != nil {
|
|
return nil, diagnosticsErrorf("invalid output block at %s: %s", item.Pos(), err)
|
|
}
|
|
|
|
o := &Output{
|
|
Name: name,
|
|
Description: block.Description,
|
|
Pos: sourcePosLegacyHCL(item.Pos(), filename),
|
|
}
|
|
if _, exists := mod.Outputs[name]; exists {
|
|
return nil, diagnosticsErrorf("duplicate output block for %q", name)
|
|
}
|
|
mod.Outputs[name] = o
|
|
}
|
|
}
|
|
|
|
for _, blockType := range []string{"resource", "data"} {
|
|
if resources := list.Filter(blockType); len(resources.Items) > 0 {
|
|
resources = resources.Children()
|
|
type ResourceBlock struct {
|
|
Provider string
|
|
}
|
|
|
|
for _, item := range resources.Items {
|
|
unwrapLegacyHCLObjectKeysFromJSON(item, 2)
|
|
|
|
if len(item.Keys) != 2 {
|
|
return nil, diagnosticsErrorf("resource block at %s has wrong label count", item.Pos())
|
|
}
|
|
|
|
typeName := item.Keys[0].Token.Value().(string)
|
|
name := item.Keys[1].Token.Value().(string)
|
|
var mode ResourceMode
|
|
var rMap map[string]*Resource
|
|
switch blockType {
|
|
case "resource":
|
|
mode = ManagedResourceMode
|
|
rMap = mod.ManagedResources
|
|
case "data":
|
|
mode = DataResourceMode
|
|
rMap = mod.DataResources
|
|
}
|
|
|
|
var block ResourceBlock
|
|
err := legacyhcl.DecodeObject(&block, item.Val)
|
|
if err != nil {
|
|
return nil, diagnosticsErrorf("invalid resource block at %s: %s", item.Pos(), err)
|
|
}
|
|
|
|
var providerName, providerAlias string
|
|
if dotPos := strings.IndexByte(block.Provider, '.'); dotPos != -1 {
|
|
providerName = block.Provider[:dotPos]
|
|
providerAlias = block.Provider[dotPos+1:]
|
|
} else {
|
|
providerName = block.Provider
|
|
}
|
|
if providerName == "" {
|
|
providerName = resourceTypeDefaultProviderName(typeName)
|
|
}
|
|
|
|
r := &Resource{
|
|
Mode: mode,
|
|
Type: typeName,
|
|
Name: name,
|
|
Provider: ProviderRef{
|
|
Name: providerName,
|
|
Alias: providerAlias,
|
|
},
|
|
Pos: sourcePosLegacyHCL(item.Pos(), filename),
|
|
}
|
|
key := r.MapKey()
|
|
if _, exists := rMap[key]; exists {
|
|
return nil, diagnosticsErrorf("duplicate resource block for %q", key)
|
|
}
|
|
rMap[key] = r
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if moduleCalls := list.Filter("module"); len(moduleCalls.Items) > 0 {
|
|
moduleCalls = moduleCalls.Children()
|
|
type ModuleBlock struct {
|
|
Source string
|
|
Version string
|
|
}
|
|
|
|
for _, item := range moduleCalls.Items {
|
|
unwrapLegacyHCLObjectKeysFromJSON(item, 1)
|
|
|
|
if len(item.Keys) != 1 {
|
|
return nil, diagnosticsErrorf("module block at %s has no label", item.Pos())
|
|
}
|
|
|
|
name := item.Keys[0].Token.Value().(string)
|
|
|
|
var block ModuleBlock
|
|
err := legacyhcl.DecodeObject(&block, item.Val)
|
|
if err != nil {
|
|
return nil, diagnosticsErrorf("module block at %s: %s", item.Pos(), err)
|
|
}
|
|
|
|
mc := &ModuleCall{
|
|
Name: name,
|
|
Source: block.Source,
|
|
Version: block.Version,
|
|
Pos: sourcePosLegacyHCL(item.Pos(), filename),
|
|
}
|
|
// it's possible this module call is from an override file
|
|
if origMod, exists := mod.ModuleCalls[name]; exists {
|
|
if mc.Source == "" {
|
|
mc.Source = origMod.Source
|
|
}
|
|
}
|
|
mod.ModuleCalls[name] = mc
|
|
}
|
|
}
|
|
|
|
if providerConfigs := list.Filter("provider"); len(providerConfigs.Items) > 0 {
|
|
providerConfigs = providerConfigs.Children()
|
|
type ProviderBlock struct {
|
|
Version string
|
|
}
|
|
|
|
for _, item := range providerConfigs.Items {
|
|
unwrapLegacyHCLObjectKeysFromJSON(item, 1)
|
|
|
|
if len(item.Keys) != 1 {
|
|
return nil, diagnosticsErrorf("provider block at %s has no label", item.Pos())
|
|
}
|
|
|
|
name := item.Keys[0].Token.Value().(string)
|
|
|
|
var block ProviderBlock
|
|
err := legacyhcl.DecodeObject(&block, item.Val)
|
|
if err != nil {
|
|
return nil, diagnosticsErrorf("invalid provider block at %s: %s", item.Pos(), err)
|
|
}
|
|
|
|
if block.Version != "" {
|
|
mod.RequiredProviders[name] = append(mod.RequiredProviders[name], block.Version)
|
|
}
|
|
|
|
// Even if there wasn't an explicit version required, we still
|
|
// need an entry in our map to signal the unversioned dependency.
|
|
if _, exists := mod.RequiredProviders[name]; !exists {
|
|
mod.RequiredProviders[name] = []string{}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return mod, nil
|
|
}
|
|
|
|
// unwrapLegacyHCLObjectKeysFromJSON cleans up an edge case that can occur when
|
|
// parsing JSON as input: if we're parsing JSON then directly nested
|
|
// items will show up as additional "keys".
|
|
//
|
|
// For objects that expect a fixed number of keys, this breaks the
|
|
// decoding process. This function unwraps the object into what it would've
|
|
// looked like if it came directly from HCL by specifying the number of keys
|
|
// you expect.
|
|
//
|
|
// Example:
|
|
//
|
|
// { "foo": { "baz": {} } }
|
|
//
|
|
// Will show up with Keys being: []string{"foo", "baz"}
|
|
// when we really just want the first two. This function will fix this.
|
|
func unwrapLegacyHCLObjectKeysFromJSON(item *legacyast.ObjectItem, depth int) {
|
|
if len(item.Keys) > depth && item.Keys[0].Token.JSON {
|
|
for len(item.Keys) > depth {
|
|
// Pop off the last key
|
|
n := len(item.Keys)
|
|
key := item.Keys[n-1]
|
|
item.Keys[n-1] = nil
|
|
item.Keys = item.Keys[:n-1]
|
|
|
|
// Wrap our value in a list
|
|
item.Val = &legacyast.ObjectType{
|
|
List: &legacyast.ObjectList{
|
|
Items: []*legacyast.ObjectItem{
|
|
&legacyast.ObjectItem{
|
|
Keys: []*legacyast.ObjectKey{key},
|
|
Val: item.Val,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|