diff --git a/config/config_string.go b/config/config_string.go new file mode 100644 index 000000000..89a990ae8 --- /dev/null +++ b/config/config_string.go @@ -0,0 +1,297 @@ +package config + +import ( + "bytes" + "fmt" + "sort" + "strings" +) + +// TestString is a Stringer-like function that outputs a string that can +// be used to easily compare multiple Config structures in unit tests. +// +// This function has no practical use outside of unit tests and debugging. +func (c *Config) TestString() string { + if c == nil { + return "" + } + + var buf bytes.Buffer + if len(c.Modules) > 0 { + buf.WriteString("Modules:\n\n") + buf.WriteString(modulesStr(c.Modules)) + buf.WriteString("\n\n") + } + + if len(c.Variables) > 0 { + buf.WriteString("Variables:\n\n") + buf.WriteString(variablesStr(c.Variables)) + buf.WriteString("\n\n") + } + + if len(c.ProviderConfigs) > 0 { + buf.WriteString("Provider Configs:\n\n") + buf.WriteString(providerConfigsStr(c.ProviderConfigs)) + buf.WriteString("\n\n") + } + + if len(c.Resources) > 0 { + buf.WriteString("Resources:\n\n") + buf.WriteString(resourcesStr(c.Resources)) + buf.WriteString("\n\n") + } + + if len(c.Outputs) > 0 { + buf.WriteString("Outputs:\n\n") + buf.WriteString(outputsStr(c.Outputs)) + buf.WriteString("\n") + } + + return strings.TrimSpace(buf.String()) +} + +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) +} + +func outputsStr(os []*Output) string { + ns := make([]string, 0, len(os)) + m := make(map[string]*Output) + for _, o := range os { + ns = append(ns, o.Name) + m[o.Name] = o + } + sort.Strings(ns) + + result := "" + for _, n := range ns { + o := m[n] + + result += fmt.Sprintf("%s\n", n) + + if len(o.RawConfig.Variables) > 0 { + result += fmt.Sprintf(" vars\n") + for _, rawV := range o.RawConfig.Variables { + kind := "unknown" + str := rawV.FullKey() + + switch rawV.(type) { + case *ResourceVariable: + kind = "resource" + case *UserVariable: + kind = "user" + } + + result += fmt.Sprintf(" %s: %s\n", kind, str) + } + } + } + + 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 { + result := "" + + ns := make([]string, 0, len(pcs)) + m := make(map[string]*ProviderConfig) + for _, n := range pcs { + ns = append(ns, n.Name) + m[n.Name] = n + } + sort.Strings(ns) + + for _, n := range ns { + pc := m[n] + + result += fmt.Sprintf("%s\n", n) + + keys := make([]string, 0, len(pc.RawConfig.Raw)) + for k, _ := range pc.RawConfig.Raw { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + result += fmt.Sprintf(" %s\n", k) + } + + if len(pc.RawConfig.Variables) > 0 { + result += fmt.Sprintf(" vars\n") + for _, rawV := range pc.RawConfig.Variables { + kind := "unknown" + str := rawV.FullKey() + + switch rawV.(type) { + case *ResourceVariable: + kind = "resource" + case *UserVariable: + kind = "user" + } + + result += fmt.Sprintf(" %s: %s\n", kind, str) + } + } + } + + return strings.TrimSpace(result) +} + +// This helper turns a resources field into a deterministic +// string value for comparison in tests. +func resourcesStr(rs []*Resource) string { + result := "" + order := make([]int, 0, len(rs)) + ks := make([]string, 0, len(rs)) + mapping := make(map[string]int) + for i, r := range rs { + k := fmt.Sprintf("%s[%s]", r.Type, r.Name) + ks = append(ks, k) + mapping[k] = i + } + sort.Strings(ks) + for _, k := range ks { + order = append(order, mapping[k]) + } + + for _, i := range order { + r := rs[i] + result += fmt.Sprintf( + "%s[%s] (x%d)\n", + r.Type, + r.Name, + r.Count) + + ks := make([]string, 0, len(r.RawConfig.Raw)) + for k, _ := range r.RawConfig.Raw { + ks = append(ks, k) + } + sort.Strings(ks) + + for _, k := range ks { + result += fmt.Sprintf(" %s\n", k) + } + + if len(r.Provisioners) > 0 { + result += fmt.Sprintf(" provisioners\n") + for _, p := range r.Provisioners { + result += fmt.Sprintf(" %s\n", p.Type) + + ks := make([]string, 0, len(p.RawConfig.Raw)) + for k, _ := range p.RawConfig.Raw { + ks = append(ks, k) + } + sort.Strings(ks) + + for _, k := range ks { + result += fmt.Sprintf(" %s\n", k) + } + } + } + + if len(r.DependsOn) > 0 { + result += fmt.Sprintf(" dependsOn\n") + for _, d := range r.DependsOn { + result += fmt.Sprintf(" %s\n", d) + } + } + + if len(r.RawConfig.Variables) > 0 { + result += fmt.Sprintf(" vars\n") + + ks := make([]string, 0, len(r.RawConfig.Variables)) + for k, _ := range r.RawConfig.Variables { + ks = append(ks, k) + } + sort.Strings(ks) + + for _, k := range ks { + rawV := r.RawConfig.Variables[k] + kind := "unknown" + str := rawV.FullKey() + + switch rawV.(type) { + case *ResourceVariable: + kind = "resource" + case *UserVariable: + kind = "user" + } + + result += fmt.Sprintf(" %s: %s\n", kind, str) + } + } + } + + return strings.TrimSpace(result) +} + +// This helper turns a variables field into a deterministic +// string value for comparison in tests. +func variablesStr(vs []*Variable) string { + result := "" + ks := make([]string, 0, len(vs)) + m := make(map[string]*Variable) + for _, v := range vs { + ks = append(ks, v.Name) + m[v.Name] = v + } + sort.Strings(ks) + + for _, k := range ks { + v := m[k] + + required := "" + if v.Required() { + required = " (required)" + } + + if v.Default == nil || v.Default == "" { + v.Default = "<>" + } + if v.Description == "" { + v.Description = "<>" + } + + result += fmt.Sprintf( + "%s%s\n %v\n %s\n", + k, + required, + v.Default, + v.Description) + } + + return strings.TrimSpace(result) +} diff --git a/config/loader_test.go b/config/loader_test.go index b92307a70..f95235d66 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -1,9 +1,7 @@ package config import ( - "fmt" "path/filepath" - "sort" "strings" "testing" ) @@ -264,42 +262,6 @@ func TestLoadDir_override(t *testing.T) { } } -func outputsStr(os []*Output) string { - ns := make([]string, 0, len(os)) - m := make(map[string]*Output) - for _, o := range os { - ns = append(ns, o.Name) - m[o.Name] = o - } - sort.Strings(ns) - - result := "" - for _, n := range ns { - o := m[n] - - result += fmt.Sprintf("%s\n", n) - - if len(o.RawConfig.Variables) > 0 { - result += fmt.Sprintf(" vars\n") - for _, rawV := range o.RawConfig.Variables { - kind := "unknown" - str := rawV.FullKey() - - switch rawV.(type) { - case *ResourceVariable: - kind = "resource" - case *UserVariable: - kind = "user" - } - - result += fmt.Sprintf(" %s: %s\n", kind, str) - } - } - } - - return strings.TrimSpace(result) -} - func TestLoad_provisioners(t *testing.T) { c, err := Load(filepath.Join(fixtureDir, "provisioners.tf")) if err != nil { @@ -354,216 +316,6 @@ 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 { - result := "" - - ns := make([]string, 0, len(pcs)) - m := make(map[string]*ProviderConfig) - for _, n := range pcs { - ns = append(ns, n.Name) - m[n.Name] = n - } - sort.Strings(ns) - - for _, n := range ns { - pc := m[n] - - result += fmt.Sprintf("%s\n", n) - - keys := make([]string, 0, len(pc.RawConfig.Raw)) - for k, _ := range pc.RawConfig.Raw { - keys = append(keys, k) - } - sort.Strings(keys) - - for _, k := range keys { - result += fmt.Sprintf(" %s\n", k) - } - - if len(pc.RawConfig.Variables) > 0 { - result += fmt.Sprintf(" vars\n") - for _, rawV := range pc.RawConfig.Variables { - kind := "unknown" - str := rawV.FullKey() - - switch rawV.(type) { - case *ResourceVariable: - kind = "resource" - case *UserVariable: - kind = "user" - } - - result += fmt.Sprintf(" %s: %s\n", kind, str) - } - } - } - - return strings.TrimSpace(result) -} - -// This helper turns a resources field into a deterministic -// string value for comparison in tests. -func resourcesStr(rs []*Resource) string { - result := "" - order := make([]int, 0, len(rs)) - ks := make([]string, 0, len(rs)) - mapping := make(map[string]int) - for i, r := range rs { - k := fmt.Sprintf("%s[%s]", r.Type, r.Name) - ks = append(ks, k) - mapping[k] = i - } - sort.Strings(ks) - for _, k := range ks { - order = append(order, mapping[k]) - } - - for _, i := range order { - r := rs[i] - result += fmt.Sprintf( - "%s[%s] (x%d)\n", - r.Type, - r.Name, - r.Count) - - ks := make([]string, 0, len(r.RawConfig.Raw)) - for k, _ := range r.RawConfig.Raw { - ks = append(ks, k) - } - sort.Strings(ks) - - for _, k := range ks { - result += fmt.Sprintf(" %s\n", k) - } - - if len(r.Provisioners) > 0 { - result += fmt.Sprintf(" provisioners\n") - for _, p := range r.Provisioners { - result += fmt.Sprintf(" %s\n", p.Type) - - ks := make([]string, 0, len(p.RawConfig.Raw)) - for k, _ := range p.RawConfig.Raw { - ks = append(ks, k) - } - sort.Strings(ks) - - for _, k := range ks { - result += fmt.Sprintf(" %s\n", k) - } - } - } - - if len(r.DependsOn) > 0 { - result += fmt.Sprintf(" dependsOn\n") - for _, d := range r.DependsOn { - result += fmt.Sprintf(" %s\n", d) - } - } - - if len(r.RawConfig.Variables) > 0 { - result += fmt.Sprintf(" vars\n") - - ks := make([]string, 0, len(r.RawConfig.Variables)) - for k, _ := range r.RawConfig.Variables { - ks = append(ks, k) - } - sort.Strings(ks) - - for _, k := range ks { - rawV := r.RawConfig.Variables[k] - kind := "unknown" - str := rawV.FullKey() - - switch rawV.(type) { - case *ResourceVariable: - kind = "resource" - case *UserVariable: - kind = "user" - } - - result += fmt.Sprintf(" %s: %s\n", kind, str) - } - } - } - - return strings.TrimSpace(result) -} - -// This helper turns a variables field into a deterministic -// string value for comparison in tests. -func variablesStr(vs []*Variable) string { - result := "" - ks := make([]string, 0, len(vs)) - m := make(map[string]*Variable) - for _, v := range vs { - ks = append(ks, v.Name) - m[v.Name] = v - } - sort.Strings(ks) - - for _, k := range ks { - v := m[k] - - required := "" - if v.Required() { - required = " (required)" - } - - if v.Default == nil || v.Default == "" { - v.Default = "<>" - } - if v.Description == "" { - v.Description = "<>" - } - - result += fmt.Sprintf( - "%s%s\n %v\n %s\n", - k, - required, - v.Default, - v.Description) - } - - return strings.TrimSpace(result) -} - const basicOutputsStr = ` web_ip vars diff --git a/config/module/tree.go b/config/module/tree.go index ed1b2b3de..f37dba649 100644 --- a/config/module/tree.go +++ b/config/module/tree.go @@ -56,17 +56,6 @@ func (t *Tree) Children() map[string]*Tree { return t.children } -// Flatten takes the entire module tree and flattens it into a single -// namespace in *config.Config with no module imports. -// -// Validate is called here implicitly, since it is important that semantic -// checks pass before flattening the configuration. Otherwise, encapsulation -// breaks in horrible ways and the errors that come out the other side -// will be surprising. -func (t *Tree) Flatten() (*config.Config, error) { - return nil, nil -} - // Loaded says whether or not this tree has been loaded or not yet. func (t *Tree) Loaded() bool { t.lock.RLock() @@ -212,7 +201,7 @@ func (t *Tree) Validate() error { } // If something goes wrong, here is our error template - newErr := &ValidateError{Name: []string{t.Name()}} + newErr := &TreeError{Name: []string{t.Name()}} // Validate our configuration first. if err := t.config.Validate(); err != nil { @@ -230,7 +219,7 @@ func (t *Tree) Validate() error { continue } - verr, ok := err.(*ValidateError) + verr, ok := err.(*TreeError) if !ok { // Unknown error, just return... return err @@ -301,14 +290,14 @@ func (t *Tree) Validate() error { return nil } -// ValidateError is an error returned by Tree.Validate if an error occurs +// TreeError is an error returned by Tree.Validate if an error occurs // with validation. -type ValidateError struct { +type TreeError struct { Name []string Err error } -func (e *ValidateError) Error() string { +func (e *TreeError) Error() string { // Build up the name var buf bytes.Buffer for _, n := range e.Name {