Merge pull request #1033 from hashicorp/f-self

core: Self Variables
This commit is contained in:
Mitchell Hashimoto 2015-02-23 15:19:48 -08:00
commit 3f9dafc5f6
14 changed files with 262 additions and 4 deletions

View File

@ -477,6 +477,22 @@ func (c *Config) Validate() error {
}
}
// Validate the self variable
for source, rc := range c.rawConfigs() {
// Ignore provisioners. This is a pretty brittle way to do this,
// but better than also repeating all the resources.
if strings.Contains(source, "provision") {
continue
}
for _, v := range rc.Variables {
if _, ok := v.(*SelfVariable); ok {
errs = append(errs, fmt.Errorf(
"%s: cannot contain self-reference %s", source, v.FullKey()))
}
}
}
if len(errs) > 0 {
return &multierror.Error{Errors: errs}
}

View File

@ -214,6 +214,20 @@ func TestConfigValidate_provSplatSelf(t *testing.T) {
}
}
func TestConfigValidate_resourceProvVarSelf(t *testing.T) {
c := testConfig(t, "validate-resource-prov-self")
if err := c.Validate(); err != nil {
t.Fatalf("should be valid: %s", err)
}
}
func TestConfigValidate_resourceVarSelf(t *testing.T) {
c := testConfig(t, "validate-resource-self")
if err := c.Validate(); err == nil {
t.Fatal("should not be valid")
}
}
func TestConfigValidate_unknownThing(t *testing.T) {
c := testConfig(t, "validate-unknownthing")
if err := c.Validate(); err == nil {

View File

@ -68,6 +68,14 @@ type ResourceVariable struct {
key string
}
// SelfVariable is a variable that is referencing the same resource
// it is running on: "${self.address}"
type SelfVariable struct {
Field string
key string
}
// A UserVariable is a variable that is referencing a user variable
// that is inputted from outside the configuration. This looks like
// "${var.foo}"
@ -83,6 +91,8 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
return NewCountVariable(v)
} else if strings.HasPrefix(v, "path.") {
return NewPathVariable(v)
} else if strings.HasPrefix(v, "self.") {
return NewSelfVariable(v)
} else if strings.HasPrefix(v, "var.") {
return NewUserVariable(v)
} else if strings.HasPrefix(v, "module.") {
@ -199,6 +209,24 @@ func (v *ResourceVariable) FullKey() string {
return v.key
}
func NewSelfVariable(key string) (*SelfVariable, error) {
field := key[len("self."):]
return &SelfVariable{
Field: field,
key: key,
}, nil
}
func (v *SelfVariable) FullKey() string {
return v.key
}
func (v *SelfVariable) GoString() string {
return fmt.Sprintf("*%#v", *v)
}
func NewUserVariable(key string) (*UserVariable, error) {
name := key[len("var."):]
elem := ""

View File

@ -54,6 +54,14 @@ func TestNewInterpolatedVariable(t *testing.T) {
},
false,
},
{
"self.address",
&SelfVariable{
Field: "address",
key: "self.address",
},
false,
},
}
for i, tc := range cases {

View File

@ -0,0 +1,11 @@
resource "aws_instance" "foo" {
foo = "bar"
connection {
host = "${self.foo}"
}
provisioner "shell" {
value = "${self.foo}"
}
}

View File

@ -0,0 +1,3 @@
resource "aws_instance" "foo" {
foo = "${self.bar}"
}

View File

@ -3766,6 +3766,112 @@ func TestContext2Apply_provisionerResourceRef(t *testing.T) {
}
}
func TestContext2Apply_provisionerSelfRef(t *testing.T) {
m := testModule(t, "apply-provisioner-self-ref")
p := testProvider("aws")
pr := testProvisioner()
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
val, ok := c.Config["command"]
if !ok || val != "bar" {
t.Fatalf("bad value for command: %v %#v", val, c)
}
return nil
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
Provisioners: map[string]ResourceProvisionerFactory{
"shell": testProvisionerFuncFixed(pr),
},
})
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(testTerraformApplyProvisionerSelfRefStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
// Verify apply was invoked
if !pr.ApplyCalled {
t.Fatalf("provisioner not invoked")
}
}
func TestContext2Apply_provisionerMultiSelfRef(t *testing.T) {
var lock sync.Mutex
commands := make([]string, 0, 5)
m := testModule(t, "apply-provisioner-multi-self-ref")
p := testProvider("aws")
pr := testProvisioner()
p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn
pr.ApplyFn = func(rs *InstanceState, c *ResourceConfig) error {
lock.Lock()
defer lock.Unlock()
val, ok := c.Config["command"]
if !ok {
t.Fatalf("bad value for command: %v %#v", val, c)
}
commands = append(commands, val.(string))
return nil
}
ctx := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
Provisioners: map[string]ResourceProvisionerFactory{
"shell": testProvisionerFuncFixed(pr),
},
})
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(testTerraformApplyProvisionerMultiSelfRefStr)
if actual != expected {
t.Fatalf("bad: \n%s", actual)
}
// Verify apply was invoked
if !pr.ApplyCalled {
t.Fatalf("provisioner not invoked")
}
// Verify our result
sort.Strings(commands)
expectedCommands := []string{"number 0", "number 1", "number 2"}
if !reflect.DeepEqual(commands, expectedCommands) {
t.Fatalf("bad: %#v", commands)
}
}
// Provisioner should NOT run on a diff, only create
func TestContext2Apply_Provisioner_Diff(t *testing.T) {
m := testModule(t, "apply-provisioner-diff")

View File

@ -62,6 +62,8 @@ func (i *Interpolater) Values(
err = i.valuePathVar(scope, n, v, result)
case *config.ResourceVariable:
err = i.valueResourceVar(scope, n, v, result)
case *config.SelfVariable:
err = i.valueSelfVar(scope, n, v, result)
case *config.UserVariable:
err = i.valueUserVar(scope, n, v, result)
default:
@ -217,6 +219,24 @@ func (i *Interpolater) valueResourceVar(
return nil
}
func (i *Interpolater) valueSelfVar(
scope *InterpolationScope,
n string,
v *config.SelfVariable,
result map[string]ast.Variable) error {
rv, err := config.NewResourceVariable(fmt.Sprintf(
"%s.%s.%d.%s",
scope.Resource.Type,
scope.Resource.Name,
scope.Resource.CountIndex,
v.Field))
if err != nil {
return err
}
return i.valueResourceVar(scope, n, rv, result)
}
func (i *Interpolater) valueUserVar(
scope *InterpolationScope,
n string,

View File

@ -26,6 +26,13 @@ type ResourceProvisionerConfig struct {
// its current state, and potentially a desired diff from the state it
// wants to reach.
type Resource struct {
// These are all used by the new EvalNode stuff.
Name string
Type string
CountIndex int
// These aren't really used anymore anywhere, but we keep them around
// since we haven't done a proper cleanup yet.
Id string
Info *InstanceInfo
Config *ResourceConfig
@ -34,7 +41,6 @@ type Resource struct {
Provider ResourceProvider
State *InstanceState
Provisioners []*ResourceProvisionerConfig
CountIndex int
Flags ResourceFlag
TaintedIndex int
}

View File

@ -359,6 +359,28 @@ aws_instance.bar:
type = aws_instance
`
const testTerraformApplyProvisionerSelfRefStr = `
aws_instance.foo:
ID = foo
foo = bar
type = aws_instance
`
const testTerraformApplyProvisionerMultiSelfRefStr = `
aws_instance.foo.0:
ID = foo
foo = number 0
type = aws_instance
aws_instance.foo.1:
ID = foo
foo = number 1
type = aws_instance
aws_instance.foo.2:
ID = foo
foo = number 2
type = aws_instance
`
const testTerraformApplyProvisionerDiffStr = `
aws_instance.bar:
ID = foo

View File

@ -0,0 +1,8 @@
resource "aws_instance" "foo" {
count = 3
foo = "number ${count.index}"
provisioner "shell" {
command = "${self.foo}"
}
}

View File

@ -0,0 +1,7 @@
resource "aws_instance" "foo" {
foo = "bar"
provisioner "shell" {
command = "${self.foo}"
}
}

View File

@ -109,7 +109,11 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
if index < 0 {
index = 0
}
resource := &Resource{CountIndex: index}
resource := &Resource{
Name: n.Resource.Name,
Type: n.Resource.Type,
CountIndex: index,
}
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}

View File

@ -26,6 +26,11 @@ can reference static keys in the map with the syntax
get the value of the `us-east-1` key within the `amis` variable
that is a mapping.
**To reference attributes of your own resource**, the syntax is
`self.ATTRIBUTE`. For example `${self.private_ip_address}` will
interpolate that resource's private IP address. Note that this is
only allowed/valid within provisioners.
**To reference attributes of other resources**, the syntax is
`TYPE.NAME.ATTRIBUTE`. For example, `${aws_instance.web.id}`
will interpolate the ID attribute from the "aws\_instance"
@ -72,8 +77,8 @@ The supported built-in functions are:
only possible with splat variables from resources with a count
greater than one. Example: `join(",", aws_instance.foo.*.id)`
* `split(delim, string)` - Splits the string previously created by `join`
back into a list. This is useful for pushing lists through module
* `split(delim, string)` - Splits the string previously created by `join`
back into a list. This is useful for pushing lists through module
outputs since they currently only support string values.
Example: `split(",", module.amod.server_ids)`