mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
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 {
|
if len(errs) > 0 {
|
||||||
return &multierror.Error{Errors: errs}
|
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) {
|
func TestConfigValidate_unknownThing(t *testing.T) {
|
||||||
c := testConfig(t, "validate-unknownthing")
|
c := testConfig(t, "validate-unknownthing")
|
||||||
if err := c.Validate(); err == nil {
|
if err := c.Validate(); err == nil {
|
||||||
|
@ -68,6 +68,14 @@ type ResourceVariable struct {
|
|||||||
key string
|
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
|
// A UserVariable is a variable that is referencing a user variable
|
||||||
// that is inputted from outside the configuration. This looks like
|
// that is inputted from outside the configuration. This looks like
|
||||||
// "${var.foo}"
|
// "${var.foo}"
|
||||||
@ -83,6 +91,8 @@ func NewInterpolatedVariable(v string) (InterpolatedVariable, error) {
|
|||||||
return NewCountVariable(v)
|
return NewCountVariable(v)
|
||||||
} else if strings.HasPrefix(v, "path.") {
|
} else if strings.HasPrefix(v, "path.") {
|
||||||
return NewPathVariable(v)
|
return NewPathVariable(v)
|
||||||
|
} else if strings.HasPrefix(v, "self.") {
|
||||||
|
return NewSelfVariable(v)
|
||||||
} else if strings.HasPrefix(v, "var.") {
|
} else if strings.HasPrefix(v, "var.") {
|
||||||
return NewUserVariable(v)
|
return NewUserVariable(v)
|
||||||
} else if strings.HasPrefix(v, "module.") {
|
} else if strings.HasPrefix(v, "module.") {
|
||||||
@ -199,6 +209,24 @@ func (v *ResourceVariable) FullKey() string {
|
|||||||
return v.key
|
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) {
|
func NewUserVariable(key string) (*UserVariable, error) {
|
||||||
name := key[len("var."):]
|
name := key[len("var."):]
|
||||||
elem := ""
|
elem := ""
|
||||||
|
@ -54,6 +54,14 @@ func TestNewInterpolatedVariable(t *testing.T) {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"self.address",
|
||||||
|
&SelfVariable{
|
||||||
|
Field: "address",
|
||||||
|
key: "self.address",
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tc := range cases {
|
for i, tc := range cases {
|
||||||
|
11
config/test-fixtures/validate-resource-prov-self/main.tf
Normal file
11
config/test-fixtures/validate-resource-prov-self/main.tf
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
resource "aws_instance" "foo" {
|
||||||
|
foo = "bar"
|
||||||
|
|
||||||
|
connection {
|
||||||
|
host = "${self.foo}"
|
||||||
|
}
|
||||||
|
|
||||||
|
provisioner "shell" {
|
||||||
|
value = "${self.foo}"
|
||||||
|
}
|
||||||
|
}
|
3
config/test-fixtures/validate-resource-self/main.tf
Normal file
3
config/test-fixtures/validate-resource-self/main.tf
Normal file
@ -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
|
// Provisioner should NOT run on a diff, only create
|
||||||
func TestContext2Apply_Provisioner_Diff(t *testing.T) {
|
func TestContext2Apply_Provisioner_Diff(t *testing.T) {
|
||||||
m := testModule(t, "apply-provisioner-diff")
|
m := testModule(t, "apply-provisioner-diff")
|
||||||
|
@ -62,6 +62,8 @@ func (i *Interpolater) Values(
|
|||||||
err = i.valuePathVar(scope, n, v, result)
|
err = i.valuePathVar(scope, n, v, result)
|
||||||
case *config.ResourceVariable:
|
case *config.ResourceVariable:
|
||||||
err = i.valueResourceVar(scope, n, v, result)
|
err = i.valueResourceVar(scope, n, v, result)
|
||||||
|
case *config.SelfVariable:
|
||||||
|
err = i.valueSelfVar(scope, n, v, result)
|
||||||
case *config.UserVariable:
|
case *config.UserVariable:
|
||||||
err = i.valueUserVar(scope, n, v, result)
|
err = i.valueUserVar(scope, n, v, result)
|
||||||
default:
|
default:
|
||||||
@ -217,6 +219,24 @@ func (i *Interpolater) valueResourceVar(
|
|||||||
return nil
|
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(
|
func (i *Interpolater) valueUserVar(
|
||||||
scope *InterpolationScope,
|
scope *InterpolationScope,
|
||||||
n string,
|
n string,
|
||||||
|
@ -26,6 +26,13 @@ type ResourceProvisionerConfig struct {
|
|||||||
// its current state, and potentially a desired diff from the state it
|
// its current state, and potentially a desired diff from the state it
|
||||||
// wants to reach.
|
// wants to reach.
|
||||||
type Resource struct {
|
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
|
Id string
|
||||||
Info *InstanceInfo
|
Info *InstanceInfo
|
||||||
Config *ResourceConfig
|
Config *ResourceConfig
|
||||||
@ -34,7 +41,6 @@ type Resource struct {
|
|||||||
Provider ResourceProvider
|
Provider ResourceProvider
|
||||||
State *InstanceState
|
State *InstanceState
|
||||||
Provisioners []*ResourceProvisionerConfig
|
Provisioners []*ResourceProvisionerConfig
|
||||||
CountIndex int
|
|
||||||
Flags ResourceFlag
|
Flags ResourceFlag
|
||||||
TaintedIndex int
|
TaintedIndex int
|
||||||
}
|
}
|
||||||
|
@ -359,6 +359,28 @@ aws_instance.bar:
|
|||||||
type = aws_instance
|
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 = `
|
const testTerraformApplyProvisionerDiffStr = `
|
||||||
aws_instance.bar:
|
aws_instance.bar:
|
||||||
ID = foo
|
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 {
|
if index < 0 {
|
||||||
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)}
|
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
|
get the value of the `us-east-1` key within the `amis` variable
|
||||||
that is a mapping.
|
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
|
**To reference attributes of other resources**, the syntax is
|
||||||
`TYPE.NAME.ATTRIBUTE`. For example, `${aws_instance.web.id}`
|
`TYPE.NAME.ATTRIBUTE`. For example, `${aws_instance.web.id}`
|
||||||
will interpolate the ID attribute from the "aws\_instance"
|
will interpolate the ID attribute from the "aws\_instance"
|
||||||
|
Loading…
Reference in New Issue
Block a user