commit
3f9dafc5f6
|
@ -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}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 := ""
|
||||
|
|
|
@ -54,6 +54,14 @@ func TestNewInterpolatedVariable(t *testing.T) {
|
|||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"self.address",
|
||||
&SelfVariable{
|
||||
Field: "address",
|
||||
key: "self.address",
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
resource "aws_instance" "foo" {
|
||||
foo = "bar"
|
||||
|
||||
connection {
|
||||
host = "${self.foo}"
|
||||
}
|
||||
|
||||
provisioner "shell" {
|
||||
value = "${self.foo}"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
resource "aws_instance" "foo" {
|
||||
foo = "${self.bar}"
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
resource "aws_instance" "foo" {
|
||||
count = 3
|
||||
foo = "number ${count.index}"
|
||||
|
||||
provisioner "shell" {
|
||||
command = "${self.foo}"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
resource "aws_instance" "foo" {
|
||||
foo = "bar"
|
||||
|
||||
provisioner "shell" {
|
||||
command = "${self.foo}"
|
||||
}
|
||||
}
|
|
@ -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)}
|
||||
|
||||
|
|
|
@ -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)`
|
||||
|
||||
|
|
Loading…
Reference in New Issue