config: `module` structures parse
This commit is contained in:
parent
2317f7ea9b
commit
2a6990e2b9
|
@ -15,6 +15,7 @@ import (
|
||||||
// Config is the configuration that comes from loading a collection
|
// Config is the configuration that comes from loading a collection
|
||||||
// of Terraform templates.
|
// of Terraform templates.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
Modules []*Module
|
||||||
ProviderConfigs []*ProviderConfig
|
ProviderConfigs []*ProviderConfig
|
||||||
Resources []*Resource
|
Resources []*Resource
|
||||||
Variables []*Variable
|
Variables []*Variable
|
||||||
|
@ -25,6 +26,17 @@ type Config struct {
|
||||||
unknownKeys []string
|
unknownKeys []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Module is a module used within a configuration.
|
||||||
|
//
|
||||||
|
// This does not represent a module itself, this represents a module
|
||||||
|
// call-site within an existing configuration.
|
||||||
|
type Module struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
Source string
|
||||||
|
RawConfig *RawConfig
|
||||||
|
}
|
||||||
|
|
||||||
// ProviderConfig is the configuration for a resource provider.
|
// ProviderConfig is the configuration for a resource provider.
|
||||||
//
|
//
|
||||||
// For example, Terraform needs to set the AWS access keys for the AWS
|
// For example, Terraform needs to set the AWS access keys for the AWS
|
||||||
|
@ -92,6 +104,11 @@ func ProviderConfigName(t string, pcs []*ProviderConfig) string {
|
||||||
return lk
|
return lk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A unique identifier for this module.
|
||||||
|
func (r *Module) Id() string {
|
||||||
|
return fmt.Sprintf("%s.%s", r.Type, r.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// A unique identifier for this resource.
|
// A unique identifier for this resource.
|
||||||
func (r *Resource) Id() string {
|
func (r *Resource) Id() string {
|
||||||
return fmt.Sprintf("%s.%s", r.Type, r.Name)
|
return fmt.Sprintf("%s.%s", r.Type, r.Name)
|
||||||
|
|
|
@ -17,6 +17,7 @@ type hclConfigurable struct {
|
||||||
|
|
||||||
func (t *hclConfigurable) Config() (*Config, error) {
|
func (t *hclConfigurable) Config() (*Config, error) {
|
||||||
validKeys := map[string]struct{}{
|
validKeys := map[string]struct{}{
|
||||||
|
"module": struct{}{},
|
||||||
"output": struct{}{},
|
"output": struct{}{},
|
||||||
"provider": struct{}{},
|
"provider": struct{}{},
|
||||||
"resource": struct{}{},
|
"resource": struct{}{},
|
||||||
|
@ -69,6 +70,15 @@ func (t *hclConfigurable) Config() (*Config, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the modules
|
||||||
|
if modules := t.Object.Get("module", false); modules != nil {
|
||||||
|
var err error
|
||||||
|
config.Modules, err = loadModulesHcl(modules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build the provider configs
|
// Build the provider configs
|
||||||
if providers := t.Object.Get("provider", false); providers != nil {
|
if providers := t.Object.Get("provider", false); providers != nil {
|
||||||
var err error
|
var err error
|
||||||
|
@ -177,6 +187,81 @@ func loadFileHcl(root string) (configurable, []string, error) {
|
||||||
return result, nil, nil
|
return result, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given a handle to a HCL object, this recurses into the structure
|
||||||
|
// and pulls out a list of modules.
|
||||||
|
//
|
||||||
|
// The resulting modules may not be unique, but each module
|
||||||
|
// represents exactly one module definition in the HCL configuration.
|
||||||
|
// We leave it up to another pass to merge them together.
|
||||||
|
func loadModulesHcl(os *hclobj.Object) ([]*Module, error) {
|
||||||
|
var allTypes []*hclobj.Object
|
||||||
|
|
||||||
|
// See loadResourcesHcl for why this exists. Don't touch this.
|
||||||
|
for _, o1 := range os.Elem(false) {
|
||||||
|
// Iterate the inner to get the list of types
|
||||||
|
for _, o2 := range o1.Elem(true) {
|
||||||
|
// Iterate all of this type to get _all_ the types
|
||||||
|
for _, o3 := range o2.Elem(false) {
|
||||||
|
allTypes = append(allTypes, o3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where all the results will go
|
||||||
|
var result []*Module
|
||||||
|
|
||||||
|
// Now go over all the types and their children in order to get
|
||||||
|
// all of the actual resources.
|
||||||
|
for _, t := range allTypes {
|
||||||
|
for _, obj := range t.Elem(true) {
|
||||||
|
k := obj.Key
|
||||||
|
|
||||||
|
var config map[string]interface{}
|
||||||
|
if err := hcl.DecodeObject(&config, obj); err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Error reading config for %s[%s]: %s",
|
||||||
|
t.Key,
|
||||||
|
k,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the fields we handle specially
|
||||||
|
delete(config, "source")
|
||||||
|
|
||||||
|
rawConfig, err := NewRawConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Error reading config for %s[%s]: %s",
|
||||||
|
t.Key,
|
||||||
|
k,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a count, then figure it out
|
||||||
|
var source string
|
||||||
|
if o := obj.Get("source", false); o != nil {
|
||||||
|
err = hcl.DecodeObject(&source, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Error parsing source for %s[%s]: %s",
|
||||||
|
t.Key,
|
||||||
|
k,
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, &Module{
|
||||||
|
Name: k,
|
||||||
|
Type: t.Key,
|
||||||
|
Source: source,
|
||||||
|
RawConfig: rawConfig,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadOutputsHcl recurses into the given HCL object and turns
|
// LoadOutputsHcl recurses into the given HCL object and turns
|
||||||
// it into a mapping of outputs.
|
// it into a mapping of outputs.
|
||||||
func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) {
|
func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) {
|
||||||
|
|
|
@ -106,6 +106,22 @@ func TestLoadBasic_json(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadBasic_modules(t *testing.T) {
|
||||||
|
c, err := Load(filepath.Join(fixtureDir, "modules.tf"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
t.Fatal("config should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := modulesStr(c.Modules)
|
||||||
|
if actual != strings.TrimSpace(modulesModulesStr) {
|
||||||
|
t.Fatalf("bad:\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoad_variables(t *testing.T) {
|
func TestLoad_variables(t *testing.T) {
|
||||||
c, err := Load(filepath.Join(fixtureDir, "variables.tf"))
|
c, err := Load(filepath.Join(fixtureDir, "variables.tf"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -302,6 +318,41 @@ func TestLoad_connections(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func modulesStr(ms []*Module) string {
|
||||||
|
result := ""
|
||||||
|
order := make([]int, 0, len(ms))
|
||||||
|
ks := make([]string, 0, len(ms))
|
||||||
|
mapping := make(map[string]int)
|
||||||
|
for i, m := range ms {
|
||||||
|
k := m.Id()
|
||||||
|
ks = append(ks, k)
|
||||||
|
mapping[k] = i
|
||||||
|
}
|
||||||
|
sort.Strings(ks)
|
||||||
|
for _, k := range ks {
|
||||||
|
order = append(order, mapping[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range order {
|
||||||
|
m := ms[i]
|
||||||
|
result += fmt.Sprintf("%s\n", m.Id())
|
||||||
|
|
||||||
|
ks := make([]string, 0, len(m.RawConfig.Raw))
|
||||||
|
for k, _ := range m.RawConfig.Raw {
|
||||||
|
ks = append(ks, k)
|
||||||
|
}
|
||||||
|
sort.Strings(ks)
|
||||||
|
|
||||||
|
result += fmt.Sprintf(" source = %s\n", m.Source)
|
||||||
|
|
||||||
|
for _, k := range ks {
|
||||||
|
result += fmt.Sprintf(" %s\n", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(result)
|
||||||
|
}
|
||||||
|
|
||||||
// This helper turns a provider configs field into a deterministic
|
// This helper turns a provider configs field into a deterministic
|
||||||
// string value for comparison in tests.
|
// string value for comparison in tests.
|
||||||
func providerConfigsStr(pcs []*ProviderConfig) string {
|
func providerConfigsStr(pcs []*ProviderConfig) string {
|
||||||
|
@ -611,6 +662,12 @@ foo
|
||||||
bar
|
bar
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const modulesModulesStr = `
|
||||||
|
foo.bar
|
||||||
|
source = baz
|
||||||
|
memory
|
||||||
|
`
|
||||||
|
|
||||||
const provisionerResourcesStr = `
|
const provisionerResourcesStr = `
|
||||||
aws_instance[web] (x1)
|
aws_instance[web] (x1)
|
||||||
ami
|
ami
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
module "foo" "bar" {
|
||||||
|
memory = "1G"
|
||||||
|
source = "baz"
|
||||||
|
}
|
Loading…
Reference in New Issue