Skip to content

Commit

Permalink
core: Make copies when creating destroy nodes
Browse files Browse the repository at this point in the history
Fixes an interpolation race that was occurring when a tainted destroy
node and a primary destroy node both tried to interpolate a computed
count in their config. Since they were sharing a pointer to the _same_
config, depending on how the race played out one of them could catch the
config uninterpolated and would then throw a syntax error.

The `Copy()` tree implemented for this fix can probably be used
elsewhere - basically we should copy the config whenever we drop nodes
into the graph - but for now I'm just applying it to the place that
fixes this bug.

Fixes #4982
  • Loading branch information
phinze committed Feb 5, 2016
1 parent 1188f1b commit 7dcdf37
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 1 deletion.
32 changes: 32 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,29 @@ type Resource struct {
Lifecycle ResourceLifecycle
}

// Copy returns a copy of this Resource. Helpful for avoiding shared
// config pointers across multiple pieces of the graph that need to do
// interpolation.
func (r *Resource) Copy() *Resource {
n := &Resource{
Name: r.Name,
Type: r.Type,
RawCount: r.RawCount.Copy(),
RawConfig: r.RawConfig.Copy(),
Provisioners: make([]*Provisioner, 0, len(r.Provisioners)),
Provider: r.Provider,
DependsOn: make([]string, 0, len(r.DependsOn)),
Lifecycle: r.Lifecycle,
}
for _, p := range r.Provisioners {
n.Provisioners = append(n.Provisioners, p.Copy())
}
for _, d := range r.DependsOn {
n.DependsOn = append(n.DependsOn, d)
}
return n
}

// ResourceLifecycle is used to store the lifecycle tuning parameters
// to allow customized behavior
type ResourceLifecycle struct {
Expand All @@ -96,6 +119,15 @@ type Provisioner struct {
ConnInfo *RawConfig
}

// Copy returns a copy of this Provisioner
func (p *Provisioner) Copy() *Provisioner {
return &Provisioner{
Type: p.Type,
RawConfig: p.RawConfig.Copy(),
ConnInfo: p.ConnInfo.Copy(),
}
}

// Variable is a variable defined within the configuration.
type Variable struct {
Name string
Expand Down
18 changes: 17 additions & 1 deletion terraform/graph_config_node_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ type GraphNodeConfigResource struct {
Path []string
}

func (n *GraphNodeConfigResource) Copy() *GraphNodeConfigResource {
ncr := &GraphNodeConfigResource{
Resource: n.Resource.Copy(),
DestroyMode: n.DestroyMode,
Targets: make([]ResourceAddress, 0, len(n.Targets)),
Path: make([]string, 0, len(n.Path)),
}
for _, t := range n.Targets {
ncr.Targets = append(ncr.Targets, *t.Copy())
}
for _, p := range n.Path {
ncr.Path = append(ncr.Path, p)
}
return ncr
}

func (n *GraphNodeConfigResource) ConfigType() GraphNodeConfigType {
return GraphNodeConfigTypeResource
}
Expand Down Expand Up @@ -247,7 +263,7 @@ func (n *GraphNodeConfigResource) DestroyNode(mode GraphNodeDestroyMode) GraphNo
}

result := &graphNodeResourceDestroy{
GraphNodeConfigResource: *n,
GraphNodeConfigResource: *n.Copy(),
Original: n,
}
result.DestroyMode = mode
Expand Down
15 changes: 15 additions & 0 deletions terraform/resource_address.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ type ResourceAddress struct {
Type string
}

// Copy returns a copy of this ResourceAddress
func (r *ResourceAddress) Copy() *ResourceAddress {
n := &ResourceAddress{
Path: make([]string, 0, len(r.Path)),
Index: r.Index,
InstanceType: r.InstanceType,
Name: r.Name,
Type: r.Type,
}
for _, p := range r.Path {
n.Path = append(n.Path, p)
}
return n
}

func ParseResourceAddress(s string) (*ResourceAddress, error) {
matches, err := tokenizeResourceAddress(s)
if err != nil {
Expand Down

0 comments on commit 7dcdf37

Please sign in to comment.