mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-24 16:10:46 -06:00
terraform: turn multi-counts into multiple nodes
This commit is contained in:
parent
5e79ddf7c6
commit
e7b7644cbf
@ -9,6 +9,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/digraph"
|
||||
@ -42,7 +43,34 @@ type ValidateError struct {
|
||||
}
|
||||
|
||||
func (v *ValidateError) Error() string {
|
||||
return "The depedency graph is not valid"
|
||||
var msgs []string
|
||||
|
||||
if v.MissingRoot {
|
||||
msgs = append(msgs, "The graph has no single root")
|
||||
}
|
||||
|
||||
for _, n := range v.Unreachable {
|
||||
msgs = append(msgs, fmt.Sprintf(
|
||||
"Unreachable node: %s", n.Name))
|
||||
}
|
||||
|
||||
for _, c := range v.Cycles {
|
||||
cycleNodes := make([]string, len(c))
|
||||
for i, n := range c {
|
||||
cycleNodes[i] = n.Name
|
||||
}
|
||||
|
||||
msgs = append(msgs, fmt.Sprintf(
|
||||
"Cycle: %s", strings.Join(cycleNodes, " -> ")))
|
||||
}
|
||||
|
||||
for i, m := range msgs {
|
||||
msgs[i] = fmt.Sprintf("* %s", m)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"The dependency graph is not valid:\n\n%s",
|
||||
strings.Join(msgs, "\n"))
|
||||
}
|
||||
|
||||
// ConstraintError is used to return detailed violation
|
||||
|
@ -45,8 +45,12 @@ type GraphOpts struct {
|
||||
// graph. This node is just a placemarker and has no associated functionality.
|
||||
const GraphRootNode = "root"
|
||||
|
||||
// GraphNodeResource is a node type in the graph that represents a resource.
|
||||
// GraphNodeResource is a node type in the graph that represents a resource
|
||||
// that will be created or managed. Unlike the GraphNodeResourceMeta node,
|
||||
// this represents a _single_, _resource_ to be managed, not a set of resources
|
||||
// or a component of a resource.
|
||||
type GraphNodeResource struct {
|
||||
Index int
|
||||
Type string
|
||||
Config *config.Resource
|
||||
Orphan bool
|
||||
@ -54,6 +58,15 @@ type GraphNodeResource struct {
|
||||
ResourceProviderID string
|
||||
}
|
||||
|
||||
// GraphNodeResourceMeta is a node type in the graph that represents the
|
||||
// metadata for a resource. There will be one meta node for every resource
|
||||
// in the configuration.
|
||||
type GraphNodeResourceMeta struct {
|
||||
Name string
|
||||
Type string
|
||||
Count int
|
||||
}
|
||||
|
||||
// GraphNodeResourceProvider is a node type in the graph that represents
|
||||
// the configuration for a resource provider.
|
||||
type GraphNodeResourceProvider struct {
|
||||
@ -152,18 +165,57 @@ func graphAddConfigResources(
|
||||
}
|
||||
}
|
||||
|
||||
noun := &depgraph.Noun{
|
||||
Name: r.Id(),
|
||||
Meta: &GraphNodeResource{
|
||||
Type: r.Type,
|
||||
Config: r,
|
||||
Resource: &Resource{
|
||||
Id: r.Id(),
|
||||
State: state,
|
||||
resourceNouns := make([]*depgraph.Noun, r.Count)
|
||||
for i := 0; i < r.Count; i++ {
|
||||
name := r.Id()
|
||||
|
||||
// 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 r.Count > 1 {
|
||||
name = fmt.Sprintf("%s.%d", name, i)
|
||||
}
|
||||
|
||||
resourceNouns[i] = &depgraph.Noun{
|
||||
Name: name,
|
||||
Meta: &GraphNodeResource{
|
||||
Type: r.Type,
|
||||
Config: r,
|
||||
Resource: &Resource{
|
||||
Id: name,
|
||||
State: state,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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{
|
||||
Name: r.Id(),
|
||||
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
|
||||
}
|
||||
|
||||
for _, n := range resourceNouns {
|
||||
nouns[n.Name] = n
|
||||
}
|
||||
nouns[noun.Name] = noun
|
||||
}
|
||||
|
||||
// Build the list of nouns that we iterate over
|
||||
@ -357,7 +409,10 @@ func graphAddProviderConfigs(g *depgraph.Graph, c *config.Config) {
|
||||
nounsList := make([]*depgraph.Noun, 0, 2)
|
||||
pcNouns := make(map[string]*depgraph.Noun)
|
||||
for _, noun := range g.Nouns {
|
||||
resourceNode := noun.Meta.(*GraphNodeResource)
|
||||
resourceNode, ok := noun.Meta.(*GraphNodeResource)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Look up the provider config for this resource
|
||||
pcName := config.ProviderConfigName(resourceNode.Type, c.ProviderConfigs)
|
||||
@ -401,11 +456,6 @@ func graphAddProviderConfigs(g *depgraph.Graph, c *config.Config) {
|
||||
func graphAddRoot(g *depgraph.Graph) {
|
||||
root := &depgraph.Noun{Name: GraphRootNode}
|
||||
for _, n := range g.Nouns {
|
||||
// The root only needs to depend on all the resources
|
||||
if _, ok := n.Meta.(*GraphNodeResource); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
root.Deps = append(root.Deps, &depgraph.Dependency{
|
||||
Name: n.Name,
|
||||
Source: root,
|
||||
|
@ -27,6 +27,21 @@ func TestGraph_configRequired(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraph_count(t *testing.T) {
|
||||
config := testConfig(t, "graph-count")
|
||||
|
||||
g, err := Graph(&GraphOpts{Config: config})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTerraformGraphCountStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGraph_cycle(t *testing.T) {
|
||||
config := testConfig(t, "graph-cycle")
|
||||
|
||||
@ -226,6 +241,25 @@ root
|
||||
root -> openstack_floating_ip.random
|
||||
`
|
||||
|
||||
const testTerraformGraphCountStr = `
|
||||
root: root
|
||||
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_instance.web
|
||||
root
|
||||
root -> aws_instance.web
|
||||
root -> aws_instance.web.0
|
||||
root -> aws_instance.web.1
|
||||
root -> aws_instance.web.2
|
||||
root -> aws_load_balancer.weblb
|
||||
`
|
||||
|
||||
const testTerraformGraphDiffStr = `
|
||||
root: root
|
||||
aws_instance.foo
|
||||
|
7
terraform/test-fixtures/graph-count/main.tf
Normal file
7
terraform/test-fixtures/graph-count/main.tf
Normal file
@ -0,0 +1,7 @@
|
||||
resource "aws_instance" "web" {
|
||||
count = 3
|
||||
}
|
||||
|
||||
resource "aws_load_balancer" "weblb" {
|
||||
members = "${aws_instance.web.*.id}"
|
||||
}
|
Loading…
Reference in New Issue
Block a user