terraform: outputs are computed for the state
This commit is contained in:
parent
ed1860de61
commit
4b5f5aec65
|
@ -105,6 +105,18 @@ func (c *Context) Apply() (*State, error) {
|
||||||
// Update our state, even if we have an error, for partial updates
|
// Update our state, even if we have an error, for partial updates
|
||||||
c.state = s
|
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
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,6 +244,48 @@ func (c *Context) Validate() ([]string, []error) {
|
||||||
return warns, errs
|
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) {
|
func (c *Context) graph() (*depgraph.Graph, error) {
|
||||||
return Graph(&GraphOpts{
|
return Graph(&GraphOpts{
|
||||||
Config: c.config,
|
Config: c.config,
|
||||||
|
|
|
@ -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) {
|
func TestContextApply_unknownAttribute(t *testing.T) {
|
||||||
c := testConfig(t, "apply-unknown")
|
c := testConfig(t, "apply-unknown")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
// can use to keep track of what real world resources it is actually
|
// can use to keep track of what real world resources it is actually
|
||||||
// managing.
|
// managing.
|
||||||
type State struct {
|
type State struct {
|
||||||
|
Outputs map[string]string
|
||||||
Resources map[string]*ResourceState
|
Resources map[string]*ResourceState
|
||||||
|
|
||||||
once sync.Once
|
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()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,21 @@ aws_instance.foo:
|
||||||
num = 2
|
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 = `
|
const testTerraformApplyUnknownAttrStr = `
|
||||||
aws_instance.foo:
|
aws_instance.foo:
|
||||||
ID = foo
|
ID = foo
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
resource "aws_instance" "foo" {
|
||||||
|
num = "2"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "bar" {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "foo_num" {
|
||||||
|
value = "${aws_instance.foo.num}"
|
||||||
|
}
|
Loading…
Reference in New Issue