mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 08:21:07 -06:00
terraform: getting closer to mapping resource providers properly
This commit is contained in:
parent
94a11583c2
commit
cdab89d7c1
@ -2,6 +2,7 @@ package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/depgraph"
|
||||
@ -23,9 +24,9 @@ type GraphNodeResource struct {
|
||||
// GraphNodeResourceProvider is a node type in the graph that represents
|
||||
// the configuration for a resource provider.
|
||||
type GraphNodeResourceProvider struct {
|
||||
ID string
|
||||
Provider ResourceProvider
|
||||
Config *config.ProviderConfig
|
||||
ID string
|
||||
Providers []ResourceProvider
|
||||
Config *config.ProviderConfig
|
||||
}
|
||||
|
||||
// Graph builds a dependency graph for the given configuration and state.
|
||||
@ -78,8 +79,9 @@ func graphAddConfigResources(g *depgraph.Graph, c *config.Config) {
|
||||
noun := &depgraph.Noun{
|
||||
Name: r.Id(),
|
||||
Meta: &GraphNodeResource{
|
||||
Type: r.Type,
|
||||
Config: r,
|
||||
Type: r.Type,
|
||||
Config: r,
|
||||
Resource: new(Resource),
|
||||
},
|
||||
}
|
||||
nouns[noun.Name] = noun
|
||||
@ -196,12 +198,9 @@ func graphAddVariableDeps(g *depgraph.Graph) {
|
||||
}
|
||||
|
||||
// Find the target
|
||||
var target *depgraph.Noun
|
||||
for _, n := range g.Nouns {
|
||||
if n.Name == rv.ResourceId() {
|
||||
target = n
|
||||
break
|
||||
}
|
||||
target := g.Noun(rv.ResourceId())
|
||||
if target == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Build the dependency
|
||||
@ -215,3 +214,149 @@ func graphAddVariableDeps(g *depgraph.Graph) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// graphInitResourceProviders maps the resource providers onto the graph
|
||||
// given a mapping of prefixes to resource providers.
|
||||
//
|
||||
// Unlike the graphAdd* functions, this one can return an error if resource
|
||||
// providers can't be found or can't be instantiated.
|
||||
func graphInitResourceProviders(
|
||||
g *depgraph.Graph,
|
||||
ps map[string]ResourceProviderFactory) error {
|
||||
var errs []error
|
||||
|
||||
// Keep track of providers we know we couldn't instantiate so
|
||||
// that we don't get a ton of errors about the same provider.
|
||||
failures := make(map[string]struct{})
|
||||
|
||||
for _, n := range g.Nouns {
|
||||
// We only care about the resource providers first. There is guaranteed
|
||||
// to be only one node per tuple (providerId, providerConfig), which
|
||||
// means we don't need to verify we have instantiated it before.
|
||||
rn, ok := n.Meta.(*GraphNodeResourceProvider)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
prefixes := matchingPrefixes(rn.ID, ps)
|
||||
if len(prefixes) > 0 {
|
||||
if _, ok := failures[prefixes[0]]; ok {
|
||||
// We already failed this provider, meaning this
|
||||
// resource will never succeed, so just continue.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Go through each prefix and instantiate if necessary, then
|
||||
// verify if this provider is of use to us or not.
|
||||
for _, prefix := range prefixes {
|
||||
p, err := ps[prefix]()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Error instantiating resource provider for "+
|
||||
"prefix %s: %s", prefix, err))
|
||||
|
||||
// Record the error so that we don't check it again
|
||||
failures[prefix] = struct{}{}
|
||||
|
||||
// Jump to the next prefix
|
||||
continue
|
||||
}
|
||||
|
||||
rn.Providers = append(rn.Providers, p)
|
||||
}
|
||||
|
||||
// If we never found a provider, then error and continue
|
||||
if len(rn.Providers) == 0 {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Provider for configuration '%s' not found.",
|
||||
rn.ID))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &MultiError{Errors: errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// graphMapResourceProviders takes a graph that already has initialized
|
||||
// the resource providers (using graphInitResourceProviders) and maps the
|
||||
// resource providers to the resources themselves.
|
||||
func graphMapResourceProviders(g *depgraph.Graph) error {
|
||||
var errs []error
|
||||
|
||||
// First build a mapping of resource provider ID to the node that
|
||||
// contains those resources.
|
||||
mapping := make(map[string]*GraphNodeResourceProvider)
|
||||
for _, n := range g.Nouns {
|
||||
rn, ok := n.Meta.(*GraphNodeResourceProvider)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
mapping[rn.ID] = rn
|
||||
}
|
||||
|
||||
// Now go through each of the resources and find a matching provider.
|
||||
for _, n := range g.Nouns {
|
||||
rn, ok := n.Meta.(*GraphNodeResource)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
rpn, ok := mapping[rn.ResourceProviderID]
|
||||
if !ok {
|
||||
// This should never happen since when building the graph
|
||||
// we ensure that everything matches up.
|
||||
panic(fmt.Sprintf(
|
||||
"Resource provider ID not found: %s (type: %s)",
|
||||
rn.ResourceProviderID,
|
||||
rn.Type))
|
||||
}
|
||||
|
||||
var provider ResourceProvider
|
||||
for _, rp := range rpn.Providers {
|
||||
if ProviderSatisfies(rp, rn.Type) {
|
||||
provider = rp
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if provider == nil {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Resource provider not found for resource type '%s'",
|
||||
rn.Type))
|
||||
continue
|
||||
}
|
||||
|
||||
rn.Resource.Provider = provider
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &MultiError{Errors: errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchingPrefixes takes a resource type and a set of resource
|
||||
// providers we know about by prefix and returns a list of prefixes
|
||||
// that might be valid for that resource.
|
||||
//
|
||||
// The list returned is in the order that they should be attempted.
|
||||
func matchingPrefixes(
|
||||
t string,
|
||||
ps map[string]ResourceProviderFactory) []string {
|
||||
result := make([]string, 0, 1)
|
||||
for prefix, _ := range ps {
|
||||
if strings.HasPrefix(t, prefix) {
|
||||
result = append(result, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(mitchellh): Order by longest prefix first
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTerraformGraph(t *testing.T) {
|
||||
func TestGraph(t *testing.T) {
|
||||
config := testConfig(t, "graph-basic")
|
||||
|
||||
g := Graph(config, nil)
|
||||
@ -20,7 +20,7 @@ func TestTerraformGraph(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformGraph_cycle(t *testing.T) {
|
||||
func TestGraph_cycle(t *testing.T) {
|
||||
config := testConfig(t, "graph-cycle")
|
||||
|
||||
g := Graph(config, nil)
|
||||
@ -29,7 +29,7 @@ func TestTerraformGraph_cycle(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformGraph_state(t *testing.T) {
|
||||
func TestGraph_state(t *testing.T) {
|
||||
config := testConfig(t, "graph-basic")
|
||||
state := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
|
@ -8,9 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestReadWritePlan(t *testing.T) {
|
||||
tf := testTerraform(t, "new-good")
|
||||
plan := &Plan{
|
||||
Config: tf.config,
|
||||
Config: testConfig(t, "new-good"),
|
||||
Diff: &Diff{
|
||||
Resources: map[string]*ResourceDiff{
|
||||
"nodeA": &ResourceDiff{
|
||||
|
@ -2,7 +2,6 @@ package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
@ -154,23 +153,3 @@ func smcVariables(c *Config) []error {
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// matchingPrefixes takes a resource type and a set of resource
|
||||
// providers we know about by prefix and returns a list of prefixes
|
||||
// that might be valid for that resource.
|
||||
//
|
||||
// The list returned is in the order that they should be attempted.
|
||||
func matchingPrefixes(
|
||||
t string,
|
||||
ps map[string]ResourceProviderFactory) []string {
|
||||
result := make([]string, 0, 1)
|
||||
for prefix, _ := range ps {
|
||||
if strings.HasPrefix(t, prefix) {
|
||||
result = append(result, prefix)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(mitchellh): Order by longest prefix first
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -12,8 +12,7 @@ import (
|
||||
// Terraform from code, and can perform operations such as returning
|
||||
// all resources, a resource tree, a specific resource, etc.
|
||||
type Terraform struct {
|
||||
config *config.Config
|
||||
mapping map[*config.Resource]*terraformProvider
|
||||
providers map[string]ResourceProviderFactory
|
||||
variables map[string]string
|
||||
}
|
||||
|
||||
@ -46,7 +45,6 @@ type Config struct {
|
||||
// can be properly initialized, can be configured, etc.
|
||||
func New(c *Config) (*Terraform, error) {
|
||||
var errs []error
|
||||
var mapping map[*config.Resource]*terraformProvider
|
||||
|
||||
if c.Config != nil {
|
||||
// Validate that all required variables have values
|
||||
@ -86,8 +84,7 @@ func New(c *Config) (*Terraform, error) {
|
||||
}
|
||||
|
||||
return &Terraform{
|
||||
config: c.Config,
|
||||
mapping: mapping,
|
||||
providers: c.Providers,
|
||||
variables: c.Variables,
|
||||
}, nil
|
||||
}
|
||||
@ -115,8 +112,15 @@ func (t *Terraform) Graph(c *config.Config, s *State) (*depgraph.Graph, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Next, we want to go through the graph and make sure that we
|
||||
// map a provider to each of the resources.
|
||||
// Initialize all the providers
|
||||
if err := graphInitResourceProviders(g, t.providers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Map the providers to resources
|
||||
if err := graphMapResourceProviders(g); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
@ -221,7 +225,7 @@ func (t *Terraform) planWalkFn(
|
||||
result.init()
|
||||
|
||||
// Write our configuration out
|
||||
result.Config = t.config
|
||||
//result.Config = t.config
|
||||
|
||||
// Copy the variables
|
||||
result.Vars = make(map[string]string)
|
||||
@ -280,7 +284,7 @@ func (t *Terraform) genericWalkFn(
|
||||
diff *Diff,
|
||||
invars map[string]string,
|
||||
cb genericWalkFunc) depgraph.WalkFunc {
|
||||
var l sync.Mutex
|
||||
//var l sync.Mutex
|
||||
|
||||
// Initialize the variables for application
|
||||
vars := make(map[string]string)
|
||||
@ -289,79 +293,81 @@ func (t *Terraform) genericWalkFn(
|
||||
}
|
||||
|
||||
return func(n *depgraph.Noun) error {
|
||||
// If it is the root node, ignore
|
||||
if n.Meta == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch n.Meta.(type) {
|
||||
case *config.ProviderConfig:
|
||||
// Ignore, we don't treat this any differently since we always
|
||||
// initialize the provider on first use and use a lock to make
|
||||
// sure we only do this once.
|
||||
return nil
|
||||
case *config.Resource:
|
||||
// Continue
|
||||
}
|
||||
|
||||
r := n.Meta.(*config.Resource)
|
||||
p := t.mapping[r]
|
||||
if p == nil {
|
||||
panic(fmt.Sprintf("No provider for resource: %s", r.Id()))
|
||||
}
|
||||
|
||||
// Initialize the provider if we haven't already
|
||||
if err := p.init(vars); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the resource state
|
||||
var rs *ResourceState
|
||||
if state != nil {
|
||||
rs = state.Resources[r.Id()]
|
||||
}
|
||||
|
||||
// Get the resource diff
|
||||
var rd *ResourceDiff
|
||||
if diff != nil {
|
||||
rd = diff.Resources[r.Id()]
|
||||
}
|
||||
|
||||
if len(vars) > 0 {
|
||||
if err := r.RawConfig.Interpolate(vars); err != nil {
|
||||
panic(fmt.Sprintf("Interpolate error: %s", err))
|
||||
/*
|
||||
// If it is the root node, ignore
|
||||
if n.Meta == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If we have no state, then create an empty state with the
|
||||
// type fulfilled at the least.
|
||||
if rs == nil {
|
||||
rs = new(ResourceState)
|
||||
}
|
||||
rs.Type = r.Type
|
||||
|
||||
// Call the callack
|
||||
newVars, err := cb(&Resource{
|
||||
Id: r.Id(),
|
||||
Config: NewResourceConfig(r.RawConfig),
|
||||
Diff: rd,
|
||||
Provider: p.Provider,
|
||||
State: rs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(newVars) > 0 {
|
||||
// Acquire a lock since this function is called in parallel
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
// Update variables
|
||||
for k, v := range newVars {
|
||||
vars[k] = v
|
||||
switch n.Meta.(type) {
|
||||
case *config.ProviderConfig:
|
||||
// Ignore, we don't treat this any differently since we always
|
||||
// initialize the provider on first use and use a lock to make
|
||||
// sure we only do this once.
|
||||
return nil
|
||||
case *config.Resource:
|
||||
// Continue
|
||||
}
|
||||
}
|
||||
|
||||
r := n.Meta.(*config.Resource)
|
||||
p := t.mapping[r]
|
||||
if p == nil {
|
||||
panic(fmt.Sprintf("No provider for resource: %s", r.Id()))
|
||||
}
|
||||
|
||||
// Initialize the provider if we haven't already
|
||||
if err := p.init(vars); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the resource state
|
||||
var rs *ResourceState
|
||||
if state != nil {
|
||||
rs = state.Resources[r.Id()]
|
||||
}
|
||||
|
||||
// Get the resource diff
|
||||
var rd *ResourceDiff
|
||||
if diff != nil {
|
||||
rd = diff.Resources[r.Id()]
|
||||
}
|
||||
|
||||
if len(vars) > 0 {
|
||||
if err := r.RawConfig.Interpolate(vars); err != nil {
|
||||
panic(fmt.Sprintf("Interpolate error: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
// If we have no state, then create an empty state with the
|
||||
// type fulfilled at the least.
|
||||
if rs == nil {
|
||||
rs = new(ResourceState)
|
||||
}
|
||||
rs.Type = r.Type
|
||||
|
||||
// Call the callack
|
||||
newVars, err := cb(&Resource{
|
||||
Id: r.Id(),
|
||||
Config: NewResourceConfig(r.RawConfig),
|
||||
Diff: rd,
|
||||
Provider: p.Provider,
|
||||
State: rs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(newVars) > 0 {
|
||||
// Acquire a lock since this function is called in parallel
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
// Update variables
|
||||
for k, v := range newVars {
|
||||
vars[k] = v
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -110,6 +110,46 @@ func TestTerraformApply_vars(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformGraph(t *testing.T) {
|
||||
rpAws := new(MockResourceProvider)
|
||||
rpOS := new(MockResourceProvider)
|
||||
|
||||
rpAws.ResourcesReturn = []ResourceType{
|
||||
ResourceType{Name: "aws_instance"},
|
||||
ResourceType{Name: "aws_load_balancer"},
|
||||
ResourceType{Name: "aws_security_group"},
|
||||
}
|
||||
rpOS.ResourcesReturn = []ResourceType{
|
||||
ResourceType{Name: "openstack_floating_ip"},
|
||||
}
|
||||
|
||||
tf := testTerraform2(t, &Config{
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(rpAws),
|
||||
"open": testProviderFuncFixed(rpOS),
|
||||
},
|
||||
})
|
||||
|
||||
c := testConfig(t, "graph-basic")
|
||||
|
||||
g, err := tf.Graph(c, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// A helper to help get us the provider for a resource.
|
||||
graphProvider := func(n string) ResourceProvider {
|
||||
return g.Noun(n).Meta.(*GraphNodeResource).Resource.Provider
|
||||
}
|
||||
|
||||
if graphProvider("aws_instance.web") != rpAws {
|
||||
t.Fatalf("bad: %#v", graphProvider("aws_instance.web"))
|
||||
}
|
||||
if graphProvider("openstack_floating_ip.random") != rpOS {
|
||||
t.Fatalf("bad: %#v", graphProvider("openstack_floating_ip.random"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan(t *testing.T) {
|
||||
tf := testTerraform(t, "plan-good")
|
||||
|
||||
@ -328,12 +368,20 @@ func testProviderFunc(n string, rs []string) ResourceProviderFactory {
|
||||
}
|
||||
}
|
||||
|
||||
func testProvider(tf *Terraform, n string) ResourceProvider {
|
||||
for r, tp := range tf.mapping {
|
||||
if r.Id() == n {
|
||||
return tp.Provider
|
||||
}
|
||||
func testProviderFuncFixed(rp ResourceProvider) ResourceProviderFactory {
|
||||
return func() (ResourceProvider, error) {
|
||||
return rp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func testProvider(tf *Terraform, n string) ResourceProvider {
|
||||
/*
|
||||
for r, tp := range tf.mapping {
|
||||
if r.Id() == n {
|
||||
return tp.Provider
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -343,23 +391,27 @@ func testProviderMock(p ResourceProvider) *MockResourceProvider {
|
||||
}
|
||||
|
||||
func testProviderConfig(tf *Terraform, n string) *config.ProviderConfig {
|
||||
for r, tp := range tf.mapping {
|
||||
if r.Id() == n {
|
||||
return tp.Config
|
||||
/*
|
||||
for r, tp := range tf.mapping {
|
||||
if r.Id() == n {
|
||||
return tp.Config
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testProviderName(t *testing.T, tf *Terraform, n string) string {
|
||||
var p ResourceProvider
|
||||
for r, tp := range tf.mapping {
|
||||
if r.Id() == n {
|
||||
p = tp.Provider
|
||||
break
|
||||
/*
|
||||
for r, tp := range tf.mapping {
|
||||
if r.Id() == n {
|
||||
p = tp.Provider
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
if p == nil {
|
||||
t.Fatalf("resource not found: %s", n)
|
||||
@ -406,11 +458,13 @@ func testTerraform2(t *testing.T, c *Config) *Terraform {
|
||||
}
|
||||
|
||||
func testTerraformProvider(tf *Terraform, n string) *terraformProvider {
|
||||
for r, tp := range tf.mapping {
|
||||
if r.Id() == n {
|
||||
return tp
|
||||
/*
|
||||
for r, tp := range tf.mapping {
|
||||
if r.Id() == n {
|
||||
return tp
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ provider "aws" {
|
||||
foo = "${openstack_floating_ip.random.value}"
|
||||
}
|
||||
|
||||
resource "openstack_floating_ip" "random" {}
|
||||
#resource "openstack_floating_ip" "random" {}
|
||||
|
||||
resource "aws_security_group" "firewall" {}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user