config: `module` structures parse

This commit is contained in:
Mitchell Hashimoto 2014-09-11 15:58:30 -07:00
parent 2317f7ea9b
commit 2a6990e2b9
4 changed files with 163 additions and 0 deletions

View File

@ -15,6 +15,7 @@ import (
// Config is the configuration that comes from loading a collection
// of Terraform templates.
type Config struct {
Modules []*Module
ProviderConfigs []*ProviderConfig
Resources []*Resource
Variables []*Variable
@ -25,6 +26,17 @@ type Config struct {
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.
//
// 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
}
// 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.
func (r *Resource) Id() string {
return fmt.Sprintf("%s.%s", r.Type, r.Name)

View File

@ -17,6 +17,7 @@ type hclConfigurable struct {
func (t *hclConfigurable) Config() (*Config, error) {
validKeys := map[string]struct{}{
"module": struct{}{},
"output": struct{}{},
"provider": 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
if providers := t.Object.Get("provider", false); providers != nil {
var err error
@ -177,6 +187,81 @@ func loadFileHcl(root string) (configurable, []string, error) {
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
// it into a mapping of outputs.
func loadOutputsHcl(os *hclobj.Object) ([]*Output, error) {

View File

@ -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) {
c, err := Load(filepath.Join(fixtureDir, "variables.tf"))
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
// string value for comparison in tests.
func providerConfigsStr(pcs []*ProviderConfig) string {
@ -611,6 +662,12 @@ foo
bar
`
const modulesModulesStr = `
foo.bar
source = baz
memory
`
const provisionerResourcesStr = `
aws_instance[web] (x1)
ami

View File

@ -0,0 +1,4 @@
module "foo" "bar" {
memory = "1G"
source = "baz"
}