2014-05-28 15:56:43 -05:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
2014-06-03 17:08:00 -05:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
2014-06-05 04:32:10 -05:00
|
|
|
"sync"
|
2014-06-03 17:08:00 -05:00
|
|
|
|
2014-05-28 15:56:43 -05:00
|
|
|
"github.com/hashicorp/terraform/config"
|
2014-06-03 19:14:19 -05:00
|
|
|
"github.com/hashicorp/terraform/depgraph"
|
2014-05-28 15:56:43 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// Terraform is the primary structure that is used to interact with
|
|
|
|
// Terraform from code, and can perform operations such as returning
|
|
|
|
// all resources, a resource tree, a specific resource, etc.
|
|
|
|
type Terraform struct {
|
2014-06-03 17:58:24 -05:00
|
|
|
config *config.Config
|
2014-06-03 19:14:19 -05:00
|
|
|
graph *depgraph.Graph
|
2014-06-03 17:58:24 -05:00
|
|
|
mapping map[*config.Resource]ResourceProvider
|
|
|
|
variables map[string]string
|
2014-05-28 15:56:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Config is the configuration that must be given to instantiate
|
|
|
|
// a Terraform structure.
|
|
|
|
type Config struct {
|
|
|
|
Config *config.Config
|
|
|
|
Providers map[string]ResourceProviderFactory
|
|
|
|
Variables map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new Terraform structure, initializes resource providers
|
|
|
|
// for the given configuration, etc.
|
|
|
|
//
|
|
|
|
// Semantic checks of the entire configuration structure are done at this
|
|
|
|
// time, as well as richer checks such as verifying that the resource providers
|
|
|
|
// can be properly initialized, can be configured, etc.
|
|
|
|
func New(c *Config) (*Terraform, error) {
|
2014-06-03 18:11:02 -05:00
|
|
|
var errs []error
|
|
|
|
|
2014-06-03 17:56:43 -05:00
|
|
|
// Validate that all required variables have values
|
|
|
|
required := make(map[string]struct{})
|
|
|
|
for k, v := range c.Config.Variables {
|
|
|
|
if v.Required() {
|
|
|
|
required[k] = struct{}{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for k, _ := range c.Variables {
|
|
|
|
delete(required, k)
|
|
|
|
}
|
|
|
|
if len(required) > 0 {
|
|
|
|
for k, _ := range required {
|
|
|
|
errs = append(errs, fmt.Errorf(
|
|
|
|
"Required variable not set: %s", k))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-03 18:12:35 -05:00
|
|
|
// TODO(mitchellh): variables that are unknown
|
|
|
|
|
2014-06-03 17:08:00 -05:00
|
|
|
// 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.
|
2014-06-03 18:11:02 -05:00
|
|
|
errs = append(errs, fmt.Errorf(
|
2014-06-03 17:08:00 -05:00
|
|
|
"Provider for resource %s not found.",
|
2014-06-03 18:11:02 -05:00
|
|
|
r.Id()))
|
2014-06-03 17:08:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
mapping[r] = provider
|
|
|
|
}
|
|
|
|
|
2014-06-03 19:14:19 -05:00
|
|
|
// Build the resource graph
|
|
|
|
graph := c.Config.ResourceGraph()
|
|
|
|
if err := graph.Validate(); err != nil {
|
|
|
|
errs = append(errs, fmt.Errorf(
|
|
|
|
"Resource graph has an error: %s", err))
|
|
|
|
}
|
|
|
|
|
2014-06-03 18:11:02 -05:00
|
|
|
// If we accumulated any errors, then return them all
|
|
|
|
if len(errs) > 0 {
|
|
|
|
return nil, &MultiError{Errors: errs}
|
|
|
|
}
|
|
|
|
|
2014-06-03 17:08:00 -05:00
|
|
|
return &Terraform{
|
2014-06-03 17:58:24 -05:00
|
|
|
config: c.Config,
|
2014-06-03 19:14:19 -05:00
|
|
|
graph: graph,
|
2014-06-03 17:58:24 -05:00
|
|
|
mapping: mapping,
|
|
|
|
variables: c.Variables,
|
2014-06-03 17:08:00 -05:00
|
|
|
}, nil
|
2014-05-28 15:56:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terraform) Apply(*State, *Diff) (*State, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2014-06-05 04:32:10 -05:00
|
|
|
func (t *Terraform) Diff(s *State) (*Diff, error) {
|
|
|
|
result := new(Diff)
|
|
|
|
err := t.graph.Walk(t.diffWalkFn(s, result))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
2014-05-28 15:56:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *Terraform) Refresh(*State) (*State, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2014-06-03 17:08:00 -05:00
|
|
|
|
2014-06-05 04:32:10 -05:00
|
|
|
func (t *Terraform) diffWalkFn(
|
|
|
|
state *State, result *Diff) depgraph.WalkFunc {
|
2014-06-05 09:27:01 -05:00
|
|
|
var l sync.RWMutex
|
|
|
|
|
|
|
|
// Initialize the result diff so we can write to it
|
|
|
|
result.init()
|
|
|
|
|
|
|
|
// This is the value that will be used for computed properties
|
|
|
|
computedId := "computed"
|
2014-06-05 04:32:10 -05:00
|
|
|
|
2014-06-05 13:08:27 -05:00
|
|
|
// Initialize the variables for application
|
|
|
|
vars := make(map[string]string)
|
|
|
|
for k, v := range t.variables {
|
|
|
|
vars[k] = v
|
|
|
|
}
|
|
|
|
|
2014-06-05 04:32:10 -05:00
|
|
|
return func(n *depgraph.Noun) error {
|
|
|
|
// If it is the root node, ignore
|
|
|
|
if n.Name == config.ResourceGraphRoot {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
r := n.Meta.(*config.Resource)
|
|
|
|
p := t.mapping[r]
|
|
|
|
if p == nil {
|
|
|
|
panic(fmt.Sprintf("No provider for resource: %s", r.Id()))
|
|
|
|
}
|
|
|
|
|
2014-06-05 09:27:01 -05:00
|
|
|
l.RLock()
|
2014-06-05 13:12:10 -05:00
|
|
|
var rs *ResourceState
|
|
|
|
if state != nil {
|
|
|
|
rs = state.resources[r.Id()]
|
|
|
|
}
|
2014-06-05 13:08:27 -05:00
|
|
|
if len(vars) > 0 {
|
|
|
|
r = r.ReplaceVariables(vars)
|
2014-06-05 09:27:01 -05:00
|
|
|
}
|
|
|
|
l.RUnlock()
|
|
|
|
|
2014-06-05 04:32:10 -05:00
|
|
|
diff, err := p.Diff(rs, r.Config)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there were no diff items, return right away
|
|
|
|
if len(diff.Attributes) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-06-05 09:27:01 -05:00
|
|
|
// Acquire a lock since this function is called in parallel
|
|
|
|
l.Lock()
|
|
|
|
defer l.Unlock()
|
|
|
|
|
|
|
|
// Update the resulting diff
|
2014-06-05 04:32:10 -05:00
|
|
|
result.Resources[r.Id()] = diff.Attributes
|
|
|
|
|
2014-06-05 13:08:27 -05:00
|
|
|
// Determine the new state and update variables
|
|
|
|
rs = rs.MergeDiff(diff.Attributes, computedId)
|
|
|
|
for ak, av := range rs.Attributes {
|
|
|
|
vars[fmt.Sprintf("%s.%s", r.Id(), ak)] = av
|
|
|
|
}
|
2014-06-05 09:27:01 -05:00
|
|
|
|
2014-06-05 04:32:10 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-03 17:08:00 -05:00
|
|
|
// 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
|
|
|
|
}
|