terraform: New happy path works decently well
This commit is contained in:
parent
0c1a341d90
commit
c9d8413431
|
@ -23,3 +23,13 @@ type ResourceType struct {
|
||||||
// ResourceProviderFactory is a function type that creates a new instance
|
// ResourceProviderFactory is a function type that creates a new instance
|
||||||
// of a resource provider.
|
// of a resource provider.
|
||||||
type ResourceProviderFactory func() (ResourceProvider, error)
|
type ResourceProviderFactory func() (ResourceProvider, error)
|
||||||
|
|
||||||
|
func ProviderSatisfies(p ResourceProvider, n string) bool {
|
||||||
|
for _, rt := range p.Resources() {
|
||||||
|
if rt.Name == n {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ package terraform
|
||||||
// MockResourceProvider implements ResourceProvider but mocks out all the
|
// MockResourceProvider implements ResourceProvider but mocks out all the
|
||||||
// calls for testing purposes.
|
// calls for testing purposes.
|
||||||
type MockResourceProvider struct {
|
type MockResourceProvider struct {
|
||||||
|
// Anything you want, in case you need to store extra data with the mock.
|
||||||
|
Meta interface{}
|
||||||
|
|
||||||
ConfigureCalled bool
|
ConfigureCalled bool
|
||||||
ConfigureConfig map[string]interface{}
|
ConfigureConfig map[string]interface{}
|
||||||
ConfigureReturnWarnings []string
|
ConfigureReturnWarnings []string
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,7 +12,7 @@ import (
|
||||||
// all resources, a resource tree, a specific resource, etc.
|
// all resources, a resource tree, a specific resource, etc.
|
||||||
type Terraform struct {
|
type Terraform struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
providers []ResourceProvider
|
mapping map[*config.Resource]ResourceProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config is the configuration that must be given to instantiate
|
// Config is the configuration that must be given to instantiate
|
||||||
|
@ -27,7 +30,56 @@ type Config struct {
|
||||||
// time, as well as richer checks such as verifying that the resource providers
|
// time, as well as richer checks such as verifying that the resource providers
|
||||||
// can be properly initialized, can be configured, etc.
|
// can be properly initialized, can be configured, etc.
|
||||||
func New(c *Config) (*Terraform, error) {
|
func New(c *Config) (*Terraform, error) {
|
||||||
return nil, nil
|
// Go through each resource and match it up to a provider
|
||||||
|
mapping := make(map[*config.Resource]ResourceProvider)
|
||||||
|
providers := make(map[string]ResourceProvider)
|
||||||
|
for _, r := range c.Config.Resources {
|
||||||
|
// Find the prefixes that match this in the order of
|
||||||
|
// longest matching first (most specific)
|
||||||
|
prefixes := matchingPrefixes(r.Type, c.Providers)
|
||||||
|
|
||||||
|
// Go through each prefix and instantiate if necessary, then
|
||||||
|
// verify if this provider is of use to us or not.
|
||||||
|
var provider ResourceProvider = nil
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
p, ok := providers[prefix]
|
||||||
|
if !ok {
|
||||||
|
var err error
|
||||||
|
p, err = c.Providers[prefix]()
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"Error instantiating resource provider for "+
|
||||||
|
"prefix %s: %s", prefix, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
providers[prefix] = p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if this provider matches what we need
|
||||||
|
if !ProviderSatisfies(p, r.Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// A match! Set it and break
|
||||||
|
provider = p
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if provider == nil {
|
||||||
|
// We never found a matching provider.
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Provider for resource %s not found.",
|
||||||
|
r.Id())
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping[r] = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Terraform{
|
||||||
|
config: c.Config,
|
||||||
|
mapping: mapping,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terraform) Apply(*State, *Diff) (*State, error) {
|
func (t *Terraform) Apply(*State, *Diff) (*State, error) {
|
||||||
|
@ -41,3 +93,23 @@ func (t *Terraform) Diff(*State) (*Diff, error) {
|
||||||
func (t *Terraform) Refresh(*State) (*State, error) {
|
func (t *Terraform) Refresh(*State) (*State, error) {
|
||||||
return nil, nil
|
return nil, 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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is the directory where our test fixtures are.
|
||||||
|
const fixtureDir = "./test-fixtures"
|
||||||
|
|
||||||
|
func testConfig(t *testing.T, name string) *config.Config {
|
||||||
|
c, err := config.Load(filepath.Join(fixtureDir, name, "main.tf"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func testProviderFunc(n string, rs []string) ResourceProviderFactory {
|
||||||
|
resources := make([]ResourceType, len(rs))
|
||||||
|
for i, v := range rs {
|
||||||
|
resources[i] = ResourceType{
|
||||||
|
Name: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() (ResourceProvider, error) {
|
||||||
|
result := &MockResourceProvider{
|
||||||
|
Meta: n,
|
||||||
|
ResourcesReturn: resources,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testProviderName(p ResourceProvider) string {
|
||||||
|
return p.(*MockResourceProvider).Meta.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testResourceMapping(tf *Terraform) map[string]ResourceProvider {
|
||||||
|
result := make(map[string]ResourceProvider)
|
||||||
|
for resource, provider := range tf.mapping {
|
||||||
|
result[resource.Id()] = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
config := testConfig(t, "new-good")
|
||||||
|
tfConfig := &Config{
|
||||||
|
Config: config,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFunc("aws", []string{"aws_instance"}),
|
||||||
|
"do": testProviderFunc("do", []string{"do_droplet"}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tf, err := New(tfConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if tf == nil {
|
||||||
|
t.Fatal("tf should not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping := testResourceMapping(tf)
|
||||||
|
if len(mapping) != 2 {
|
||||||
|
t.Fatalf("bad: %#v", mapping)
|
||||||
|
}
|
||||||
|
if testProviderName(mapping["aws_instance.foo"]) != "aws" {
|
||||||
|
t.Fatalf("bad: %#v", mapping)
|
||||||
|
}
|
||||||
|
if testProviderName(mapping["do_droplet.bar"]) != "do" {
|
||||||
|
t.Fatalf("bad: %#v", mapping)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
resource "aws_instance" "foo" {}
|
||||||
|
resource "do_droplet" "bar" {}
|
Loading…
Reference in New Issue