Merge pull request #354 from hashicorp/f-dynamic-subgraph
Count can now interpolate values
This commit is contained in:
commit
6089e4973b
|
@ -4,6 +4,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/flatmap"
|
"github.com/hashicorp/terraform/flatmap"
|
||||||
|
@ -56,7 +57,7 @@ type ProviderConfig struct {
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
Count int
|
RawCount *RawConfig
|
||||||
RawConfig *RawConfig
|
RawConfig *RawConfig
|
||||||
Provisioners []*Provisioner
|
Provisioners []*Provisioner
|
||||||
DependsOn []string
|
DependsOn []string
|
||||||
|
@ -120,6 +121,16 @@ func (r *Module) Id() string {
|
||||||
return fmt.Sprintf("%s", r.Name)
|
return fmt.Sprintf("%s", r.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count returns the count of this resource.
|
||||||
|
func (r *Resource) Count() (int, error) {
|
||||||
|
v, err := strconv.ParseInt(r.RawCount.Value().(string), 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
// A unique identifier for this resource.
|
// A unique identifier for this resource.
|
||||||
func (r *Resource) Id() string {
|
func (r *Resource) Id() string {
|
||||||
return fmt.Sprintf("%s.%s", r.Type, r.Name)
|
return fmt.Sprintf("%s.%s", r.Type, r.Name)
|
||||||
|
@ -244,11 +255,37 @@ func (c *Config) Validate() error {
|
||||||
|
|
||||||
// Validate resources
|
// Validate resources
|
||||||
for n, r := range resources {
|
for n, r := range resources {
|
||||||
if r.Count < 1 {
|
// Verify count variables
|
||||||
|
for _, v := range r.RawCount.Variables {
|
||||||
|
switch v.(type) {
|
||||||
|
case *ModuleVariable:
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"%s: resource count can't reference module variable: %s",
|
||||||
|
n,
|
||||||
|
v.FullKey()))
|
||||||
|
case *ResourceVariable:
|
||||||
|
errs = append(errs, fmt.Errorf(
|
||||||
|
"%s: resource count can't reference resource variable: %s",
|
||||||
|
n,
|
||||||
|
v.FullKey()))
|
||||||
|
case *UserVariable:
|
||||||
|
// Good
|
||||||
|
default:
|
||||||
|
panic("Unknown type in count var: " + n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate with a fixed number to verify that its a number
|
||||||
|
r.RawCount.interpolate(func(Interpolation) (string, error) {
|
||||||
|
return "5", nil
|
||||||
|
})
|
||||||
|
_, err := strconv.ParseInt(r.RawCount.Value().(string), 0, 0)
|
||||||
|
if err != nil {
|
||||||
errs = append(errs, fmt.Errorf(
|
errs = append(errs, fmt.Errorf(
|
||||||
"%s: count must be greater than or equal to 1",
|
"%s: resource count must be an integer",
|
||||||
n))
|
n))
|
||||||
}
|
}
|
||||||
|
r.RawCount.init()
|
||||||
|
|
||||||
for _, d := range r.DependsOn {
|
for _, d := range r.DependsOn {
|
||||||
if _, ok := resources[d]; !ok {
|
if _, ok := resources[d]; !ok {
|
||||||
|
@ -267,8 +304,7 @@ func (c *Config) Validate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
id := fmt.Sprintf("%s.%s", rv.Type, rv.Name)
|
id := fmt.Sprintf("%s.%s", rv.Type, rv.Name)
|
||||||
r, ok := resources[id]
|
if _, ok := resources[id]; !ok {
|
||||||
if !ok {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
errs = append(errs, fmt.Errorf(
|
||||||
"%s: unknown resource '%s' referenced in variable %s",
|
"%s: unknown resource '%s' referenced in variable %s",
|
||||||
source,
|
source,
|
||||||
|
@ -276,18 +312,6 @@ func (c *Config) Validate() error {
|
||||||
rv.FullKey()))
|
rv.FullKey()))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it is a multi reference and resource has a single
|
|
||||||
// count, it is an error.
|
|
||||||
if r.Count > 1 && !rv.Multi {
|
|
||||||
errs = append(errs, fmt.Errorf(
|
|
||||||
"%s: variable '%s' must specify index for multi-count "+
|
|
||||||
"resource %s",
|
|
||||||
source,
|
|
||||||
rv.FullKey(),
|
|
||||||
id))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,6 +351,9 @@ func (c *Config) InterpolatedVariables() map[string][]InterpolatedVariable {
|
||||||
|
|
||||||
for _, rc := range c.Resources {
|
for _, rc := range c.Resources {
|
||||||
source := fmt.Sprintf("resource '%s'", rc.Id())
|
source := fmt.Sprintf("resource '%s'", rc.Id())
|
||||||
|
for _, v := range rc.RawCount.Variables {
|
||||||
|
result[source] = append(result[source], v)
|
||||||
|
}
|
||||||
for _, v := range rc.RawConfig.Variables {
|
for _, v := range rc.RawConfig.Variables {
|
||||||
result[source] = append(result[source], v)
|
result[source] = append(result[source], v)
|
||||||
}
|
}
|
||||||
|
@ -400,8 +427,8 @@ func (r *Resource) mergerMerge(m merger) merger {
|
||||||
result.Type = r2.Type
|
result.Type = r2.Type
|
||||||
result.RawConfig = result.RawConfig.merge(r2.RawConfig)
|
result.RawConfig = result.RawConfig.merge(r2.RawConfig)
|
||||||
|
|
||||||
if r2.Count > 0 {
|
if r2.RawCount.Value() != "1" {
|
||||||
result.Count = r2.Count
|
result.RawCount = r2.RawCount
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r2.Provisioners) > 0 {
|
if len(r2.Provisioners) > 0 {
|
||||||
|
|
|
@ -190,10 +190,10 @@ func resourcesStr(rs []*Resource) string {
|
||||||
for _, i := range order {
|
for _, i := range order {
|
||||||
r := rs[i]
|
r := rs[i]
|
||||||
result += fmt.Sprintf(
|
result += fmt.Sprintf(
|
||||||
"%s[%s] (x%d)\n",
|
"%s[%s] (x%s)\n",
|
||||||
r.Type,
|
r.Type,
|
||||||
r.Name,
|
r.Name,
|
||||||
r.Count)
|
r.RawCount.Value())
|
||||||
|
|
||||||
ks := make([]string, 0, len(r.RawConfig.Raw))
|
ks := make([]string, 0, len(r.RawConfig.Raw))
|
||||||
for k, _ := range r.RawConfig.Raw {
|
for k, _ := range r.RawConfig.Raw {
|
||||||
|
|
|
@ -9,6 +9,36 @@ import (
|
||||||
// This is the directory where our test fixtures are.
|
// This is the directory where our test fixtures are.
|
||||||
const fixtureDir = "./test-fixtures"
|
const fixtureDir = "./test-fixtures"
|
||||||
|
|
||||||
|
func TestConfigCount(t *testing.T) {
|
||||||
|
c := testConfig(t, "count-int")
|
||||||
|
actual, err := c.Resources[0].Count()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if actual != 5 {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigCount_string(t *testing.T) {
|
||||||
|
c := testConfig(t, "count-string")
|
||||||
|
actual, err := c.Resources[0].Count()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if actual != 5 {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigCount_var(t *testing.T) {
|
||||||
|
c := testConfig(t, "count-var")
|
||||||
|
_, err := c.Resources[0].Count()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigValidate(t *testing.T) {
|
func TestConfigValidate(t *testing.T) {
|
||||||
c := testConfig(t, "validate-good")
|
c := testConfig(t, "validate-good")
|
||||||
if err := c.Validate(); err != nil {
|
if err := c.Validate(); err != nil {
|
||||||
|
@ -23,27 +53,41 @@ func TestConfigValidate_badDependsOn(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigValidate_badMultiResource(t *testing.T) {
|
func TestConfigValidate_countInt(t *testing.T) {
|
||||||
c := testConfig(t, "validate-bad-multi-resource")
|
c := testConfig(t, "validate-count-int")
|
||||||
|
if err := c.Validate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigValidate_countModuleVar(t *testing.T) {
|
||||||
|
c := testConfig(t, "validate-count-module-var")
|
||||||
if err := c.Validate(); err == nil {
|
if err := c.Validate(); err == nil {
|
||||||
t.Fatal("should not be valid")
|
t.Fatal("should not be valid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigValidate_countBelowZero(t *testing.T) {
|
func TestConfigValidate_countNotInt(t *testing.T) {
|
||||||
c := testConfig(t, "validate-count-below-zero")
|
c := testConfig(t, "validate-count-not-int")
|
||||||
if err := c.Validate(); err == nil {
|
if err := c.Validate(); err == nil {
|
||||||
t.Fatal("should not be valid")
|
t.Fatal("should not be valid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigValidate_countZero(t *testing.T) {
|
func TestConfigValidate_countResourceVar(t *testing.T) {
|
||||||
c := testConfig(t, "validate-count-zero")
|
c := testConfig(t, "validate-count-resource-var")
|
||||||
if err := c.Validate(); err == nil {
|
if err := c.Validate(); err == nil {
|
||||||
t.Fatal("should not be valid")
|
t.Fatal("should not be valid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigValidate_countUserVar(t *testing.T) {
|
||||||
|
c := testConfig(t, "validate-count-user-var")
|
||||||
|
if err := c.Validate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigValidate_dupModule(t *testing.T) {
|
func TestConfigValidate_dupModule(t *testing.T) {
|
||||||
c := testConfig(t, "validate-dup-module")
|
c := testConfig(t, "validate-dup-module")
|
||||||
if err := c.Validate(); err == nil {
|
if err := c.Validate(); err == nil {
|
||||||
|
@ -100,6 +144,13 @@ func TestConfigValidate_unknownVar(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigValidate_unknownVarCount(t *testing.T) {
|
||||||
|
c := testConfig(t, "validate-unknownvar-count")
|
||||||
|
if err := c.Validate(); err == nil {
|
||||||
|
t.Fatal("should not be valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConfigValidate_varDefault(t *testing.T) {
|
func TestConfigValidate_varDefault(t *testing.T) {
|
||||||
c := testConfig(t, "validate-var-default")
|
c := testConfig(t, "validate-var-default")
|
||||||
if err := c.Validate(); err != nil {
|
if err := c.Validate(); err != nil {
|
||||||
|
|
|
@ -406,7 +406,7 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have a count, then figure it out
|
// If we have a count, then figure it out
|
||||||
var count int = 1
|
var count string = "1"
|
||||||
if o := obj.Get("count", false); o != nil {
|
if o := obj.Get("count", false); o != nil {
|
||||||
err = hcl.DecodeObject(&count, o)
|
err = hcl.DecodeObject(&count, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -417,6 +417,13 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) {
|
||||||
err)
|
err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
countConfig, err := NewRawConfig(map[string]interface{}{
|
||||||
|
"count": count,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
countConfig.Key = "count"
|
||||||
|
|
||||||
// If we have depends fields, then add those in
|
// If we have depends fields, then add those in
|
||||||
var dependsOn []string
|
var dependsOn []string
|
||||||
|
@ -475,7 +482,7 @@ func loadResourcesHcl(os *hclobj.Object) ([]*Resource, error) {
|
||||||
result = append(result, &Resource{
|
result = append(result, &Resource{
|
||||||
Name: k,
|
Name: k,
|
||||||
Type: t.Key,
|
Type: t.Key,
|
||||||
Count: count,
|
RawCount: countConfig,
|
||||||
RawConfig: rawConfig,
|
RawConfig: rawConfig,
|
||||||
Provisioners: provisioners,
|
Provisioners: provisioners,
|
||||||
DependsOn: dependsOn,
|
DependsOn: dependsOn,
|
||||||
|
|
|
@ -24,6 +24,7 @@ const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66"
|
||||||
// RawConfig supports a query-like interface to request
|
// RawConfig supports a query-like interface to request
|
||||||
// information from deep within the structure.
|
// information from deep within the structure.
|
||||||
type RawConfig struct {
|
type RawConfig struct {
|
||||||
|
Key string
|
||||||
Raw map[string]interface{}
|
Raw map[string]interface{}
|
||||||
Interpolations []Interpolation
|
Interpolations []Interpolation
|
||||||
Variables map[string]InterpolatedVariable
|
Variables map[string]InterpolatedVariable
|
||||||
|
@ -43,6 +44,18 @@ func NewRawConfig(raw map[string]interface{}) (*RawConfig, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value returns the value of the configuration if this configuration
|
||||||
|
// has a Key set. If this does not have a Key set, nil will be returned.
|
||||||
|
func (r *RawConfig) Value() interface{} {
|
||||||
|
if c := r.Config(); c != nil {
|
||||||
|
if v, ok := c[r.Key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.Raw[r.Key]
|
||||||
|
}
|
||||||
|
|
||||||
// Config returns the entire configuration with the variables
|
// Config returns the entire configuration with the variables
|
||||||
// interpolated from any call to Interpolate.
|
// interpolated from any call to Interpolate.
|
||||||
//
|
//
|
||||||
|
@ -66,24 +79,9 @@ func (r *RawConfig) Config() map[string]interface{} {
|
||||||
//
|
//
|
||||||
// If a variable key is missing, this will panic.
|
// If a variable key is missing, this will panic.
|
||||||
func (r *RawConfig) Interpolate(vs map[string]string) error {
|
func (r *RawConfig) Interpolate(vs map[string]string) error {
|
||||||
config, err := copystructure.Copy(r.Raw)
|
return r.interpolate(func(i Interpolation) (string, error) {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.config = config.(map[string]interface{})
|
|
||||||
|
|
||||||
fn := func(i Interpolation) (string, error) {
|
|
||||||
return i.Interpolate(vs)
|
return i.Interpolate(vs)
|
||||||
}
|
})
|
||||||
|
|
||||||
w := &interpolationWalker{F: fn, Replace: true}
|
|
||||||
err = reflectwalk.Walk(r.config, w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.unknownKeys = w.unknownKeys
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RawConfig) init() error {
|
func (r *RawConfig) init() error {
|
||||||
|
@ -113,6 +111,23 @@ func (r *RawConfig) init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RawConfig) interpolate(fn interpolationWalkerFunc) error {
|
||||||
|
config, err := copystructure.Copy(r.Raw)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.config = config.(map[string]interface{})
|
||||||
|
|
||||||
|
w := &interpolationWalker{F: fn, Replace: true}
|
||||||
|
err = reflectwalk.Walk(r.config, w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.unknownKeys = w.unknownKeys
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RawConfig) merge(r2 *RawConfig) *RawConfig {
|
func (r *RawConfig) merge(r2 *RawConfig) *RawConfig {
|
||||||
rawRaw, err := copystructure.Copy(r.Raw)
|
rawRaw, err := copystructure.Copy(r.Raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -140,11 +155,15 @@ func (r *RawConfig) UnknownKeys() []string {
|
||||||
|
|
||||||
// See GobEncode
|
// See GobEncode
|
||||||
func (r *RawConfig) GobDecode(b []byte) error {
|
func (r *RawConfig) GobDecode(b []byte) error {
|
||||||
err := gob.NewDecoder(bytes.NewReader(b)).Decode(&r.Raw)
|
var data gobRawConfig
|
||||||
|
err := gob.NewDecoder(bytes.NewReader(b)).Decode(&data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.Key = data.Key
|
||||||
|
r.Raw = data.Raw
|
||||||
|
|
||||||
return r.init()
|
return r.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,10 +172,20 @@ func (r *RawConfig) GobDecode(b []byte) error {
|
||||||
// tree of interpolated variables is recomputed on decode, since it is
|
// tree of interpolated variables is recomputed on decode, since it is
|
||||||
// referentially transparent.
|
// referentially transparent.
|
||||||
func (r *RawConfig) GobEncode() ([]byte, error) {
|
func (r *RawConfig) GobEncode() ([]byte, error) {
|
||||||
|
data := gobRawConfig{
|
||||||
|
Key: r.Key,
|
||||||
|
Raw: r.Raw,
|
||||||
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := gob.NewEncoder(&buf).Encode(r.Raw); err != nil {
|
if err := gob.NewEncoder(&buf).Encode(data); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type gobRawConfig struct {
|
||||||
|
Key string
|
||||||
|
Raw map[string]interface{}
|
||||||
|
}
|
||||||
|
|
|
@ -125,6 +125,36 @@ func TestRawConfig_unknown(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRawConfigValue(t *testing.T) {
|
||||||
|
raw := map[string]interface{}{
|
||||||
|
"foo": "${var.bar}",
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := NewRawConfig(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.Key = ""
|
||||||
|
if rc.Value() != nil {
|
||||||
|
t.Fatalf("bad: %#v", rc.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
rc.Key = "foo"
|
||||||
|
if rc.Value() != "${var.bar}" {
|
||||||
|
t.Fatalf("err: %#v", rc.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := map[string]string{"var.bar": "baz"}
|
||||||
|
if err := rc.Interpolate(vars); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rc.Value() != "baz" {
|
||||||
|
t.Fatalf("bad: %#v", rc.Value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRawConfig_implGob(t *testing.T) {
|
func TestRawConfig_implGob(t *testing.T) {
|
||||||
var _ gob.GobDecoder = new(RawConfig)
|
var _ gob.GobDecoder = new(RawConfig)
|
||||||
var _ gob.GobEncoder = new(RawConfig)
|
var _ gob.GobEncoder = new(RawConfig)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "foo" "bar" {
|
||||||
|
count = 5
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "foo" "bar" {
|
||||||
|
count = "5"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "foo" "bar" {
|
||||||
|
count = "${var.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
variable "foo" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
count = "${var.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
module "foo" {
|
||||||
|
source = "./bar"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
count = "${module.foo.bar}"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
variable "foo" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
count = "nope${var.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
resource "aws_instance" "foo" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
count = "${aws_instance.foo.bar}"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
variable "foo" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
count = "${var.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
resource "foo" "bar" {
|
||||||
|
default = "bar"
|
||||||
|
description = "bar"
|
||||||
|
count = "${var.bar}"
|
||||||
|
}
|
|
@ -869,7 +869,8 @@ func (c *walkContext) planDestroyWalkFn() depgraph.WalkFunc {
|
||||||
result := c.Meta.(*Plan)
|
result := c.Meta.(*Plan)
|
||||||
result.init()
|
result.init()
|
||||||
|
|
||||||
return func(n *depgraph.Noun) error {
|
var walkFn depgraph.WalkFunc
|
||||||
|
walkFn = func(n *depgraph.Noun) error {
|
||||||
switch m := n.Meta.(type) {
|
switch m := n.Meta.(type) {
|
||||||
case *GraphNodeModule:
|
case *GraphNodeModule:
|
||||||
// Build another walkContext for this module and walk it.
|
// Build another walkContext for this module and walk it.
|
||||||
|
@ -883,7 +884,13 @@ func (c *walkContext) planDestroyWalkFn() depgraph.WalkFunc {
|
||||||
|
|
||||||
return wc.Walk()
|
return wc.Walk()
|
||||||
case *GraphNodeResource:
|
case *GraphNodeResource:
|
||||||
|
// If we're expanding, then expand the nodes, and then rewalk the graph
|
||||||
|
if m.ExpandMode > ResourceExpandNone {
|
||||||
|
return c.genericWalkResource(m, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
r := m.Resource
|
r := m.Resource
|
||||||
|
|
||||||
if r.State != nil && r.State.ID != "" {
|
if r.State != nil && r.State.ID != "" {
|
||||||
log.Printf("[DEBUG] %s: Making for destroy", r.Id)
|
log.Printf("[DEBUG] %s: Making for destroy", r.Id)
|
||||||
|
|
||||||
|
@ -901,6 +908,8 @@ func (c *walkContext) planDestroyWalkFn() depgraph.WalkFunc {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return walkFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *walkContext) refreshWalkFn() depgraph.WalkFunc {
|
func (c *walkContext) refreshWalkFn() depgraph.WalkFunc {
|
||||||
|
@ -947,7 +956,8 @@ func (c *walkContext) validateWalkFn() depgraph.WalkFunc {
|
||||||
meta.Children = make(map[string]*walkValidateMeta)
|
meta.Children = make(map[string]*walkValidateMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(n *depgraph.Noun) error {
|
var walkFn depgraph.WalkFunc
|
||||||
|
walkFn = func(n *depgraph.Noun) error {
|
||||||
// If it is the root node, ignore
|
// If it is the root node, ignore
|
||||||
if n.Name == GraphRootNode {
|
if n.Name == GraphRootNode {
|
||||||
return nil
|
return nil
|
||||||
|
@ -979,6 +989,28 @@ func (c *walkContext) validateWalkFn() depgraph.WalkFunc {
|
||||||
panic("resource should never be nil")
|
panic("resource should never be nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're expanding, then expand the nodes, and then rewalk the graph
|
||||||
|
if rn.ExpandMode > ResourceExpandNone {
|
||||||
|
// Interpolate the count and verify it is non-negative
|
||||||
|
rc := NewResourceConfig(rn.Config.RawCount)
|
||||||
|
rc.interpolate(c)
|
||||||
|
count, err := rn.Config.Count()
|
||||||
|
if err == nil {
|
||||||
|
if count < 0 {
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"%s error: count must be positive", rn.Resource.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
|
meta.Errs = append(meta.Errs, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.genericWalkResource(rn, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
// If it doesn't have a provider, that is a different problem
|
// If it doesn't have a provider, that is a different problem
|
||||||
if rn.Resource.Provider == nil {
|
if rn.Resource.Provider == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -1051,13 +1083,16 @@ func (c *walkContext) validateWalkFn() depgraph.WalkFunc {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return walkFn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
|
func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
|
||||||
// This will keep track of whether we're stopped or not
|
// This will keep track of whether we're stopped or not
|
||||||
var stop uint32 = 0
|
var stop uint32 = 0
|
||||||
|
|
||||||
return func(n *depgraph.Noun) error {
|
var walkFn depgraph.WalkFunc
|
||||||
|
walkFn = func(n *depgraph.Noun) error {
|
||||||
// If it is the root node, ignore
|
// If it is the root node, ignore
|
||||||
if n.Name == GraphRootNode {
|
if n.Name == GraphRootNode {
|
||||||
return nil
|
return nil
|
||||||
|
@ -1104,9 +1139,6 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
|
||||||
return wc.Walk()
|
return wc.Walk()
|
||||||
case *GraphNodeResource:
|
case *GraphNodeResource:
|
||||||
// Continue, we care about this the most
|
// Continue, we care about this the most
|
||||||
case *GraphNodeResourceMeta:
|
|
||||||
// Skip it
|
|
||||||
return nil
|
|
||||||
case *GraphNodeResourceProvider:
|
case *GraphNodeResourceProvider:
|
||||||
sharedProvider := m.Provider
|
sharedProvider := m.Provider
|
||||||
|
|
||||||
|
@ -1135,6 +1167,11 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
|
||||||
|
|
||||||
rn := n.Meta.(*GraphNodeResource)
|
rn := n.Meta.(*GraphNodeResource)
|
||||||
|
|
||||||
|
// If we're expanding, then expand the nodes, and then rewalk the graph
|
||||||
|
if rn.ExpandMode > ResourceExpandNone {
|
||||||
|
return c.genericWalkResource(rn, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure that at least some resource configuration is set
|
// Make sure that at least some resource configuration is set
|
||||||
if rn.Config == nil {
|
if rn.Config == nil {
|
||||||
rn.Resource.Config = new(ResourceConfig)
|
rn.Resource.Config = new(ResourceConfig)
|
||||||
|
@ -1166,6 +1203,49 @@ func (c *walkContext) genericWalkFn(cb genericWalkFunc) depgraph.WalkFunc {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return walkFn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *walkContext) genericWalkResource(
|
||||||
|
rn *GraphNodeResource, fn depgraph.WalkFunc) error {
|
||||||
|
// Interpolate the count
|
||||||
|
rc := NewResourceConfig(rn.Config.RawCount)
|
||||||
|
rc.interpolate(c)
|
||||||
|
|
||||||
|
// Expand the node to the actual resources
|
||||||
|
ns, err := rn.Expand()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go through all the nouns and run them in parallel, collecting
|
||||||
|
// any errors.
|
||||||
|
var l sync.Mutex
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errs := make([]error, 0, len(ns))
|
||||||
|
for _, n := range ns {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
go func(n *depgraph.Noun) {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := fn(n); err != nil {
|
||||||
|
l.Lock()
|
||||||
|
defer l.Unlock()
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the subgraph
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// If there are errors, then we should return them
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return &multierror.Error{Errors: errs}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyProvisioners is used to run any provisioners a resource has
|
// applyProvisioners is used to run any provisioners a resource has
|
||||||
|
@ -1418,10 +1498,15 @@ func (c *walkContext) computeResourceVariable(
|
||||||
|
|
||||||
r, ok := module.Resources[id]
|
r, ok := module.Resources[id]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", fmt.Errorf(
|
if v.Multi && v.Index == 0 {
|
||||||
"Resource '%s' not found for variable '%s'",
|
r, ok = module.Resources[v.ResourceId()]
|
||||||
id,
|
}
|
||||||
v.FullKey())
|
if !ok {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"Resource '%s' not found for variable '%s'",
|
||||||
|
id,
|
||||||
|
v.FullKey())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Primary == nil {
|
if r.Primary == nil {
|
||||||
|
@ -1479,13 +1564,26 @@ func (c *walkContext) computeResourceMultiVariable(
|
||||||
// TODO: Not use only root module
|
// TODO: Not use only root module
|
||||||
module := c.Context.state.RootModule()
|
module := c.Context.state.RootModule()
|
||||||
|
|
||||||
|
count, err := cr.Count()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"Error reading %s count: %s",
|
||||||
|
v.ResourceId(),
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no count, return empty
|
||||||
|
if count == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
var values []string
|
var values []string
|
||||||
for i := 0; i < cr.Count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
id := fmt.Sprintf("%s.%d", v.ResourceId(), i)
|
id := fmt.Sprintf("%s.%d", v.ResourceId(), i)
|
||||||
|
|
||||||
// If we're dealing with only a single resource, then the
|
// If we're dealing with only a single resource, then the
|
||||||
// ID doesn't have a trailing index.
|
// ID doesn't have a trailing index.
|
||||||
if cr.Count == 1 {
|
if count == 1 {
|
||||||
id = v.ResourceId()
|
id = v.ResourceId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,25 @@ func TestContextValidate_badVar(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextValidate_countNegative(t *testing.T) {
|
||||||
|
p := testProvider("aws")
|
||||||
|
m := testModule(t, "validate-count-negative")
|
||||||
|
c := testContext(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
w, e := c.Validate()
|
||||||
|
if len(w) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", w)
|
||||||
|
}
|
||||||
|
if len(e) == 0 {
|
||||||
|
t.Fatalf("bad: %#v", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextValidate_moduleBadResource(t *testing.T) {
|
func TestContextValidate_moduleBadResource(t *testing.T) {
|
||||||
m := testModule(t, "validate-module-bad-rc")
|
m := testModule(t, "validate-module-bad-rc")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
@ -2428,6 +2447,95 @@ func TestContextPlan_count(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextPlan_countComputed(t *testing.T) {
|
||||||
|
m := testModule(t, "plan-count-computed")
|
||||||
|
p := testProvider("aws")
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
ctx := testContext(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := ctx.Plan(nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextPlan_countVar(t *testing.T) {
|
||||||
|
m := testModule(t, "plan-count-var")
|
||||||
|
p := testProvider("aws")
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
ctx := testContext(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
Variables: map[string]string{
|
||||||
|
"count": "3",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
plan, err := ctx.Plan(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(plan.String())
|
||||||
|
expected := strings.TrimSpace(testTerraformPlanCountVarStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextPlan_countZero(t *testing.T) {
|
||||||
|
m := testModule(t, "plan-count-zero")
|
||||||
|
p := testProvider("aws")
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
ctx := testContext(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
plan, err := ctx.Plan(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(plan.String())
|
||||||
|
expected := strings.TrimSpace(testTerraformPlanCountZeroStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContextPlan_countOneIndex(t *testing.T) {
|
||||||
|
m := testModule(t, "plan-count-one-index")
|
||||||
|
p := testProvider("aws")
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
ctx := testContext(t, &ContextOpts{
|
||||||
|
Module: m,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
plan, err := ctx.Plan(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(plan.String())
|
||||||
|
expected := strings.TrimSpace(testTerraformPlanCountOneIndexStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextPlan_countDecreaseToOne(t *testing.T) {
|
func TestContextPlan_countDecreaseToOne(t *testing.T) {
|
||||||
m := testModule(t, "plan-count-dec")
|
m := testModule(t, "plan-count-dec")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
|
@ -167,6 +167,19 @@ func (d *ModuleDiff) Empty() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Instances returns the instance diffs for the id given. This can return
|
||||||
|
// multiple instance diffs if there are counts within the resource.
|
||||||
|
func (d *ModuleDiff) Instances(id string) []*InstanceDiff {
|
||||||
|
var result []*InstanceDiff
|
||||||
|
for k, diff := range d.Resources {
|
||||||
|
if strings.HasPrefix(k, id) && !diff.Empty() {
|
||||||
|
result = append(result, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// IsRoot says whether or not this module diff is for the root module.
|
// IsRoot says whether or not this module diff is for the root module.
|
||||||
func (d *ModuleDiff) IsRoot() bool {
|
func (d *ModuleDiff) IsRoot() bool {
|
||||||
return reflect.DeepEqual(d.Path, rootModulePath)
|
return reflect.DeepEqual(d.Path, rootModulePath)
|
||||||
|
|
|
@ -91,16 +91,12 @@ type GraphNodeResource struct {
|
||||||
Config *config.Resource
|
Config *config.Resource
|
||||||
Resource *Resource
|
Resource *Resource
|
||||||
ResourceProviderNode string
|
ResourceProviderNode string
|
||||||
}
|
|
||||||
|
|
||||||
// GraphNodeResourceMeta is a node type in the graph that represents the
|
// All the fields below are related to expansion. These are set by
|
||||||
// metadata for a resource. There will be one meta node for every resource
|
// the graph but aren't useful individually.
|
||||||
// in the configuration.
|
ExpandMode ResourceExpandMode
|
||||||
type GraphNodeResourceMeta struct {
|
Diff *ModuleDiff
|
||||||
ID string
|
State *ModuleState
|
||||||
Name string
|
|
||||||
Type string
|
|
||||||
Count int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphNodeResourceProvider is a node type in the graph that represents
|
// GraphNodeResourceProvider is a node type in the graph that represents
|
||||||
|
@ -123,6 +119,16 @@ type graphSharedProvider struct {
|
||||||
parentNoun *depgraph.Noun
|
parentNoun *depgraph.Noun
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResourceExpandMode specifies the expand behavior of the GraphNodeResource
|
||||||
|
// node.
|
||||||
|
type ResourceExpandMode byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ResourceExpandNone ResourceExpandMode = iota
|
||||||
|
ResourceExpandApply
|
||||||
|
ResourceExpandDestroy
|
||||||
|
)
|
||||||
|
|
||||||
// Graph builds a dependency graph of all the resources for infrastructure
|
// Graph builds a dependency graph of all the resources for infrastructure
|
||||||
// change.
|
// change.
|
||||||
//
|
//
|
||||||
|
@ -321,13 +327,6 @@ func graphEncodeDependencies(g *depgraph.Graph) {
|
||||||
}
|
}
|
||||||
inject = append(inject, target.Resource.Id)
|
inject = append(inject, target.Resource.Id)
|
||||||
|
|
||||||
case *GraphNodeResourceMeta:
|
|
||||||
// Inject each sub-resource as a depedency
|
|
||||||
for i := 0; i < target.Count; i++ {
|
|
||||||
id := fmt.Sprintf("%s.%d", target.ID, i)
|
|
||||||
inject = append(inject, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *GraphNodeResourceProvider:
|
case *GraphNodeResourceProvider:
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
|
||||||
|
@ -372,108 +371,126 @@ func graphAddConfigResources(
|
||||||
meta := g.Meta.(*GraphMeta)
|
meta := g.Meta.(*GraphMeta)
|
||||||
|
|
||||||
// This tracks all the resource nouns
|
// This tracks all the resource nouns
|
||||||
nouns := make(map[string]*depgraph.Noun)
|
nounsList := make([]*depgraph.Noun, len(c.Resources))
|
||||||
for _, r := range c.Resources {
|
for i, r := range c.Resources {
|
||||||
resourceNouns := make([]*depgraph.Noun, r.Count)
|
name := r.Id()
|
||||||
for i := 0; i < r.Count; i++ {
|
|
||||||
name := r.Id()
|
|
||||||
index := -1
|
|
||||||
|
|
||||||
// If we have a count that is more than one, then make sure
|
// Build the noun
|
||||||
// we suffix with the number of the resource that this is.
|
nounsList[i] = &depgraph.Noun{
|
||||||
if r.Count > 1 {
|
Name: name,
|
||||||
name = fmt.Sprintf("%s.%d", name, i)
|
Meta: &GraphNodeResource{
|
||||||
index = i
|
Index: -1,
|
||||||
}
|
Config: r,
|
||||||
|
Resource: &Resource{
|
||||||
var state *ResourceState
|
Id: name,
|
||||||
if mod != nil {
|
Info: &InstanceInfo{
|
||||||
// Lookup the resource state
|
Id: name,
|
||||||
state = mod.Resources[name]
|
ModulePath: meta.ModulePath,
|
||||||
if state == nil {
|
Type: r.Type,
|
||||||
if r.Count == 1 {
|
|
||||||
// If the count is one, check the state for ".0"
|
|
||||||
// appended, which might exist if we go from
|
|
||||||
// count > 1 to count == 1.
|
|
||||||
state = mod.Resources[r.Id()+".0"]
|
|
||||||
} else if i == 0 {
|
|
||||||
// If count is greater than one, check for state
|
|
||||||
// with just the ID, which might exist if we go
|
|
||||||
// from count == 1 to count > 1
|
|
||||||
state = mod.Resources[r.Id()]
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(mitchellh): If one of the above works, delete
|
|
||||||
// the old style and just copy it to the new style.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if state == nil {
|
|
||||||
state = &ResourceState{
|
|
||||||
Type: r.Type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := FlagPrimary
|
|
||||||
if len(state.Tainted) > 0 {
|
|
||||||
flags |= FlagHasTainted
|
|
||||||
}
|
|
||||||
|
|
||||||
resourceNouns[i] = &depgraph.Noun{
|
|
||||||
Name: name,
|
|
||||||
Meta: &GraphNodeResource{
|
|
||||||
Index: index,
|
|
||||||
Config: r,
|
|
||||||
Resource: &Resource{
|
|
||||||
Id: name,
|
|
||||||
Info: &InstanceInfo{
|
|
||||||
Id: name,
|
|
||||||
ModulePath: meta.ModulePath,
|
|
||||||
Type: r.Type,
|
|
||||||
},
|
|
||||||
State: state.Primary,
|
|
||||||
Config: NewResourceConfig(r.RawConfig),
|
|
||||||
Flags: flags,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
State: mod.View(name),
|
||||||
|
ExpandMode: ResourceExpandApply,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have more than one, then create a meta node to track
|
/*
|
||||||
// the resources.
|
TODO: probably did something important, bring it back somehow
|
||||||
if r.Count > 1 {
|
resourceNouns := make([]*depgraph.Noun, r.Count)
|
||||||
metaNoun := &depgraph.Noun{
|
for i := 0; i < r.Count; i++ {
|
||||||
Name: r.Id(),
|
name := r.Id()
|
||||||
Meta: &GraphNodeResourceMeta{
|
index := -1
|
||||||
ID: r.Id(),
|
|
||||||
Name: r.Name,
|
// If we have a count that is more than one, then make sure
|
||||||
Type: r.Type,
|
// we suffix with the number of the resource that this is.
|
||||||
Count: r.Count,
|
if r.Count > 1 {
|
||||||
},
|
name = fmt.Sprintf("%s.%d", name, i)
|
||||||
|
index = i
|
||||||
|
}
|
||||||
|
|
||||||
|
var state *ResourceState
|
||||||
|
if mod != nil {
|
||||||
|
// Lookup the resource state
|
||||||
|
state = mod.Resources[name]
|
||||||
|
if state == nil {
|
||||||
|
if r.Count == 1 {
|
||||||
|
// If the count is one, check the state for ".0"
|
||||||
|
// appended, which might exist if we go from
|
||||||
|
// count > 1 to count == 1.
|
||||||
|
state = mod.Resources[r.Id()+".0"]
|
||||||
|
} else if i == 0 {
|
||||||
|
// If count is greater than one, check for state
|
||||||
|
// with just the ID, which might exist if we go
|
||||||
|
// from count == 1 to count > 1
|
||||||
|
state = mod.Resources[r.Id()]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mitchellh): If one of the above works, delete
|
||||||
|
// the old style and just copy it to the new style.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == nil {
|
||||||
|
state = &ResourceState{
|
||||||
|
Type: r.Type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := FlagPrimary
|
||||||
|
if len(state.Tainted) > 0 {
|
||||||
|
flags |= FlagHasTainted
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceNouns[i] = &depgraph.Noun{
|
||||||
|
Name: name,
|
||||||
|
Meta: &GraphNodeResource{
|
||||||
|
Index: index,
|
||||||
|
Config: r,
|
||||||
|
Resource: &Resource{
|
||||||
|
Id: name,
|
||||||
|
Info: &InstanceInfo{
|
||||||
|
Id: name,
|
||||||
|
ModulePath: meta.ModulePath,
|
||||||
|
Type: r.Type,
|
||||||
|
},
|
||||||
|
State: state.Primary,
|
||||||
|
Config: NewResourceConfig(r.RawConfig),
|
||||||
|
Flags: flags,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have more than one, then create a meta node to track
|
||||||
|
// the resources.
|
||||||
|
if r.Count > 1 {
|
||||||
|
metaNoun := &depgraph.Noun{
|
||||||
|
Name: r.Id(),
|
||||||
|
Meta: &GraphNodeResourceMeta{
|
||||||
|
ID: r.Id(),
|
||||||
|
Name: r.Name,
|
||||||
|
Type: r.Type,
|
||||||
|
Count: r.Count,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the dependencies on this noun
|
||||||
|
for _, n := range resourceNouns {
|
||||||
|
metaNoun.Deps = append(metaNoun.Deps, &depgraph.Dependency{
|
||||||
|
Name: n.Name,
|
||||||
|
Source: metaNoun,
|
||||||
|
Target: n,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign it to the map so that we have it
|
||||||
|
nouns[metaNoun.Name] = metaNoun
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the dependencies on this noun
|
|
||||||
for _, n := range resourceNouns {
|
for _, n := range resourceNouns {
|
||||||
metaNoun.Deps = append(metaNoun.Deps, &depgraph.Dependency{
|
nouns[n.Name] = n
|
||||||
Name: n.Name,
|
|
||||||
Source: metaNoun,
|
|
||||||
Target: n,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// Assign it to the map so that we have it
|
|
||||||
nouns[metaNoun.Name] = metaNoun
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, n := range resourceNouns {
|
|
||||||
nouns[n.Name] = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the list of nouns that we iterate over
|
|
||||||
nounsList := make([]*depgraph.Noun, 0, len(nouns))
|
|
||||||
for _, n := range nouns {
|
|
||||||
nounsList = append(nounsList, n)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Name = "terraform"
|
g.Name = "terraform"
|
||||||
|
@ -501,15 +518,33 @@ func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rd, ok := d.Resources[rn.Resource.Id]
|
change := false
|
||||||
if !ok {
|
destroy := false
|
||||||
|
diffs := d.Instances(rn.Resource.Id)
|
||||||
|
if len(diffs) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if rd.Empty() {
|
for _, d := range diffs {
|
||||||
continue
|
if d.Destroy {
|
||||||
|
destroy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(d.Attributes) > 0 {
|
||||||
|
change = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if rd.Destroy {
|
// If we're expanding, save the diff so we can add it on later
|
||||||
|
if rn.ExpandMode > ResourceExpandNone {
|
||||||
|
rn.Diff = d
|
||||||
|
}
|
||||||
|
|
||||||
|
var rd *InstanceDiff
|
||||||
|
if rn.ExpandMode == ResourceExpandNone {
|
||||||
|
rd = diffs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if destroy {
|
||||||
// If we're destroying, we create a new destroy node with
|
// If we're destroying, we create a new destroy node with
|
||||||
// the proper dependencies. Perform a dirty copy operation.
|
// the proper dependencies. Perform a dirty copy operation.
|
||||||
newNode := new(GraphNodeResource)
|
newNode := new(GraphNodeResource)
|
||||||
|
@ -520,6 +555,11 @@ func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error {
|
||||||
// Make the diff _just_ the destroy.
|
// Make the diff _just_ the destroy.
|
||||||
newNode.Resource.Diff = &InstanceDiff{Destroy: true}
|
newNode.Resource.Diff = &InstanceDiff{Destroy: true}
|
||||||
|
|
||||||
|
// Make sure ExpandDestroy is set if Expand
|
||||||
|
if newNode.ExpandMode == ResourceExpandApply {
|
||||||
|
newNode.ExpandMode = ResourceExpandDestroy
|
||||||
|
}
|
||||||
|
|
||||||
// Create the new node
|
// Create the new node
|
||||||
newN := &depgraph.Noun{
|
newN := &depgraph.Noun{
|
||||||
Name: fmt.Sprintf("%s (destroy)", newNode.Resource.Id),
|
Name: fmt.Sprintf("%s (destroy)", newNode.Resource.Id),
|
||||||
|
@ -533,17 +573,19 @@ func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error {
|
||||||
// Append it to the list so we handle it later
|
// Append it to the list so we handle it later
|
||||||
nlist = append(nlist, newN)
|
nlist = append(nlist, newN)
|
||||||
|
|
||||||
// Mark the old diff to not destroy since we handle that in
|
if rd != nil {
|
||||||
// the dedicated node.
|
// Mark the old diff to not destroy since we handle that in
|
||||||
newDiff := new(InstanceDiff)
|
// the dedicated node.
|
||||||
*newDiff = *rd
|
newDiff := new(InstanceDiff)
|
||||||
newDiff.Destroy = false
|
*newDiff = *rd
|
||||||
rd = newDiff
|
newDiff.Destroy = false
|
||||||
|
rd = newDiff
|
||||||
|
}
|
||||||
|
|
||||||
// The dependency ordering depends on if the CreateBeforeDestroy
|
// The dependency ordering depends on if the CreateBeforeDestroy
|
||||||
// flag is enabled. If so, we must create the replacement first,
|
// flag is enabled. If so, we must create the replacement first,
|
||||||
// and then destroy the old instance.
|
// and then destroy the old instance.
|
||||||
if rn.Config != nil && rn.Config.Lifecycle.CreateBeforeDestroy && !rd.Empty() {
|
if rn.Config != nil && rn.Config.Lifecycle.CreateBeforeDestroy && change {
|
||||||
dep := &depgraph.Dependency{
|
dep := &depgraph.Dependency{
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
Source: newN,
|
Source: newN,
|
||||||
|
@ -560,11 +602,13 @@ func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error {
|
||||||
|
|
||||||
// Add a depedency from the root, since the create node
|
// Add a depedency from the root, since the create node
|
||||||
// does not depend on us
|
// does not depend on us
|
||||||
g.Root.Deps = append(g.Root.Deps, &depgraph.Dependency{
|
if g.Root != nil {
|
||||||
Name: newN.Name,
|
g.Root.Deps = append(g.Root.Deps, &depgraph.Dependency{
|
||||||
Source: g.Root,
|
Name: newN.Name,
|
||||||
Target: newN,
|
Source: g.Root,
|
||||||
})
|
Target: newN,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Set the ReplacePrimary flag on the new instance so that
|
// Set the ReplacePrimary flag on the new instance so that
|
||||||
// it will become the new primary, and Diposed flag on the
|
// it will become the new primary, and Diposed flag on the
|
||||||
|
@ -653,33 +697,6 @@ func graphAddDiff(g *depgraph.Graph, d *ModuleDiff) error {
|
||||||
num--
|
num--
|
||||||
i--
|
i--
|
||||||
|
|
||||||
case *GraphNodeResourceMeta:
|
|
||||||
// Check if any of the resources part of the meta node
|
|
||||||
// are being destroyed, because we must be destroyed first.
|
|
||||||
for i := 0; i < target.Count; i++ {
|
|
||||||
id := fmt.Sprintf("%s.%d", target.ID, i)
|
|
||||||
for _, n2 := range nlist {
|
|
||||||
rn2 := n2.Meta.(*GraphNodeResource)
|
|
||||||
if id == rn2.Resource.Id {
|
|
||||||
newDep := &depgraph.Dependency{
|
|
||||||
Name: n.Name,
|
|
||||||
Source: n2,
|
|
||||||
Target: n,
|
|
||||||
}
|
|
||||||
injected[newDep] = struct{}{}
|
|
||||||
n2.Deps = append(n2.Deps, newDep)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop the dependency, since there is
|
|
||||||
// nothing that needs to be done for a meta
|
|
||||||
// resource on destroy.
|
|
||||||
deps[i], deps[num-1] = deps[num-1], nil
|
|
||||||
num--
|
|
||||||
i--
|
|
||||||
|
|
||||||
case *GraphNodeModule:
|
case *GraphNodeModule:
|
||||||
// We invert any module dependencies so we're destroyed
|
// We invert any module dependencies so we're destroyed
|
||||||
// first, before any modules are applied.
|
// first, before any modules are applied.
|
||||||
|
@ -877,7 +894,7 @@ func graphAddOrphanDeps(g *depgraph.Graph, mod *ModuleState) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, depName := range rs.Dependencies {
|
for _, depName := range rs.Dependencies {
|
||||||
if compareName != depName {
|
if !strings.HasPrefix(depName, compareName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dep := &depgraph.Dependency{
|
dep := &depgraph.Dependency{
|
||||||
|
@ -886,6 +903,7 @@ func graphAddOrphanDeps(g *depgraph.Graph, mod *ModuleState) {
|
||||||
Target: n2,
|
Target: n2,
|
||||||
}
|
}
|
||||||
n.Deps = append(n.Deps, dep)
|
n.Deps = append(n.Deps, dep)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -999,8 +1017,6 @@ func graphAddRoot(g *depgraph.Graph) {
|
||||||
if m.Index != -1 {
|
if m.Index != -1 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case *GraphNodeResourceMeta:
|
|
||||||
// Always in the graph
|
|
||||||
case *GraphNodeResourceProvider:
|
case *GraphNodeResourceProvider:
|
||||||
// ResourceProviders don't need to be in the root deps because
|
// ResourceProviders don't need to be in the root deps because
|
||||||
// they're always pointed to by some resource.
|
// they're always pointed to by some resource.
|
||||||
|
@ -1030,8 +1046,12 @@ func graphAddVariableDeps(g *depgraph.Graph) {
|
||||||
|
|
||||||
case *GraphNodeResource:
|
case *GraphNodeResource:
|
||||||
if m.Config != nil {
|
if m.Config != nil {
|
||||||
|
// Handle the count variables
|
||||||
|
vars := m.Config.RawCount.Variables
|
||||||
|
nounAddVariableDeps(g, n, vars, false)
|
||||||
|
|
||||||
// Handle the resource variables
|
// Handle the resource variables
|
||||||
vars := m.Config.RawConfig.Variables
|
vars = m.Config.RawConfig.Variables
|
||||||
nounAddVariableDeps(g, n, vars, false)
|
nounAddVariableDeps(g, n, vars, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1573,6 +1593,211 @@ func (p *graphSharedProvider) MergeConfig(
|
||||||
return NewResourceConfig(rc)
|
return NewResourceConfig(rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Expand will expand this node into a subgraph if Expand is set.
|
||||||
|
func (n *GraphNodeResource) Expand() ([]*depgraph.Noun, error) {
|
||||||
|
// Expand the count out, which should be interpolated at this point.
|
||||||
|
count, err := n.Config.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] %s: expanding to count = %d", n.Resource.Id, count)
|
||||||
|
|
||||||
|
// TODO: can we DRY this up?
|
||||||
|
g := new(depgraph.Graph)
|
||||||
|
g.Meta = &GraphMeta{
|
||||||
|
ModulePath: n.Resource.Info.ModulePath,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the nodes to create. If we're just looking for the
|
||||||
|
// nodes to create, return that.
|
||||||
|
n.expand(g, count)
|
||||||
|
|
||||||
|
// Add in the diff if we have it
|
||||||
|
if n.Diff != nil {
|
||||||
|
if err := graphAddDiff(g, n.Diff); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're just expanding the apply, then filter those out and
|
||||||
|
// return them now.
|
||||||
|
if n.ExpandMode == ResourceExpandApply {
|
||||||
|
return n.finalizeNouns(g, false), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if n.State != nil {
|
||||||
|
// TODO: orphans
|
||||||
|
|
||||||
|
// Add the tainted resources
|
||||||
|
graphAddTainted(g, n.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.finalizeNouns(g, true), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// expand expands this resource and adds the resources to the graph. It
|
||||||
|
// adds both create and destroy resources.
|
||||||
|
func (n *GraphNodeResource) expand(g *depgraph.Graph, count int) {
|
||||||
|
// Create the list of nouns
|
||||||
|
result := make([]*depgraph.Noun, 0, count)
|
||||||
|
|
||||||
|
// Build the key set so we know what is removed
|
||||||
|
var keys map[string]struct{}
|
||||||
|
if n.State != nil {
|
||||||
|
keys = make(map[string]struct{})
|
||||||
|
for k, _ := range n.State.Resources {
|
||||||
|
keys[k] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First thing, expand the counts that we have defined for our
|
||||||
|
// current config into the full set of resources that are being
|
||||||
|
// created.
|
||||||
|
r := n.Config
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
name := r.Id()
|
||||||
|
index := -1
|
||||||
|
|
||||||
|
// If we have a count that is more than one, then make sure
|
||||||
|
// we suffix with the number of the resource that this is.
|
||||||
|
if count > 1 {
|
||||||
|
name = fmt.Sprintf("%s.%d", name, i)
|
||||||
|
index = i
|
||||||
|
}
|
||||||
|
|
||||||
|
var state *ResourceState
|
||||||
|
if n.State != nil {
|
||||||
|
// Lookup the resource state
|
||||||
|
if s, ok := n.State.Resources[name]; ok {
|
||||||
|
state = s
|
||||||
|
delete(keys, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == nil {
|
||||||
|
if count == 1 {
|
||||||
|
// If the count is one, check the state for ".0"
|
||||||
|
// appended, which might exist if we go from
|
||||||
|
// count > 1 to count == 1.
|
||||||
|
k := r.Id() + ".0"
|
||||||
|
state = n.State.Resources[k]
|
||||||
|
delete(keys, k)
|
||||||
|
} else if i == 0 {
|
||||||
|
// If count is greater than one, check for state
|
||||||
|
// with just the ID, which might exist if we go
|
||||||
|
// from count == 1 to count > 1
|
||||||
|
state = n.State.Resources[r.Id()]
|
||||||
|
delete(keys, r.Id())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == nil {
|
||||||
|
state = &ResourceState{
|
||||||
|
Type: r.Type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := FlagPrimary
|
||||||
|
if len(state.Tainted) > 0 {
|
||||||
|
flags |= FlagHasTainted
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the base resource so we can fill it in
|
||||||
|
resource := n.copyResource(name)
|
||||||
|
resource.State = state.Primary
|
||||||
|
resource.Flags = flags
|
||||||
|
|
||||||
|
// Add the result
|
||||||
|
result = append(result, &depgraph.Noun{
|
||||||
|
Name: name,
|
||||||
|
Meta: &GraphNodeResource{
|
||||||
|
Index: index,
|
||||||
|
Config: r,
|
||||||
|
Resource: resource,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go over the leftover keys which are orphans (decreasing counts)
|
||||||
|
for k, _ := range keys {
|
||||||
|
rs := n.State.Resources[k]
|
||||||
|
|
||||||
|
resource := n.copyResource(k)
|
||||||
|
resource.Config = NewResourceConfig(nil)
|
||||||
|
resource.State = rs.Primary
|
||||||
|
resource.Flags = FlagOrphan
|
||||||
|
|
||||||
|
noun := &depgraph.Noun{
|
||||||
|
Name: k,
|
||||||
|
Meta: &GraphNodeResource{
|
||||||
|
Index: -1,
|
||||||
|
Resource: resource,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, noun)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Nouns = append(g.Nouns, result...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyResource copies the Resource structure to assign to a subgraph.
|
||||||
|
func (n *GraphNodeResource) copyResource(id string) *Resource {
|
||||||
|
info := *n.Resource.Info
|
||||||
|
info.Id = id
|
||||||
|
resource := *n.Resource
|
||||||
|
resource.Id = id
|
||||||
|
resource.Info = &info
|
||||||
|
resource.Config = NewResourceConfig(n.Config.RawConfig)
|
||||||
|
return &resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *GraphNodeResource) finalizeNouns(
|
||||||
|
g *depgraph.Graph, destroy bool) []*depgraph.Noun {
|
||||||
|
result := make([]*depgraph.Noun, 0, len(g.Nouns))
|
||||||
|
for _, n := range g.Nouns {
|
||||||
|
rn, ok := n.Meta.(*GraphNodeResource)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the diff is nil, then we're not destroying, so append only
|
||||||
|
// in that case.
|
||||||
|
if rn.Resource.Diff == nil {
|
||||||
|
if !destroy {
|
||||||
|
result = append(result, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are destroying, append it only if we care about destroys
|
||||||
|
if rn.Resource.Diff.Destroy {
|
||||||
|
if destroy {
|
||||||
|
result = append(result, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is an oprhan, we only care about it if we're destroying.
|
||||||
|
if rn.Resource.Flags&FlagOrphan != 0 {
|
||||||
|
if destroy {
|
||||||
|
result = append(result, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're not destroying, then add it only if we don't
|
||||||
|
// care about deploys.
|
||||||
|
if !destroy {
|
||||||
|
result = append(result, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// matchingPrefixes takes a resource type and a set of resource
|
// matchingPrefixes takes a resource type and a set of resource
|
||||||
// providers we know about by prefix and returns a list of prefixes
|
// providers we know about by prefix and returns a list of prefixes
|
||||||
// that might be valid for that resource.
|
// that might be valid for that resource.
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/depgraph"
|
"github.com/hashicorp/terraform/depgraph"
|
||||||
"github.com/hashicorp/terraform/digraph"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GraphDotOpts are options for turning a graph into dot format.
|
// GraphDotOpts are options for turning a graph into dot format.
|
||||||
|
@ -221,6 +220,7 @@ func graphDotAddResources(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the meta resources
|
// Handle the meta resources
|
||||||
|
/*
|
||||||
edgeBuf.Reset()
|
edgeBuf.Reset()
|
||||||
for _, n := range g.Nouns {
|
for _, n := range g.Nouns {
|
||||||
_, ok := n.Meta.(*GraphNodeResourceMeta)
|
_, ok := n.Meta.(*GraphNodeResourceMeta)
|
||||||
|
@ -264,6 +264,7 @@ func graphDotAddResources(
|
||||||
buf.WriteString(edgeBuf.String())
|
buf.WriteString(edgeBuf.String())
|
||||||
buf.WriteString("\n")
|
buf.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func graphDotAddResourceProviders(
|
func graphDotAddResourceProviders(
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGraph(t *testing.T) {
|
func TestGraph_basic(t *testing.T) {
|
||||||
m := testModule(t, "graph-basic")
|
m := testModule(t, "graph-basic")
|
||||||
|
|
||||||
g, err := Graph(&GraphOpts{Module: m})
|
g, err := Graph(&GraphOpts{Module: m})
|
||||||
|
@ -43,6 +43,21 @@ func TestGraph_count(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGraph_varResource(t *testing.T) {
|
||||||
|
m := testModule(t, "graph-count-var-resource")
|
||||||
|
|
||||||
|
g, err := Graph(&GraphOpts{Module: m})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(g.String())
|
||||||
|
expected := strings.TrimSpace(testTerraformGraphCountVarResourceStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGraph_cycle(t *testing.T) {
|
func TestGraph_cycle(t *testing.T) {
|
||||||
m := testModule(t, "graph-cycle")
|
m := testModule(t, "graph-cycle")
|
||||||
|
|
||||||
|
@ -447,6 +462,8 @@ func TestGraphAddDiff(t *testing.T) {
|
||||||
t.Fatalf("bad:\n\n%s", actual)
|
t.Fatalf("bad:\n\n%s", actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: test this somewhere
|
||||||
// Verify that the state has been added
|
// Verify that the state has been added
|
||||||
n := g.Noun("aws_instance.foo")
|
n := g.Noun("aws_instance.foo")
|
||||||
rn := n.Meta.(*GraphNodeResource)
|
rn := n.Meta.(*GraphNodeResource)
|
||||||
|
@ -456,6 +473,7 @@ func TestGraphAddDiff(t *testing.T) {
|
||||||
if !reflect.DeepEqual(actual2, expected2) {
|
if !reflect.DeepEqual(actual2, expected2) {
|
||||||
t.Fatalf("bad: %#v", actual2)
|
t.Fatalf("bad: %#v", actual2)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGraphAddDiff_destroy(t *testing.T) {
|
func TestGraphAddDiff_destroy(t *testing.T) {
|
||||||
|
@ -609,13 +627,11 @@ func TestGraphAddDiff_destroy_counts(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the state has been added
|
// Verify that the state has been added
|
||||||
n := g.Noun("aws_instance.web.0 (destroy)")
|
n := g.Noun("aws_instance.web (destroy)")
|
||||||
rn := n.Meta.(*GraphNodeResource)
|
rn := n.Meta.(*GraphNodeResource)
|
||||||
|
|
||||||
expected2 := &InstanceDiff{Destroy: true}
|
if rn.ExpandMode != ResourceExpandDestroy {
|
||||||
actual2 := rn.Resource.Diff
|
t.Fatalf("bad: %#v", rn)
|
||||||
if !reflect.DeepEqual(actual2, expected2) {
|
|
||||||
t.Fatalf("bad: %#v", actual2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that our original structure has not been modified
|
// Verify that our original structure has not been modified
|
||||||
|
@ -816,13 +832,13 @@ func TestGraphEncodeDependencies_count(t *testing.T) {
|
||||||
// This should encode the dependency information into the state
|
// This should encode the dependency information into the state
|
||||||
graphEncodeDependencies(g)
|
graphEncodeDependencies(g)
|
||||||
|
|
||||||
web := g.Noun("aws_instance.web.0").Meta.(*GraphNodeResource).Resource
|
web := g.Noun("aws_instance.web").Meta.(*GraphNodeResource).Resource
|
||||||
if len(web.Dependencies) != 0 {
|
if len(web.Dependencies) != 0 {
|
||||||
t.Fatalf("bad: %#v", web)
|
t.Fatalf("bad: %#v", web)
|
||||||
}
|
}
|
||||||
|
|
||||||
weblb := g.Noun("aws_load_balancer.weblb").Meta.(*GraphNodeResource).Resource
|
weblb := g.Noun("aws_load_balancer.weblb").Meta.(*GraphNodeResource).Resource
|
||||||
if len(weblb.Dependencies) != 3 {
|
if len(weblb.Dependencies) != 1 {
|
||||||
t.Fatalf("bad: %#v", weblb)
|
t.Fatalf("bad: %#v", weblb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -956,12 +972,6 @@ root
|
||||||
const testTerraformGraphCountStr = `
|
const testTerraformGraphCountStr = `
|
||||||
root: root
|
root: root
|
||||||
aws_instance.web
|
aws_instance.web
|
||||||
aws_instance.web -> aws_instance.web.0
|
|
||||||
aws_instance.web -> aws_instance.web.1
|
|
||||||
aws_instance.web -> aws_instance.web.2
|
|
||||||
aws_instance.web.0
|
|
||||||
aws_instance.web.1
|
|
||||||
aws_instance.web.2
|
|
||||||
aws_load_balancer.weblb
|
aws_load_balancer.weblb
|
||||||
aws_load_balancer.weblb -> aws_instance.web
|
aws_load_balancer.weblb -> aws_instance.web
|
||||||
root
|
root
|
||||||
|
@ -969,6 +979,19 @@ root
|
||||||
root -> aws_load_balancer.weblb
|
root -> aws_load_balancer.weblb
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testTerraformGraphCountVarResourceStr = `
|
||||||
|
root: root
|
||||||
|
aws_instance.foo
|
||||||
|
aws_instance.web
|
||||||
|
aws_instance.web -> aws_instance.foo
|
||||||
|
aws_load_balancer.weblb
|
||||||
|
aws_load_balancer.weblb -> aws_instance.web
|
||||||
|
root
|
||||||
|
root -> aws_instance.foo
|
||||||
|
root -> aws_instance.web
|
||||||
|
root -> aws_load_balancer.weblb
|
||||||
|
`
|
||||||
|
|
||||||
const testTerraformGraphDependsStr = `
|
const testTerraformGraphDependsStr = `
|
||||||
root: root
|
root: root
|
||||||
aws_instance.db
|
aws_instance.db
|
||||||
|
@ -982,12 +1005,7 @@ root
|
||||||
const testTerraformGraphDependsCountStr = `
|
const testTerraformGraphDependsCountStr = `
|
||||||
root: root
|
root: root
|
||||||
aws_instance.db
|
aws_instance.db
|
||||||
aws_instance.db -> aws_instance.db.0
|
aws_instance.db -> aws_instance.web
|
||||||
aws_instance.db -> aws_instance.db.1
|
|
||||||
aws_instance.db.0
|
|
||||||
aws_instance.db.0 -> aws_instance.web
|
|
||||||
aws_instance.db.1
|
|
||||||
aws_instance.db.1 -> aws_instance.web
|
|
||||||
aws_instance.web
|
aws_instance.web
|
||||||
root
|
root
|
||||||
root -> aws_instance.db
|
root -> aws_instance.db
|
||||||
|
@ -1024,21 +1042,9 @@ root
|
||||||
const testTerraformGraphDiffDestroyCountsStr = `
|
const testTerraformGraphDiffDestroyCountsStr = `
|
||||||
root: root
|
root: root
|
||||||
aws_instance.web
|
aws_instance.web
|
||||||
aws_instance.web -> aws_instance.web.0
|
aws_instance.web -> aws_instance.web (destroy)
|
||||||
aws_instance.web -> aws_instance.web.1
|
aws_instance.web (destroy)
|
||||||
aws_instance.web -> aws_instance.web.2
|
aws_instance.web (destroy) -> aws_load_balancer.weblb (destroy)
|
||||||
aws_instance.web.0
|
|
||||||
aws_instance.web.0 -> aws_instance.web.0 (destroy)
|
|
||||||
aws_instance.web.0 (destroy)
|
|
||||||
aws_instance.web.0 (destroy) -> aws_load_balancer.weblb (destroy)
|
|
||||||
aws_instance.web.1
|
|
||||||
aws_instance.web.1 -> aws_instance.web.1 (destroy)
|
|
||||||
aws_instance.web.1 (destroy)
|
|
||||||
aws_instance.web.1 (destroy) -> aws_load_balancer.weblb (destroy)
|
|
||||||
aws_instance.web.2
|
|
||||||
aws_instance.web.2 -> aws_instance.web.2 (destroy)
|
|
||||||
aws_instance.web.2 (destroy)
|
|
||||||
aws_instance.web.2 (destroy) -> aws_load_balancer.weblb (destroy)
|
|
||||||
aws_load_balancer.weblb
|
aws_load_balancer.weblb
|
||||||
aws_load_balancer.weblb -> aws_instance.web
|
aws_load_balancer.weblb -> aws_instance.web
|
||||||
aws_load_balancer.weblb -> aws_load_balancer.weblb (destroy)
|
aws_load_balancer.weblb -> aws_load_balancer.weblb (destroy)
|
||||||
|
@ -1197,16 +1203,8 @@ root
|
||||||
const testTerraformGraphCountOrphanStr = `
|
const testTerraformGraphCountOrphanStr = `
|
||||||
root: root
|
root: root
|
||||||
aws_instance.web
|
aws_instance.web
|
||||||
aws_instance.web -> aws_instance.web.0
|
|
||||||
aws_instance.web -> aws_instance.web.1
|
|
||||||
aws_instance.web -> aws_instance.web.2
|
|
||||||
aws_instance.web.0
|
|
||||||
aws_instance.web.1
|
|
||||||
aws_instance.web.2
|
|
||||||
aws_load_balancer.old
|
aws_load_balancer.old
|
||||||
aws_load_balancer.old -> aws_instance.web.0
|
aws_load_balancer.old -> aws_instance.web
|
||||||
aws_load_balancer.old -> aws_instance.web.1
|
|
||||||
aws_load_balancer.old -> aws_instance.web.2
|
|
||||||
aws_load_balancer.weblb
|
aws_load_balancer.weblb
|
||||||
aws_load_balancer.weblb -> aws_instance.web
|
aws_load_balancer.weblb -> aws_instance.web
|
||||||
root
|
root
|
||||||
|
|
|
@ -198,9 +198,10 @@ func (m *ModuleState) Orphans(c *config.Config) []string {
|
||||||
for _, r := range c.Resources {
|
for _, r := range c.Resources {
|
||||||
delete(keys, r.Id())
|
delete(keys, r.Id())
|
||||||
|
|
||||||
// Mark all the counts as not orphans.
|
for k, _ := range keys {
|
||||||
for i := 0; i < r.Count; i++ {
|
if strings.HasPrefix(k, r.Id()+".") {
|
||||||
delete(keys, fmt.Sprintf("%s.%d", r.Id(), i))
|
delete(keys, k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +213,24 @@ func (m *ModuleState) Orphans(c *config.Config) []string {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// View returns a view with the given resource prefix.
|
||||||
|
func (m *ModuleState) View(id string) *ModuleState {
|
||||||
|
if m == nil {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
r := m.deepcopy()
|
||||||
|
for k, _ := range r.Resources {
|
||||||
|
if id == k || strings.HasPrefix(k, id+".") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(r.Resources, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
func (m *ModuleState) init() {
|
func (m *ModuleState) init() {
|
||||||
if m.Outputs == nil {
|
if m.Outputs == nil {
|
||||||
m.Outputs = make(map[string]string)
|
m.Outputs = make(map[string]string)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
|
@ -80,9 +81,10 @@ func (s *StateV1) Orphans(c *config.Config) []string {
|
||||||
for _, r := range c.Resources {
|
for _, r := range c.Resources {
|
||||||
delete(keys, r.Id())
|
delete(keys, r.Id())
|
||||||
|
|
||||||
// Mark all the counts as not orphans.
|
for k, _ := range keys {
|
||||||
for i := 0; i < r.Count; i++ {
|
if strings.HasPrefix(k, r.Id()+".") {
|
||||||
delete(keys, fmt.Sprintf("%s.%d", r.Id(), i))
|
delete(keys, k)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -477,6 +477,54 @@ STATE:
|
||||||
<no state>
|
<no state>
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testTerraformPlanCountOneIndexStr = `
|
||||||
|
DIFF:
|
||||||
|
|
||||||
|
CREATE: aws_instance.bar
|
||||||
|
foo: "" => "foo"
|
||||||
|
type: "" => "aws_instance"
|
||||||
|
CREATE: aws_instance.foo
|
||||||
|
foo: "" => "foo"
|
||||||
|
type: "" => "aws_instance"
|
||||||
|
|
||||||
|
STATE:
|
||||||
|
|
||||||
|
<no state>
|
||||||
|
`
|
||||||
|
|
||||||
|
const testTerraformPlanCountZeroStr = `
|
||||||
|
DIFF:
|
||||||
|
|
||||||
|
CREATE: aws_instance.bar
|
||||||
|
foo: "" => ""
|
||||||
|
type: "" => "aws_instance"
|
||||||
|
|
||||||
|
STATE:
|
||||||
|
|
||||||
|
<no state>
|
||||||
|
`
|
||||||
|
|
||||||
|
const testTerraformPlanCountVarStr = `
|
||||||
|
DIFF:
|
||||||
|
|
||||||
|
CREATE: aws_instance.bar
|
||||||
|
foo: "" => "foo,foo,foo"
|
||||||
|
type: "" => "aws_instance"
|
||||||
|
CREATE: aws_instance.foo.0
|
||||||
|
foo: "" => "foo"
|
||||||
|
type: "" => "aws_instance"
|
||||||
|
CREATE: aws_instance.foo.1
|
||||||
|
foo: "" => "foo"
|
||||||
|
type: "" => "aws_instance"
|
||||||
|
CREATE: aws_instance.foo.2
|
||||||
|
foo: "" => "foo"
|
||||||
|
type: "" => "aws_instance"
|
||||||
|
|
||||||
|
STATE:
|
||||||
|
|
||||||
|
<no state>
|
||||||
|
`
|
||||||
|
|
||||||
const testTerraformPlanCountDecreaseStr = `
|
const testTerraformPlanCountDecreaseStr = `
|
||||||
DIFF:
|
DIFF:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
resource "aws_instance" "foo" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "web" {
|
||||||
|
count = "${aws_instance.foo.bar}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_load_balancer" "weblb" {
|
||||||
|
members = "${aws_instance.web.*.id}"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
resource "aws_instance" "foo" {
|
||||||
|
num = "2"
|
||||||
|
compute = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "bar" {
|
||||||
|
count = "${aws_instance.foo.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
resource "aws_instance" "foo" {
|
||||||
|
count = 1
|
||||||
|
foo = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "bar" {
|
||||||
|
foo = "${aws_instance.foo.0.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
variable "count" {}
|
||||||
|
|
||||||
|
resource "aws_instance" "foo" {
|
||||||
|
count = "${var.count}"
|
||||||
|
foo = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "bar" {
|
||||||
|
foo = "${aws_instance.foo.*.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
resource "aws_instance" "foo" {
|
||||||
|
count = 0
|
||||||
|
foo = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "bar" {
|
||||||
|
foo = "${aws_instance.foo.*.foo}"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
resource "aws_instance" "test" {
|
||||||
|
count = "-5"
|
||||||
|
}
|
Loading…
Reference in New Issue