terraform: outputs are computed for the state

This commit is contained in:
Mitchell Hashimoto 2014-07-04 15:36:28 -07:00
parent ed1860de61
commit 4b5f5aec65
5 changed files with 124 additions and 0 deletions

View File

@ -105,6 +105,18 @@ func (c *Context) Apply() (*State, error) {
// Update our state, even if we have an error, for partial updates
c.state = s
// If we have no errors, then calculate the outputs if we have any
if err == nil && len(c.config.Outputs) > 0 {
s.Outputs = make(map[string]string)
for _, o := range c.config.Outputs {
if err = c.computeVars(s, o.RawConfig); err != nil {
break
}
s.Outputs[o.Name] = o.RawConfig.Config()["value"].(string)
}
}
return s, err
}
@ -232,6 +244,48 @@ func (c *Context) Validate() ([]string, []error) {
return warns, errs
}
// computeVars takes the State and given RawConfig and processes all
// the variables. This dynamically discovers the attributes instead of
// using a static map[string]string that the genericWalkFn uses.
func (c *Context) computeVars(s *State, raw *config.RawConfig) error {
// If there are on variables, then we're done
if len(raw.Variables) == 0 {
return nil
}
// Go through each variable and find it
vs := make(map[string]string)
for n, rawV := range raw.Variables {
switch v := rawV.(type) {
case *config.ResourceVariable:
r, ok := s.Resources[v.ResourceId()]
if !ok {
return fmt.Errorf(
"Resource '%s' not found for variable '%s'",
v.ResourceId(),
v.FullKey())
}
attr, ok := r.Attributes[v.Field]
if !ok {
return fmt.Errorf(
"Resource '%s' does not have attribute '%s' "+
"for variable '%s'",
v.ResourceId(),
v.Field,
v.FullKey())
}
vs[n] = attr
case *config.UserVariable:
vs[n] = c.variables[v.Name]
}
}
// Interpolate the variables
return raw.Interpolate(vs)
}
func (c *Context) graph() (*depgraph.Graph, error) {
return Graph(&GraphOpts{
Config: c.config,

View File

@ -508,6 +508,34 @@ func TestContextApply_hook(t *testing.T) {
}
}
func TestContextApply_output(t *testing.T) {
c := testConfig(t, "apply-output")
p := testProvider("aws")
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
ctx := testContext(t, &ContextOpts{
Config: c,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
})
if _, err := ctx.Plan(nil); err != nil {
t.Fatalf("err: %s", err)
}
state, err := ctx.Apply()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyOutputStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
}
func TestContextApply_unknownAttribute(t *testing.T) {
c := testConfig(t, "apply-unknown")
p := testProvider("aws")

View File

@ -16,6 +16,7 @@ import (
// can use to keep track of what real world resources it is actually
// managing.
type State struct {
Outputs map[string]string
Resources map[string]*ResourceState
once sync.Once
@ -96,6 +97,21 @@ func (s *State) String() string {
}
}
if len(s.Outputs) > 0 {
buf.WriteString("\nOutputs:\n\n")
ks := make([]string, 0, len(s.Outputs))
for k, _ := range s.Outputs {
ks = append(ks, k)
}
sort.Strings(ks)
for _, k := range ks {
v := s.Outputs[k]
buf.WriteString(fmt.Sprintf("%s = %s\n", k, v))
}
}
return buf.String()
}

View File

@ -106,6 +106,21 @@ aws_instance.foo:
num = 2
`
const testTerraformApplyOutputStr = `
aws_instance.bar:
ID = foo
foo = bar
type = aws_instance
aws_instance.foo:
ID = foo
num = 2
type = aws_instance
Outputs:
foo_num = 2
`
const testTerraformApplyUnknownAttrStr = `
aws_instance.foo:
ID = foo

View File

@ -0,0 +1,11 @@
resource "aws_instance" "foo" {
num = "2"
}
resource "aws_instance" "bar" {
foo = "bar"
}
output "foo_num" {
value = "${aws_instance.foo.num}"
}