Merge pull request #12 from hashicorp/f-semantics
Richer Validation, Semantic Checks, Core Refactor
This commit is contained in:
commit
3decae513e
|
@ -4,6 +4,7 @@ import (
|
|||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/helper/multierror"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/goamz/ec2"
|
||||
)
|
||||
|
@ -18,6 +19,11 @@ func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []er
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) ValidateResource(
|
||||
t string, c *terraform.ResourceConfig) ([]string, []error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error {
|
||||
if _, err := config.Decode(&p.Config, c.Config); err != nil {
|
||||
return err
|
||||
|
@ -44,7 +50,7 @@ func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error {
|
|||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &terraform.MultiError{Errors: errs}
|
||||
return &multierror.Error{Errors: errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -109,18 +109,18 @@ func resource_aws_instance_diff(
|
|||
c *terraform.ResourceConfig,
|
||||
meta interface{}) (*terraform.ResourceDiff, error) {
|
||||
b := &diff.ResourceBuilder{
|
||||
CreateComputedAttrs: []string{
|
||||
Attrs: map[string]diff.AttrType{
|
||||
"ami": diff.AttrTypeCreate,
|
||||
"availability_zone": diff.AttrTypeCreate,
|
||||
"instance_type": diff.AttrTypeCreate,
|
||||
},
|
||||
|
||||
ComputedAttrs: []string{
|
||||
"public_dns",
|
||||
"public_ip",
|
||||
"private_dns",
|
||||
"private_ip",
|
||||
},
|
||||
|
||||
RequiresNewAttrs: []string{
|
||||
"ami",
|
||||
"availability_zone",
|
||||
"instance_type",
|
||||
},
|
||||
}
|
||||
|
||||
return b.Diff(s, c)
|
||||
|
|
|
@ -13,9 +13,9 @@ import (
|
|||
// ApplyCommand is a Command implementation that applies a Terraform
|
||||
// configuration and actually builds or changes infrastructure.
|
||||
type ApplyCommand struct {
|
||||
ShutdownCh <-chan struct{}
|
||||
TFConfig *terraform.Config
|
||||
Ui cli.Ui
|
||||
ShutdownCh <-chan struct{}
|
||||
ContextOpts *terraform.ContextOpts
|
||||
Ui cli.Ui
|
||||
}
|
||||
|
||||
func (c *ApplyCommand) Run(args []string) int {
|
||||
|
@ -44,30 +44,26 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
stateOutPath = statePath
|
||||
}
|
||||
|
||||
// Initialize Terraform right away
|
||||
c.TFConfig.Hooks = append(c.TFConfig.Hooks, &UiHook{Ui: c.Ui})
|
||||
tf, err := terraform.New(c.TFConfig)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing Terraform: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
// Attempt to read a plan from the path given. This is how we test that
|
||||
// it is a plan or not (kind of jank, but if it quacks like a duck...)
|
||||
planStatePath := statePath
|
||||
if init {
|
||||
planStatePath = ""
|
||||
}
|
||||
plan, err := PlanArg(configPath, planStatePath, tf)
|
||||
|
||||
// Initialize Terraform right away
|
||||
c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui})
|
||||
ctx, err := ContextArg(configPath, planStatePath, c.ContextOpts)
|
||||
if err != nil {
|
||||
c.Ui.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
if !validateContext(ctx, c.Ui) {
|
||||
return 1
|
||||
}
|
||||
|
||||
errCh := make(chan error)
|
||||
stateCh := make(chan *terraform.State)
|
||||
go func() {
|
||||
state, err := tf.Apply(plan)
|
||||
state, err := ctx.Apply()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
|
@ -83,7 +79,7 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
c.Ui.Output("Interrupt received. Gracefully shutting down...")
|
||||
|
||||
// Stop execution
|
||||
tf.Stop()
|
||||
ctx.Stop()
|
||||
|
||||
// Still get the result, since there is still one
|
||||
select {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
@ -16,8 +17,8 @@ func TestApply(t *testing.T) {
|
|||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &ApplyCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
args := []string{
|
||||
|
@ -48,15 +49,35 @@ func TestApply(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestApply_configInvalid(t *testing.T) {
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &ApplyCommand{
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-init",
|
||||
testTempFile(t),
|
||||
testFixturePath("apply-config-invalid"),
|
||||
}
|
||||
if code := c.Run(args); code != 1 {
|
||||
t.Fatalf("bad: \n%s", ui.OutputWriter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestApply_plan(t *testing.T) {
|
||||
planPath := testPlanFile(t, new(terraform.Plan))
|
||||
planPath := testPlanFile(t, &terraform.Plan{
|
||||
Config: new(config.Config),
|
||||
})
|
||||
statePath := testTempFile(t)
|
||||
|
||||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &ApplyCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
args := []string{
|
||||
|
@ -97,9 +118,9 @@ func TestApply_shutdown(t *testing.T) {
|
|||
shutdownCh := make(chan struct{})
|
||||
ui := new(cli.MockUi)
|
||||
c := &ApplyCommand{
|
||||
ShutdownCh: shutdownCh,
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
ShutdownCh: shutdownCh,
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
p.DiffFn = func(
|
||||
|
@ -197,8 +218,8 @@ func TestApply_state(t *testing.T) {
|
|||
|
||||
ui := new(cli.MockUi)
|
||||
c := &ApplyCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
// Run the apply command pointing to our existing state
|
||||
|
@ -244,8 +265,8 @@ func TestApply_stateNoExist(t *testing.T) {
|
|||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &ApplyCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
args := []string{
|
||||
|
|
|
@ -6,19 +6,20 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func PlanArg(
|
||||
func ContextArg(
|
||||
path string,
|
||||
statePath string,
|
||||
tf *terraform.Terraform) (*terraform.Plan, error) {
|
||||
opts *terraform.ContextOpts) (*terraform.Context, error) {
|
||||
// First try to just read the plan directly from the path given.
|
||||
f, err := os.Open(path)
|
||||
if err == nil {
|
||||
plan, err := terraform.ReadPlan(f)
|
||||
f.Close()
|
||||
if err == nil {
|
||||
return plan, nil
|
||||
return plan.Context(opts), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,14 +56,47 @@ func PlanArg(
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading config: %s", err)
|
||||
}
|
||||
if err := config.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("Error validating config: %s", err)
|
||||
}
|
||||
|
||||
plan, err := tf.Plan(&terraform.PlanOpts{
|
||||
Config: config,
|
||||
State: state,
|
||||
})
|
||||
if err != nil {
|
||||
opts.Config = config
|
||||
opts.State = state
|
||||
ctx := terraform.NewContext(opts)
|
||||
|
||||
if _, err := ctx.Plan(nil); err != nil {
|
||||
return nil, fmt.Errorf("Error running plan: %s", err)
|
||||
}
|
||||
|
||||
return plan, nil
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func validateContext(ctx *terraform.Context, ui cli.Ui) bool {
|
||||
if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
|
||||
ui.Output(
|
||||
"There are warnings and/or errors related to your configuration. Please\n" +
|
||||
"fix these before continuing.\n")
|
||||
|
||||
if len(ws) > 0 {
|
||||
ui.Output("Warnings:\n")
|
||||
for _, w := range ws {
|
||||
ui.Output(fmt.Sprintf(" * %s", w))
|
||||
}
|
||||
|
||||
if len(es) > 0 {
|
||||
ui.Output("")
|
||||
}
|
||||
}
|
||||
|
||||
if len(es) > 0 {
|
||||
ui.Output("Errors:\n")
|
||||
for _, e := range es {
|
||||
ui.Output(fmt.Sprintf(" * %s", e))
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ func testFixturePath(name string) string {
|
|||
return filepath.Join(fixtureDir, name, "main.tf")
|
||||
}
|
||||
|
||||
func testTFConfig(p terraform.ResourceProvider) *terraform.Config {
|
||||
return &terraform.Config{
|
||||
func testCtxConfig(p terraform.ResourceProvider) *terraform.ContextOpts {
|
||||
return &terraform.ContextOpts{
|
||||
Providers: map[string]terraform.ResourceProviderFactory{
|
||||
"test": func() (terraform.ResourceProvider, error) {
|
||||
return p, nil
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
// Config is a structure used to configure many commands with Terraform
|
||||
// configurations.
|
||||
type Config struct {
|
||||
Hooks []terraform.Hook
|
||||
Providers map[string]terraform.ResourceProviderFactory
|
||||
Ui cli.Ui
|
||||
}
|
||||
|
||||
func (c *Config) ContextOpts() *terraform.ContextOpts {
|
||||
hooks := make([]terraform.Hook, len(c.Hooks)+1)
|
||||
copy(hooks, c.Hooks)
|
||||
hooks[len(c.Hooks)] = &UiHook{Ui: c.Ui}
|
||||
|
||||
return &terraform.ContextOpts{
|
||||
Hooks: hooks,
|
||||
Providers: c.Providers,
|
||||
}
|
||||
}
|
|
@ -15,8 +15,8 @@ import (
|
|||
// GraphCommand is a Command implementation that takes a Terraform
|
||||
// configuration and outputs the dependency tree in graphical form.
|
||||
type GraphCommand struct {
|
||||
TFConfig *terraform.Config
|
||||
Ui cli.Ui
|
||||
ContextOpts *terraform.ContextOpts
|
||||
Ui cli.Ui
|
||||
}
|
||||
|
||||
func (c *GraphCommand) Run(args []string) int {
|
||||
|
@ -41,7 +41,7 @@ func (c *GraphCommand) Run(args []string) int {
|
|||
|
||||
g, err := terraform.Graph(&terraform.GraphOpts{
|
||||
Config: conf,
|
||||
Providers: c.TFConfig.Providers,
|
||||
Providers: c.ContextOpts.Providers,
|
||||
})
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error creating graph: %s", err))
|
||||
|
|
|
@ -15,8 +15,8 @@ import (
|
|||
// PlanCommand is a Command implementation that compares a Terraform
|
||||
// configuration to an actual infrastructure and shows the differences.
|
||||
type PlanCommand struct {
|
||||
TFConfig *terraform.Config
|
||||
Ui cli.Ui
|
||||
ContextOpts *terraform.ContextOpts
|
||||
Ui cli.Ui
|
||||
}
|
||||
|
||||
func (c *PlanCommand) Run(args []string) int {
|
||||
|
@ -65,26 +65,22 @@ func (c *PlanCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
c.TFConfig.Hooks = append(c.TFConfig.Hooks, &UiHook{Ui: c.Ui})
|
||||
tf, err := terraform.New(c.TFConfig)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing Terraform: %s", err))
|
||||
c.ContextOpts.Config = b
|
||||
c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui})
|
||||
c.ContextOpts.State = state
|
||||
ctx := terraform.NewContext(c.ContextOpts)
|
||||
if !validateContext(ctx, c.Ui) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if refresh {
|
||||
state, err = tf.Refresh(b, state)
|
||||
if err != nil {
|
||||
if _, err := ctx.Refresh(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
plan, err := tf.Plan(&terraform.PlanOpts{
|
||||
Config: b,
|
||||
Destroy: destroy,
|
||||
State: state,
|
||||
})
|
||||
plan, err := ctx.Plan(&terraform.PlanOpts{Destroy: destroy})
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error running plan: %s", err))
|
||||
return 1
|
||||
|
|
|
@ -26,8 +26,8 @@ func TestPlan_destroy(t *testing.T) {
|
|||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &PlanCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
args := []string{
|
||||
|
@ -51,8 +51,8 @@ func TestPlan_noState(t *testing.T) {
|
|||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &PlanCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
args := []string{
|
||||
|
@ -87,8 +87,8 @@ func TestPlan_outPath(t *testing.T) {
|
|||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &PlanCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
p.DiffReturn = &terraform.ResourceDiff{
|
||||
|
@ -118,8 +118,8 @@ func TestPlan_refresh(t *testing.T) {
|
|||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &PlanCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
args := []string{
|
||||
|
@ -162,8 +162,8 @@ func TestPlan_state(t *testing.T) {
|
|||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &PlanCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
args := []string{
|
||||
|
|
|
@ -15,8 +15,8 @@ import (
|
|||
// RefreshCommand is a cli.Command implementation that refreshes the state
|
||||
// file.
|
||||
type RefreshCommand struct {
|
||||
TFConfig *terraform.Config
|
||||
Ui cli.Ui
|
||||
ContextOpts *terraform.ContextOpts
|
||||
Ui cli.Ui
|
||||
}
|
||||
|
||||
func (c *RefreshCommand) Run(args []string) int {
|
||||
|
@ -66,14 +66,14 @@ func (c *RefreshCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
c.TFConfig.Hooks = append(c.TFConfig.Hooks, &UiHook{Ui: c.Ui})
|
||||
tf, err := terraform.New(c.TFConfig)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error initializing Terraform: %s", err))
|
||||
c.ContextOpts.Config = b
|
||||
c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui})
|
||||
ctx := terraform.NewContext(c.ContextOpts)
|
||||
if !validateContext(ctx, c.Ui) {
|
||||
return 1
|
||||
}
|
||||
|
||||
state, err = tf.Refresh(b, state)
|
||||
state, err = ctx.Refresh()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
|
||||
return 1
|
||||
|
|
|
@ -30,8 +30,8 @@ func TestRefresh(t *testing.T) {
|
|||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &RefreshCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
p.RefreshFn = nil
|
||||
|
@ -96,8 +96,8 @@ func TestRefresh_outPath(t *testing.T) {
|
|||
p := testProvider()
|
||||
ui := new(cli.MockUi)
|
||||
c := &RefreshCommand{
|
||||
TFConfig: testTFConfig(p),
|
||||
Ui: ui,
|
||||
ContextOpts: testCtxConfig(p),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
p.RefreshFn = nil
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
resource "test_instance" "foo" {
|
||||
ami = "${var.nope}"
|
||||
}
|
18
commands.go
18
commands.go
|
@ -29,30 +29,30 @@ func init() {
|
|||
Commands = map[string]cli.CommandFactory{
|
||||
"apply": func() (cli.Command, error) {
|
||||
return &command.ApplyCommand{
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
TFConfig: &TFConfig,
|
||||
Ui: Ui,
|
||||
ShutdownCh: makeShutdownCh(),
|
||||
ContextOpts: &ContextOpts,
|
||||
Ui: Ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"graph": func() (cli.Command, error) {
|
||||
return &command.GraphCommand{
|
||||
TFConfig: &TFConfig,
|
||||
Ui: Ui,
|
||||
ContextOpts: &ContextOpts,
|
||||
Ui: Ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"plan": func() (cli.Command, error) {
|
||||
return &command.PlanCommand{
|
||||
TFConfig: &TFConfig,
|
||||
Ui: Ui,
|
||||
ContextOpts: &ContextOpts,
|
||||
Ui: Ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
"refresh": func() (cli.Command, error) {
|
||||
return &command.RefreshCommand{
|
||||
TFConfig: &TFConfig,
|
||||
Ui: Ui,
|
||||
ContextOpts: &ContextOpts,
|
||||
Ui: Ui,
|
||||
}, nil
|
||||
},
|
||||
|
||||
|
|
|
@ -11,12 +11,6 @@ import (
|
|||
"github.com/mitchellh/osext"
|
||||
)
|
||||
|
||||
// TFConfig is the global base configuration that has the
|
||||
// basic providers registered. Users of this configuration
|
||||
// should copy it (call the Copy method) before using it so
|
||||
// that it isn't corrupted.
|
||||
var TFConfig terraform.Config
|
||||
|
||||
// Config is the structure of the configuration for the Terraform CLI.
|
||||
//
|
||||
// This is not the configuration for Terraform itself. That is in the
|
||||
|
@ -29,6 +23,9 @@ type Config struct {
|
|||
// can be overridden by user configurations.
|
||||
var BuiltinConfig Config
|
||||
|
||||
// ContextOpts are the global ContextOpts we use to initialize the CLI.
|
||||
var ContextOpts terraform.ContextOpts
|
||||
|
||||
// Put the parse flags we use for libucl in a constant so we can get
|
||||
// equally behaving parsing everywhere.
|
||||
const libuclParseFlags = libucl.ParserKeyLowercase
|
||||
|
|
|
@ -5,6 +5,8 @@ package config
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/multierror"
|
||||
)
|
||||
|
||||
// Config is the configuration that comes from loading a collection
|
||||
|
@ -89,12 +91,76 @@ func (r *Resource) Id() string {
|
|||
|
||||
// Validate does some basic semantic checking of the configuration.
|
||||
func (c *Config) Validate() error {
|
||||
// TODO(mitchellh): make sure all referenced variables exist
|
||||
// TODO(mitchellh): make sure types/names have valid values (characters)
|
||||
var errs []error
|
||||
|
||||
vars := c.allVariables()
|
||||
|
||||
// Check for references to user variables that do not actually
|
||||
// exist and record those errors.
|
||||
for source, v := range vars {
|
||||
uv, ok := v.(*UserVariable)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := c.Variables[uv.Name]; !ok {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"%s: unknown variable referenced: %s",
|
||||
source,
|
||||
uv.Name))
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all references to resources are valid
|
||||
resources := make(map[string]struct{})
|
||||
for _, r := range c.Resources {
|
||||
resources[r.Id()] = struct{}{}
|
||||
}
|
||||
for source, v := range vars {
|
||||
rv, ok := v.(*ResourceVariable)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
id := fmt.Sprintf("%s.%s", rv.Type, rv.Name)
|
||||
if _, ok := resources[id]; !ok {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"%s: unknown resource '%s' referenced in variable %s",
|
||||
source,
|
||||
id,
|
||||
rv.FullKey()))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &multierror.Error{Errors: errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// allVariables is a helper that returns a mapping of all the interpolated
|
||||
// variables within the configuration. This is used to verify references
|
||||
// are valid in the Validate step.
|
||||
func (c *Config) allVariables() map[string]InterpolatedVariable {
|
||||
result := make(map[string]InterpolatedVariable)
|
||||
for n, pc := range c.ProviderConfigs {
|
||||
source := fmt.Sprintf("provider config '%s'", n)
|
||||
for _, v := range pc.RawConfig.Variables {
|
||||
result[source] = v
|
||||
}
|
||||
}
|
||||
|
||||
for _, rc := range c.Resources {
|
||||
source := fmt.Sprintf("resource '%s'", rc.Id())
|
||||
for _, v := range rc.RawConfig.Variables {
|
||||
result[source] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Required tests whether a variable is required or not.
|
||||
func (v *Variable) Required() bool {
|
||||
return !v.defaultSet
|
||||
|
|
|
@ -1,12 +1,34 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This is the directory where our test fixtures are.
|
||||
const fixtureDir = "./test-fixtures"
|
||||
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
c := testConfig(t, "validate-good")
|
||||
if err := c.Validate(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigValidate_unknownResourceVar(t *testing.T) {
|
||||
c := testConfig(t, "validate-unknown-resource-var")
|
||||
if err := c.Validate(); err == nil {
|
||||
t.Fatal("should not be valid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigValidate_unknownVar(t *testing.T) {
|
||||
c := testConfig(t, "validate-unknownvar")
|
||||
if err := c.Validate(); err == nil {
|
||||
t.Fatal("should not be valid")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewResourceVariable(t *testing.T) {
|
||||
v, err := NewResourceVariable("foo.bar.baz")
|
||||
if err != nil {
|
||||
|
@ -55,3 +77,12 @@ func TestProviderConfigName(t *testing.T) {
|
|||
t.Fatalf("bad: %s", n)
|
||||
}
|
||||
}
|
||||
|
||||
func testConfig(t *testing.T, name string) *Config {
|
||||
c, err := Load(filepath.Join(fixtureDir, name, "main.tf"))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
variable "foo" {
|
||||
default = "bar";
|
||||
description = "bar";
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
access_key = "foo";
|
||||
secret_key = "bar";
|
||||
}
|
||||
|
||||
provider "do" {
|
||||
api_key = "${var.foo}";
|
||||
}
|
||||
|
||||
resource "aws_security_group" "firewall" {
|
||||
}
|
||||
|
||||
resource aws_instance "web" {
|
||||
ami = "${var.foo}"
|
||||
security_groups = [
|
||||
"foo",
|
||||
"${aws_security_group.firewall.foo}"
|
||||
]
|
||||
|
||||
network_interface {
|
||||
device_index = 0
|
||||
description = "Main network interface"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
resource "aws_instance" "web" {
|
||||
}
|
||||
|
||||
resource "aws_instance" "db" {
|
||||
ami = "${aws_instance.loadbalancer.foo}"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
variable "foo" {
|
||||
default = "bar";
|
||||
description = "bar";
|
||||
}
|
||||
|
||||
provider "do" {
|
||||
api_key = "${var.bar}";
|
||||
}
|
|
@ -1,18 +1,18 @@
|
|||
package terraform
|
||||
package multierror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MultiError is an error type to track multiple errors. This is used to
|
||||
// Error is an error type to track multiple errors. This is used to
|
||||
// accumulate errors in cases such as configuration parsing, and returning
|
||||
// them as a single error.
|
||||
type MultiError struct {
|
||||
type Error struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (e *MultiError) Error() string {
|
||||
func (e *Error) Error() string {
|
||||
points := make([]string, len(e.Errors))
|
||||
for i, err := range e.Errors {
|
||||
points[i] = fmt.Sprintf("* %s", err)
|
||||
|
@ -23,18 +23,18 @@ func (e *MultiError) Error() string {
|
|||
len(e.Errors), strings.Join(points, "\n"))
|
||||
}
|
||||
|
||||
// MultiErrorAppend is a helper function that will append more errors
|
||||
// onto a MultiError in order to create a larger multi-error. If the
|
||||
// original error is not a MultiError, it will be turned into one.
|
||||
func MultiErrorAppend(err error, errs ...error) *MultiError {
|
||||
// ErrorAppend is a helper function that will append more errors
|
||||
// onto a Error in order to create a larger multi-error. If the
|
||||
// original error is not a Error, it will be turned into one.
|
||||
func ErrorAppend(err error, errs ...error) *Error {
|
||||
if err == nil {
|
||||
err = new(MultiError)
|
||||
err = new(Error)
|
||||
}
|
||||
|
||||
switch err := err.(type) {
|
||||
case *MultiError:
|
||||
case *Error:
|
||||
if err == nil {
|
||||
err = new(MultiError)
|
||||
err = new(Error)
|
||||
}
|
||||
|
||||
err.Errors = append(err.Errors, errs...)
|
||||
|
@ -43,7 +43,7 @@ func MultiErrorAppend(err error, errs ...error) *MultiError {
|
|||
newErrs := make([]error, len(errs)+1)
|
||||
newErrs[0] = err
|
||||
copy(newErrs[1:], errs)
|
||||
return &MultiError{
|
||||
return &Error{
|
||||
Errors: newErrs,
|
||||
}
|
||||
}
|
|
@ -1,19 +1,19 @@
|
|||
package terraform
|
||||
package multierror
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMultiError_Impl(t *testing.T) {
|
||||
func TestError_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &MultiError{}
|
||||
raw = &Error{}
|
||||
if _, ok := raw.(error); !ok {
|
||||
t.Fatal("MultiError must implement error")
|
||||
t.Fatal("Error must implement error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiErrorError(t *testing.T) {
|
||||
func TestErrorError(t *testing.T) {
|
||||
expected := `2 error(s) occurred:
|
||||
|
||||
* foo
|
||||
|
@ -24,32 +24,32 @@ func TestMultiErrorError(t *testing.T) {
|
|||
errors.New("bar"),
|
||||
}
|
||||
|
||||
multi := &MultiError{errors}
|
||||
multi := &Error{errors}
|
||||
if multi.Error() != expected {
|
||||
t.Fatalf("bad: %s", multi.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiErrorAppend_MultiError(t *testing.T) {
|
||||
original := &MultiError{
|
||||
func TestErrorAppend_Error(t *testing.T) {
|
||||
original := &Error{
|
||||
Errors: []error{errors.New("foo")},
|
||||
}
|
||||
|
||||
result := MultiErrorAppend(original, errors.New("bar"))
|
||||
result := ErrorAppend(original, errors.New("bar"))
|
||||
if len(result.Errors) != 2 {
|
||||
t.Fatalf("wrong len: %d", len(result.Errors))
|
||||
}
|
||||
|
||||
original = &MultiError{}
|
||||
result = MultiErrorAppend(original, errors.New("bar"))
|
||||
original = &Error{}
|
||||
result = ErrorAppend(original, errors.New("bar"))
|
||||
if len(result.Errors) != 1 {
|
||||
t.Fatalf("wrong len: %d", len(result.Errors))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiErrorAppend_NonMultiError(t *testing.T) {
|
||||
func TestErrorAppend_NonError(t *testing.T) {
|
||||
original := errors.New("foo")
|
||||
result := MultiErrorAppend(original, errors.New("bar"))
|
||||
result := ErrorAppend(original, errors.New("bar"))
|
||||
if len(result.Errors) != 2 {
|
||||
t.Fatalf("wrong len: %d", len(result.Errors))
|
||||
}
|
2
main.go
2
main.go
|
@ -84,7 +84,7 @@ func wrappedMain() int {
|
|||
defer plugin.CleanupClients()
|
||||
|
||||
// Initialize the TFConfig settings for the commands...
|
||||
TFConfig.Providers = config.ProviderFactories()
|
||||
ContextOpts.Providers = config.ProviderFactories()
|
||||
|
||||
// Get the command line args. We shortcut "--version" and "-v" to
|
||||
// just show the version.
|
||||
|
|
|
@ -35,6 +35,30 @@ func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []er
|
|||
return resp.Warnings, errs
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) ValidateResource(
|
||||
t string, c *terraform.ResourceConfig) ([]string, []error) {
|
||||
var resp ResourceProviderValidateResourceResponse
|
||||
args := ResourceProviderValidateResourceArgs{
|
||||
Config: c,
|
||||
Type: t,
|
||||
}
|
||||
|
||||
err := p.Client.Call(p.Name+".ValidateResource", &args, &resp)
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
var errs []error
|
||||
if len(resp.Errors) > 0 {
|
||||
errs = make([]error, len(resp.Errors))
|
||||
for i, err := range resp.Errors {
|
||||
errs[i] = err
|
||||
}
|
||||
}
|
||||
|
||||
return resp.Warnings, errs
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error {
|
||||
var resp ResourceProviderConfigureResponse
|
||||
err := p.Client.Call(p.Name+".Configure", c, &resp)
|
||||
|
@ -157,6 +181,16 @@ type ResourceProviderValidateResponse struct {
|
|||
Errors []*BasicError
|
||||
}
|
||||
|
||||
type ResourceProviderValidateResourceArgs struct {
|
||||
Config *terraform.ResourceConfig
|
||||
Type string
|
||||
}
|
||||
|
||||
type ResourceProviderValidateResourceResponse struct {
|
||||
Warnings []string
|
||||
Errors []*BasicError
|
||||
}
|
||||
|
||||
func (s *ResourceProviderServer) Validate(
|
||||
args *ResourceProviderValidateArgs,
|
||||
reply *ResourceProviderValidateResponse) error {
|
||||
|
@ -172,6 +206,21 @@ func (s *ResourceProviderServer) Validate(
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *ResourceProviderServer) ValidateResource(
|
||||
args *ResourceProviderValidateResourceArgs,
|
||||
reply *ResourceProviderValidateResourceResponse) error {
|
||||
warns, errs := s.Provider.ValidateResource(args.Type, args.Config)
|
||||
berrs := make([]*BasicError, len(errs))
|
||||
for i, err := range errs {
|
||||
berrs[i] = NewBasicError(err)
|
||||
}
|
||||
*reply = ResourceProviderValidateResourceResponse{
|
||||
Warnings: warns,
|
||||
Errors: berrs,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ResourceProviderServer) Configure(
|
||||
config *terraform.ResourceConfig,
|
||||
reply *ResourceProviderConfigureResponse) error {
|
||||
|
|
|
@ -341,3 +341,106 @@ func TestResourceProvider_validate_warns(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_validateResource(t *testing.T) {
|
||||
p := new(terraform.MockResourceProvider)
|
||||
client, server := testClientServer(t)
|
||||
name, err := Register(server, p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
provider := &ResourceProvider{Client: client, Name: name}
|
||||
|
||||
// Configure
|
||||
config := &terraform.ResourceConfig{
|
||||
Raw: map[string]interface{}{"foo": "bar"},
|
||||
}
|
||||
w, e := provider.ValidateResource("foo", config)
|
||||
if !p.ValidateResourceCalled {
|
||||
t.Fatal("configure should be called")
|
||||
}
|
||||
if p.ValidateResourceType != "foo" {
|
||||
t.Fatalf("bad: %#v", p.ValidateResourceType)
|
||||
}
|
||||
if !reflect.DeepEqual(p.ValidateResourceConfig, config) {
|
||||
t.Fatalf("bad: %#v", p.ValidateResourceConfig)
|
||||
}
|
||||
if w != nil {
|
||||
t.Fatalf("bad: %#v", w)
|
||||
}
|
||||
if e != nil {
|
||||
t.Fatalf("bad: %#v", e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_validateResource_errors(t *testing.T) {
|
||||
p := new(terraform.MockResourceProvider)
|
||||
p.ValidateResourceReturnErrors = []error{errors.New("foo")}
|
||||
|
||||
client, server := testClientServer(t)
|
||||
name, err := Register(server, p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
provider := &ResourceProvider{Client: client, Name: name}
|
||||
|
||||
// Configure
|
||||
config := &terraform.ResourceConfig{
|
||||
Raw: map[string]interface{}{"foo": "bar"},
|
||||
}
|
||||
w, e := provider.ValidateResource("foo", config)
|
||||
if !p.ValidateResourceCalled {
|
||||
t.Fatal("configure should be called")
|
||||
}
|
||||
if p.ValidateResourceType != "foo" {
|
||||
t.Fatalf("bad: %#v", p.ValidateResourceType)
|
||||
}
|
||||
if !reflect.DeepEqual(p.ValidateResourceConfig, config) {
|
||||
t.Fatalf("bad: %#v", p.ValidateResourceConfig)
|
||||
}
|
||||
if w != nil {
|
||||
t.Fatalf("bad: %#v", w)
|
||||
}
|
||||
|
||||
if len(e) != 1 {
|
||||
t.Fatalf("bad: %#v", e)
|
||||
}
|
||||
if e[0].Error() != "foo" {
|
||||
t.Fatalf("bad: %#v", e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_validateResource_warns(t *testing.T) {
|
||||
p := new(terraform.MockResourceProvider)
|
||||
p.ValidateResourceReturnWarns = []string{"foo"}
|
||||
|
||||
client, server := testClientServer(t)
|
||||
name, err := Register(server, p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
provider := &ResourceProvider{Client: client, Name: name}
|
||||
|
||||
// Configure
|
||||
config := &terraform.ResourceConfig{
|
||||
Raw: map[string]interface{}{"foo": "bar"},
|
||||
}
|
||||
w, e := provider.ValidateResource("foo", config)
|
||||
if !p.ValidateResourceCalled {
|
||||
t.Fatal("configure should be called")
|
||||
}
|
||||
if p.ValidateResourceType != "foo" {
|
||||
t.Fatalf("bad: %#v", p.ValidateResourceType)
|
||||
}
|
||||
if !reflect.DeepEqual(p.ValidateResourceConfig, config) {
|
||||
t.Fatalf("bad: %#v", p.ValidateResourceConfig)
|
||||
}
|
||||
if e != nil {
|
||||
t.Fatalf("bad: %#v", e)
|
||||
}
|
||||
|
||||
expected := []string{"foo"}
|
||||
if !reflect.DeepEqual(w, expected) {
|
||||
t.Fatalf("bad: %#v", w)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,605 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/depgraph"
|
||||
"github.com/hashicorp/terraform/helper/multierror"
|
||||
)
|
||||
|
||||
// This is a function type used to implement a walker for the resource
|
||||
// tree internally on the Terraform structure.
|
||||
type genericWalkFunc func(*Resource) (map[string]string, error)
|
||||
|
||||
// Context represents all the context that Terraform needs in order to
|
||||
// perform operations on infrastructure. This structure is built using
|
||||
// ContextOpts and NewContext. See the documentation for those.
|
||||
//
|
||||
// Additionally, a context can be created from a Plan using Plan.Context.
|
||||
type Context struct {
|
||||
config *config.Config
|
||||
diff *Diff
|
||||
hooks []Hook
|
||||
state *State
|
||||
providers map[string]ResourceProviderFactory
|
||||
variables map[string]string
|
||||
|
||||
l sync.Mutex
|
||||
runCh <-chan struct{}
|
||||
sh *stopHook
|
||||
}
|
||||
|
||||
// ContextOpts are the user-creatable configuration structure to create
|
||||
// a context with NewContext.
|
||||
type ContextOpts struct {
|
||||
Config *config.Config
|
||||
Diff *Diff
|
||||
Hooks []Hook
|
||||
State *State
|
||||
Providers map[string]ResourceProviderFactory
|
||||
Variables map[string]string
|
||||
}
|
||||
|
||||
// NewContext creates a new context.
|
||||
//
|
||||
// Once a context is created, the pointer values within ContextOpts should
|
||||
// not be mutated in any way, since the pointers are copied, not the values
|
||||
// themselves.
|
||||
func NewContext(opts *ContextOpts) *Context {
|
||||
sh := new(stopHook)
|
||||
|
||||
// Copy all the hooks and add our stop hook. We don't append directly
|
||||
// to the Config so that we're not modifying that in-place.
|
||||
hooks := make([]Hook, len(opts.Hooks)+1)
|
||||
copy(hooks, opts.Hooks)
|
||||
hooks[len(opts.Hooks)] = sh
|
||||
|
||||
return &Context{
|
||||
config: opts.Config,
|
||||
diff: opts.Diff,
|
||||
hooks: hooks,
|
||||
state: opts.State,
|
||||
providers: opts.Providers,
|
||||
variables: opts.Variables,
|
||||
|
||||
sh: sh,
|
||||
}
|
||||
}
|
||||
|
||||
// Apply applies the changes represented by this context and returns
|
||||
// the resulting state.
|
||||
//
|
||||
// In addition to returning the resulting state, this context is updated
|
||||
// with the latest state.
|
||||
func (c *Context) Apply() (*State, error) {
|
||||
v := c.acquireRun()
|
||||
defer c.releaseRun(v)
|
||||
|
||||
g, err := Graph(&GraphOpts{
|
||||
Config: c.config,
|
||||
Diff: c.diff,
|
||||
Providers: c.providers,
|
||||
State: c.state,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create our result. Make sure we preserve the prior states
|
||||
s := new(State)
|
||||
s.init()
|
||||
if c.state != nil {
|
||||
for k, v := range c.state.Resources {
|
||||
s.Resources[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Walk
|
||||
err = g.Walk(c.applyWalkFn(s))
|
||||
|
||||
// Update our state, even if we have an error, for partial updates
|
||||
c.state = s
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// Plan generates an execution plan for the given context.
|
||||
//
|
||||
// The execution plan encapsulates the context and can be stored
|
||||
// in order to reinstantiate a context later for Apply.
|
||||
//
|
||||
// Plan also updates the diff of this context to be the diff generated
|
||||
// by the plan, so Apply can be called after.
|
||||
func (c *Context) Plan(opts *PlanOpts) (*Plan, error) {
|
||||
v := c.acquireRun()
|
||||
defer c.releaseRun(v)
|
||||
|
||||
g, err := Graph(&GraphOpts{
|
||||
Config: c.config,
|
||||
Providers: c.providers,
|
||||
State: c.state,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &Plan{
|
||||
Config: c.config,
|
||||
Vars: c.variables,
|
||||
State: c.state,
|
||||
}
|
||||
err = g.Walk(c.planWalkFn(p, opts))
|
||||
|
||||
// Update the diff so that our context is up-to-date
|
||||
c.diff = p.Diff
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
// Refresh goes through all the resources in the state and refreshes them
|
||||
// to their latest state. This will update the state that this context
|
||||
// works with, along with returning it.
|
||||
//
|
||||
// Even in the case an error is returned, the state will be returned and
|
||||
// will potentially be partially updated.
|
||||
func (c *Context) Refresh() (*State, error) {
|
||||
v := c.acquireRun()
|
||||
defer c.releaseRun(v)
|
||||
|
||||
g, err := Graph(&GraphOpts{
|
||||
Config: c.config,
|
||||
Providers: c.providers,
|
||||
State: c.state,
|
||||
})
|
||||
if err != nil {
|
||||
return c.state, err
|
||||
}
|
||||
|
||||
s := new(State)
|
||||
s.init()
|
||||
err = g.Walk(c.refreshWalkFn(s))
|
||||
|
||||
// Update our state
|
||||
c.state = s
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
// Stop stops the running task.
|
||||
//
|
||||
// Stop will block until the task completes.
|
||||
func (c *Context) Stop() {
|
||||
c.l.Lock()
|
||||
ch := c.runCh
|
||||
|
||||
// If we aren't running, then just return
|
||||
if ch == nil {
|
||||
c.l.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Tell the hook we want to stop
|
||||
c.sh.Stop()
|
||||
|
||||
// Wait for us to stop
|
||||
c.l.Unlock()
|
||||
<-ch
|
||||
}
|
||||
|
||||
// Validate validates the configuration and returns any warnings or errors.
|
||||
func (c *Context) Validate() ([]string, []error) {
|
||||
var rerr *multierror.Error
|
||||
|
||||
// Validate the configuration itself
|
||||
if err := c.config.Validate(); err != nil {
|
||||
rerr = multierror.ErrorAppend(rerr, err)
|
||||
}
|
||||
|
||||
// Validate the user variables
|
||||
if errs := smcUserVariables(c.config, c.variables); len(errs) > 0 {
|
||||
rerr = multierror.ErrorAppend(rerr, errs...)
|
||||
}
|
||||
|
||||
// Validate the graph
|
||||
g, err := c.graph()
|
||||
if err != nil {
|
||||
rerr = multierror.ErrorAppend(rerr, fmt.Errorf(
|
||||
"Error creating graph: %s", err))
|
||||
}
|
||||
|
||||
// Walk the graph and validate all the configs
|
||||
var warns []string
|
||||
var errs []error
|
||||
err = g.Walk(c.validateWalkFn(&warns, &errs))
|
||||
if err != nil {
|
||||
rerr = multierror.ErrorAppend(rerr, fmt.Errorf(
|
||||
"Error validating resources in graph: %s", err))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
rerr = multierror.ErrorAppend(rerr, errs...)
|
||||
}
|
||||
|
||||
errs = nil
|
||||
if rerr != nil && len(rerr.Errors) > 0 {
|
||||
errs = rerr.Errors
|
||||
}
|
||||
|
||||
return warns, errs
|
||||
}
|
||||
|
||||
func (c *Context) graph() (*depgraph.Graph, error) {
|
||||
return Graph(&GraphOpts{
|
||||
Config: c.config,
|
||||
Diff: c.diff,
|
||||
Providers: c.providers,
|
||||
State: c.state,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Context) acquireRun() chan<- struct{} {
|
||||
c.l.Lock()
|
||||
defer c.l.Unlock()
|
||||
|
||||
// Wait for no channel to exist
|
||||
for c.runCh != nil {
|
||||
c.l.Unlock()
|
||||
ch := c.runCh
|
||||
<-ch
|
||||
c.l.Lock()
|
||||
}
|
||||
|
||||
ch := make(chan struct{})
|
||||
c.runCh = ch
|
||||
return ch
|
||||
}
|
||||
|
||||
func (c *Context) releaseRun(ch chan<- struct{}) {
|
||||
c.l.Lock()
|
||||
defer c.l.Unlock()
|
||||
|
||||
close(ch)
|
||||
c.runCh = nil
|
||||
c.sh.Reset()
|
||||
}
|
||||
|
||||
func (c *Context) applyWalkFn(result *State) depgraph.WalkFunc {
|
||||
var l sync.Mutex
|
||||
|
||||
// Initialize the result
|
||||
result.init()
|
||||
|
||||
cb := func(r *Resource) (map[string]string, error) {
|
||||
diff := r.Diff
|
||||
if diff.Empty() {
|
||||
return r.Vars(), nil
|
||||
}
|
||||
|
||||
if !diff.Destroy {
|
||||
var err error
|
||||
diff, err = r.Provider.Diff(r.State, r.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(mitchellh): we need to verify the diff doesn't change
|
||||
// anything and that the diff has no computed values (pre-computed)
|
||||
|
||||
for _, h := range c.hooks {
|
||||
handleHook(h.PreApply(r.Id, r.State, diff))
|
||||
}
|
||||
|
||||
// With the completed diff, apply!
|
||||
log.Printf("[DEBUG] %s: Executing Apply", r.Id)
|
||||
rs, err := r.Provider.Apply(r.State, diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure the result is instantiated
|
||||
if rs == nil {
|
||||
rs = new(ResourceState)
|
||||
}
|
||||
|
||||
// Force the resource state type to be our type
|
||||
rs.Type = r.State.Type
|
||||
|
||||
var errs []error
|
||||
for ak, av := range rs.Attributes {
|
||||
// If the value is the unknown variable value, then it is an error.
|
||||
// In this case we record the error and remove it from the state
|
||||
if av == config.UnknownVariableValue {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Attribute with unknown value: %s", ak))
|
||||
delete(rs.Attributes, ak)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the resulting diff
|
||||
l.Lock()
|
||||
if rs.ID == "" {
|
||||
delete(result.Resources, r.Id)
|
||||
} else {
|
||||
result.Resources[r.Id] = rs
|
||||
}
|
||||
l.Unlock()
|
||||
|
||||
// Update the state for the resource itself
|
||||
r.State = rs
|
||||
|
||||
for _, h := range c.hooks {
|
||||
handleHook(h.PostApply(r.Id, r.State))
|
||||
}
|
||||
|
||||
// Determine the new state and update variables
|
||||
err = nil
|
||||
if len(errs) > 0 {
|
||||
err = &multierror.Error{Errors: errs}
|
||||
}
|
||||
|
||||
return r.Vars(), err
|
||||
}
|
||||
|
||||
return c.genericWalkFn(c.variables, cb)
|
||||
}
|
||||
|
||||
func (c *Context) planWalkFn(result *Plan, opts *PlanOpts) depgraph.WalkFunc {
|
||||
var l sync.Mutex
|
||||
|
||||
// If we were given nil options, instantiate it
|
||||
if opts == nil {
|
||||
opts = new(PlanOpts)
|
||||
}
|
||||
|
||||
// Initialize the result
|
||||
result.init()
|
||||
|
||||
cb := func(r *Resource) (map[string]string, error) {
|
||||
var diff *ResourceDiff
|
||||
|
||||
for _, h := range c.hooks {
|
||||
handleHook(h.PreDiff(r.Id, r.State))
|
||||
}
|
||||
|
||||
if opts.Destroy {
|
||||
if r.State.ID != "" {
|
||||
log.Printf("[DEBUG] %s: Making for destroy", r.Id)
|
||||
diff = &ResourceDiff{Destroy: true}
|
||||
} else {
|
||||
log.Printf("[DEBUG] %s: Not marking for destroy, no ID", r.Id)
|
||||
}
|
||||
} else if r.Config == nil {
|
||||
log.Printf("[DEBUG] %s: Orphan, marking for destroy", r.Id)
|
||||
|
||||
// This is an orphan (no config), so we mark it to be destroyed
|
||||
diff = &ResourceDiff{Destroy: true}
|
||||
} else {
|
||||
log.Printf("[DEBUG] %s: Executing diff", r.Id)
|
||||
|
||||
// Get a diff from the newest state
|
||||
var err error
|
||||
diff, err = r.Provider.Diff(r.State, r.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
if !diff.Empty() {
|
||||
result.Diff.Resources[r.Id] = diff
|
||||
}
|
||||
l.Unlock()
|
||||
|
||||
for _, h := range c.hooks {
|
||||
handleHook(h.PostDiff(r.Id, diff))
|
||||
}
|
||||
|
||||
// Determine the new state and update variables
|
||||
if !diff.Empty() {
|
||||
r.State = r.State.MergeDiff(diff)
|
||||
}
|
||||
|
||||
return r.Vars(), nil
|
||||
}
|
||||
|
||||
return c.genericWalkFn(c.variables, cb)
|
||||
}
|
||||
|
||||
func (c *Context) refreshWalkFn(result *State) depgraph.WalkFunc {
|
||||
var l sync.Mutex
|
||||
|
||||
cb := func(r *Resource) (map[string]string, error) {
|
||||
for _, h := range c.hooks {
|
||||
handleHook(h.PreRefresh(r.Id, r.State))
|
||||
}
|
||||
|
||||
rs, err := r.Provider.Refresh(r.State)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rs == nil {
|
||||
rs = new(ResourceState)
|
||||
}
|
||||
|
||||
// Fix the type to be the type we have
|
||||
rs.Type = r.State.Type
|
||||
|
||||
l.Lock()
|
||||
result.Resources[r.Id] = rs
|
||||
l.Unlock()
|
||||
|
||||
for _, h := range c.hooks {
|
||||
handleHook(h.PostRefresh(r.Id, rs))
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return c.genericWalkFn(c.variables, cb)
|
||||
}
|
||||
|
||||
func (c *Context) validateWalkFn(rws *[]string, res *[]error) depgraph.WalkFunc {
|
||||
return func(n *depgraph.Noun) error {
|
||||
// If it is the root node, ignore
|
||||
if n.Name == GraphRootNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch rn := n.Meta.(type) {
|
||||
case *GraphNodeResource:
|
||||
if rn.Resource == nil {
|
||||
panic("resource should never be nil")
|
||||
}
|
||||
|
||||
// If it doesn't have a provider, that is a different problem
|
||||
if rn.Resource.Provider == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Validating resource: %s", rn.Resource.Id)
|
||||
ws, es := rn.Resource.Provider.ValidateResource(
|
||||
rn.Type, rn.Resource.Config)
|
||||
for i, w := range ws {
|
||||
ws[i] = fmt.Sprintf("'%s' warning: %s", rn.Resource.Id, w)
|
||||
}
|
||||
for i, e := range es {
|
||||
es[i] = fmt.Errorf("'%s' error: %s", rn.Resource.Id, e)
|
||||
}
|
||||
|
||||
*rws = append(*rws, ws...)
|
||||
*res = append(*res, es...)
|
||||
case *GraphNodeResourceProvider:
|
||||
if rn.Config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rc := NewResourceConfig(rn.Config.RawConfig)
|
||||
|
||||
for k, p := range rn.Providers {
|
||||
log.Printf("[INFO] Validating provider: %s", k)
|
||||
ws, es := p.Validate(rc)
|
||||
for i, w := range ws {
|
||||
ws[i] = fmt.Sprintf("Provider '%s' warning: %s", k, w)
|
||||
}
|
||||
for i, e := range es {
|
||||
es[i] = fmt.Errorf("Provider '%s' error: %s", k, e)
|
||||
}
|
||||
|
||||
*rws = append(*rws, ws...)
|
||||
*res = append(*res, es...)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) genericWalkFn(
|
||||
invars map[string]string,
|
||||
cb genericWalkFunc) depgraph.WalkFunc {
|
||||
var l sync.RWMutex
|
||||
|
||||
// Initialize the variables for application
|
||||
vars := make(map[string]string)
|
||||
for k, v := range invars {
|
||||
vars[fmt.Sprintf("var.%s", k)] = v
|
||||
}
|
||||
|
||||
// This will keep track of whether we're stopped or not
|
||||
var stop uint32 = 0
|
||||
|
||||
return func(n *depgraph.Noun) error {
|
||||
// If it is the root node, ignore
|
||||
if n.Name == GraphRootNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we're stopped, return right away
|
||||
if atomic.LoadUint32(&stop) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch m := n.Meta.(type) {
|
||||
case *GraphNodeResource:
|
||||
case *GraphNodeResourceProvider:
|
||||
var rc *ResourceConfig
|
||||
if m.Config != nil {
|
||||
if err := m.Config.RawConfig.Interpolate(vars); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rc = NewResourceConfig(m.Config.RawConfig)
|
||||
}
|
||||
|
||||
for k, p := range m.Providers {
|
||||
log.Printf("[INFO] Configuring provider: %s", k)
|
||||
err := p.Configure(rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
rn := n.Meta.(*GraphNodeResource)
|
||||
|
||||
l.RLock()
|
||||
if len(vars) > 0 && rn.Config != nil {
|
||||
if err := rn.Config.RawConfig.Interpolate(vars); err != nil {
|
||||
panic(fmt.Sprintf("Interpolate error: %s", err))
|
||||
}
|
||||
|
||||
// Force the config to be set later
|
||||
rn.Resource.Config = nil
|
||||
}
|
||||
l.RUnlock()
|
||||
|
||||
// Make sure that at least some resource configuration is set
|
||||
if !rn.Orphan {
|
||||
if rn.Resource.Config == nil {
|
||||
if rn.Config == nil {
|
||||
rn.Resource.Config = new(ResourceConfig)
|
||||
} else {
|
||||
rn.Resource.Config = NewResourceConfig(rn.Config.RawConfig)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rn.Resource.Config = nil
|
||||
}
|
||||
|
||||
// Handle recovery of special panic scenarios
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
if v == HookActionHalt {
|
||||
atomic.StoreUint32(&stop, 1)
|
||||
} else {
|
||||
panic(v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Call the callack
|
||||
log.Printf("[INFO] Walking: %s", rn.Resource.Id)
|
||||
newVars, err := cb(rn.Resource)
|
||||
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
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/depgraph"
|
||||
"github.com/hashicorp/terraform/helper/multierror"
|
||||
)
|
||||
|
||||
// GraphOpts are options used to create the resource graph that Terraform
|
||||
|
@ -325,7 +326,7 @@ func graphAddMissingResourceProviders(
|
|||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &MultiError{Errors: errs}
|
||||
return &multierror.Error{Errors: errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -518,7 +519,7 @@ func graphInitResourceProviders(
|
|||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &MultiError{Errors: errs}
|
||||
return &multierror.Error{Errors: errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -583,7 +584,7 @@ func graphMapResourceProviders(g *depgraph.Graph) error {
|
|||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &MultiError{Errors: errs}
|
||||
return &multierror.Error{Errors: errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -1,24 +1,13 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// stopHook is a private Hook implementation that Terraform uses to
|
||||
// signal when to stop or cancel actions.
|
||||
type stopHook struct {
|
||||
sync.Mutex
|
||||
|
||||
// This should be incremented for every thing that can be stopped.
|
||||
// When this is zero, a stopper can assume that everything is properly
|
||||
// stopped.
|
||||
count int
|
||||
|
||||
// This channel should be closed when it is time to stop
|
||||
ch chan struct{}
|
||||
|
||||
serial int
|
||||
stoppedCh chan<- struct{}
|
||||
stop uint32
|
||||
}
|
||||
|
||||
func (h *stopHook) PreApply(string, *ResourceState, *ResourceDiff) (HookAction, error) {
|
||||
|
@ -46,34 +35,22 @@ func (h *stopHook) PostRefresh(string, *ResourceState) (HookAction, error) {
|
|||
}
|
||||
|
||||
func (h *stopHook) hook() (HookAction, error) {
|
||||
select {
|
||||
case <-h.ch:
|
||||
h.stoppedCh <- struct{}{}
|
||||
if h.Stopped() {
|
||||
return HookActionHalt, nil
|
||||
default:
|
||||
return HookActionContinue, nil
|
||||
}
|
||||
|
||||
return HookActionContinue, nil
|
||||
}
|
||||
|
||||
// reset should be called within the lock context
|
||||
func (h *stopHook) reset() {
|
||||
h.ch = make(chan struct{})
|
||||
h.count = 0
|
||||
h.serial += 1
|
||||
h.stoppedCh = nil
|
||||
func (h *stopHook) Reset() {
|
||||
atomic.StoreUint32(&h.stop, 0)
|
||||
}
|
||||
|
||||
func (h *stopHook) ref() int {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
h.count++
|
||||
return h.serial
|
||||
func (h *stopHook) Stop() {
|
||||
atomic.StoreUint32(&h.stop, 1)
|
||||
}
|
||||
|
||||
func (h *stopHook) unref(s int) {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
if h.serial == s {
|
||||
h.count--
|
||||
}
|
||||
func (h *stopHook) Stopped() bool {
|
||||
return atomic.LoadUint32(&h.stop) == 1
|
||||
}
|
||||
|
|
|
@ -23,10 +23,6 @@ type PlanOpts struct {
|
|||
// that are created. Otherwise, it will move towards the desired state
|
||||
// specified in the configuration.
|
||||
Destroy bool
|
||||
|
||||
Config *config.Config
|
||||
State *State
|
||||
Vars map[string]string
|
||||
}
|
||||
|
||||
// Plan represents a single Terraform execution plan, which contains
|
||||
|
@ -40,6 +36,18 @@ type Plan struct {
|
|||
once sync.Once
|
||||
}
|
||||
|
||||
// Context returns a Context with the data encapsulated in this plan.
|
||||
//
|
||||
// The following fields in opts are overridden by the plan: Config,
|
||||
// Diff, State, Variables.
|
||||
func (p *Plan) Context(opts *ContextOpts) *Context {
|
||||
opts.Config = p.Config
|
||||
opts.Diff = p.Diff
|
||||
opts.State = p.State
|
||||
opts.Variables = p.Vars
|
||||
return NewContext(opts)
|
||||
}
|
||||
|
||||
func (p *Plan) String() string {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString("DIFF:\n\n")
|
||||
|
|
|
@ -17,14 +17,31 @@ type ResourceProvider interface {
|
|||
// (no interpolation done) and can return a list of warnings and/or
|
||||
// errors.
|
||||
//
|
||||
// This is called once with the provider configuration only. It may not
|
||||
// be called at all if no provider configuration is given.
|
||||
//
|
||||
// This should not assume that any values of the configurations are valid.
|
||||
// The primary use case of this call is to check that required keys are
|
||||
// set.
|
||||
Validate(*ResourceConfig) ([]string, []error)
|
||||
|
||||
// ValidateResource is called once at the beginning with the raw
|
||||
// configuration (no interpolation done) and can return a list of warnings
|
||||
// and/or errors.
|
||||
//
|
||||
// This is called once per resource.
|
||||
//
|
||||
// This should not assume any of the values in the resource configuration
|
||||
// are valid since it is possible they have to be interpolated still.
|
||||
// The primary use case of this call is to check that the required keys
|
||||
// are set and that the general structure is correct.
|
||||
ValidateResource(string, *ResourceConfig) ([]string, []error)
|
||||
|
||||
// Configure configures the provider itself with the configuration
|
||||
// given. This is useful for setting things like access keys.
|
||||
//
|
||||
// This won't be called at all if no provider configuration is given.
|
||||
//
|
||||
// Configure returns an error if it occurred.
|
||||
Configure(*ResourceConfig) error
|
||||
|
||||
|
|
|
@ -6,32 +6,37 @@ type MockResourceProvider struct {
|
|||
// Anything you want, in case you need to store extra data with the mock.
|
||||
Meta interface{}
|
||||
|
||||
ApplyCalled bool
|
||||
ApplyState *ResourceState
|
||||
ApplyDiff *ResourceDiff
|
||||
ApplyFn func(*ResourceState, *ResourceDiff) (*ResourceState, error)
|
||||
ApplyReturn *ResourceState
|
||||
ApplyReturnError error
|
||||
ConfigureCalled bool
|
||||
ConfigureConfig *ResourceConfig
|
||||
ConfigureReturnError error
|
||||
DiffCalled bool
|
||||
DiffState *ResourceState
|
||||
DiffDesired *ResourceConfig
|
||||
DiffFn func(*ResourceState, *ResourceConfig) (*ResourceDiff, error)
|
||||
DiffReturn *ResourceDiff
|
||||
DiffReturnError error
|
||||
RefreshCalled bool
|
||||
RefreshState *ResourceState
|
||||
RefreshFn func(*ResourceState) (*ResourceState, error)
|
||||
RefreshReturn *ResourceState
|
||||
RefreshReturnError error
|
||||
ResourcesCalled bool
|
||||
ResourcesReturn []ResourceType
|
||||
ValidateCalled bool
|
||||
ValidateConfig *ResourceConfig
|
||||
ValidateReturnWarns []string
|
||||
ValidateReturnErrors []error
|
||||
ApplyCalled bool
|
||||
ApplyState *ResourceState
|
||||
ApplyDiff *ResourceDiff
|
||||
ApplyFn func(*ResourceState, *ResourceDiff) (*ResourceState, error)
|
||||
ApplyReturn *ResourceState
|
||||
ApplyReturnError error
|
||||
ConfigureCalled bool
|
||||
ConfigureConfig *ResourceConfig
|
||||
ConfigureReturnError error
|
||||
DiffCalled bool
|
||||
DiffState *ResourceState
|
||||
DiffDesired *ResourceConfig
|
||||
DiffFn func(*ResourceState, *ResourceConfig) (*ResourceDiff, error)
|
||||
DiffReturn *ResourceDiff
|
||||
DiffReturnError error
|
||||
RefreshCalled bool
|
||||
RefreshState *ResourceState
|
||||
RefreshFn func(*ResourceState) (*ResourceState, error)
|
||||
RefreshReturn *ResourceState
|
||||
RefreshReturnError error
|
||||
ResourcesCalled bool
|
||||
ResourcesReturn []ResourceType
|
||||
ValidateCalled bool
|
||||
ValidateConfig *ResourceConfig
|
||||
ValidateReturnWarns []string
|
||||
ValidateReturnErrors []error
|
||||
ValidateResourceCalled bool
|
||||
ValidateResourceType string
|
||||
ValidateResourceConfig *ResourceConfig
|
||||
ValidateResourceReturnWarns []string
|
||||
ValidateResourceReturnErrors []error
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Validate(c *ResourceConfig) ([]string, []error) {
|
||||
|
@ -40,6 +45,13 @@ func (p *MockResourceProvider) Validate(c *ResourceConfig) ([]string, []error) {
|
|||
return p.ValidateReturnWarns, p.ValidateReturnErrors
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) ValidateResource(t string, c *ResourceConfig) ([]string, []error) {
|
||||
p.ValidateResourceCalled = true
|
||||
p.ValidateResourceType = t
|
||||
p.ValidateResourceConfig = c
|
||||
return p.ValidateResourceReturnWarns, p.ValidateResourceReturnErrors
|
||||
}
|
||||
|
||||
func (p *MockResourceProvider) Configure(c *ResourceConfig) error {
|
||||
p.ConfigureCalled = true
|
||||
p.ConfigureConfig = c
|
||||
|
|
|
@ -1,147 +1,24 @@
|
|||
package terraform
|
||||
|
||||
/*
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
|
||||
/*
|
||||
// smcProviders matches up the resources with a provider and initializes
|
||||
// it. This does not call "Configure" on the ResourceProvider, since that
|
||||
// might actually depend on upstream resources.
|
||||
func smcProviders(
|
||||
c *Config) (map[*config.Resource]*terraformProvider, []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{})
|
||||
|
||||
// Go through each resource and match it up to a provider
|
||||
mapping := make(map[*config.Resource]*terraformProvider)
|
||||
providers := make(map[string]ResourceProvider)
|
||||
tpcache := make(map[string]*terraformProvider)
|
||||
|
||||
ResourceLoop:
|
||||
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)
|
||||
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.
|
||||
var providerName string
|
||||
var provider ResourceProvider
|
||||
for _, prefix := range prefixes {
|
||||
// Initialize the provider
|
||||
p, ok := providers[prefix]
|
||||
if !ok {
|
||||
var err error
|
||||
p, err = c.Providers[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 resource
|
||||
continue ResourceLoop
|
||||
}
|
||||
|
||||
providers[prefix] = p
|
||||
}
|
||||
|
||||
// Test if this provider matches what we need
|
||||
if !ProviderSatisfies(p, r.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
providerName = prefix
|
||||
provider = p
|
||||
break
|
||||
}
|
||||
|
||||
// If we didn't find a valid provider, then error and continue
|
||||
if providerName == "" {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Provider for resource %s not found.",
|
||||
r.Id()))
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the matching provider configuration for this resource
|
||||
var pc *config.ProviderConfig
|
||||
pcName := config.ProviderConfigName(r.Type, c.Config.ProviderConfigs)
|
||||
if pcName != "" {
|
||||
pc = c.Config.ProviderConfigs[pcName]
|
||||
}
|
||||
|
||||
// Look up if we already have a provider for this pair of PC
|
||||
// and provider name. If not, create it.
|
||||
cacheKey := fmt.Sprintf("%s|%s", pcName, providerName)
|
||||
tp, ok := tpcache[cacheKey]
|
||||
if !ok {
|
||||
renew := false
|
||||
for _, tp := range tpcache {
|
||||
if tp.Provider == provider {
|
||||
renew = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if renew {
|
||||
var err error
|
||||
provider, err = c.Providers[providerName]()
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Error instantiating resource provider for "+
|
||||
"prefix %s: %s", providerName, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
tp = &terraformProvider{
|
||||
Provider: provider,
|
||||
Config: pc,
|
||||
}
|
||||
tpcache[cacheKey] = tp
|
||||
}
|
||||
|
||||
mapping[r] = tp
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
return mapping, nil
|
||||
}
|
||||
|
||||
// smcVariables does all the semantic checks to verify that the
|
||||
// variables given in the configuration to instantiate a Terraform
|
||||
// struct are valid.
|
||||
func smcVariables(c *Config) []error {
|
||||
// smcUserVariables does all the semantic checks to verify that the
|
||||
// variables given satisfy the configuration itself.
|
||||
func smcUserVariables(c *config.Config, vs map[string]string) []error {
|
||||
var errs []error
|
||||
|
||||
// Check that all required variables are present
|
||||
required := make(map[string]struct{})
|
||||
for k, v := range c.Config.Variables {
|
||||
for k, v := range c.Variables {
|
||||
if v.Required() {
|
||||
required[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
for k, _ := range c.Variables {
|
||||
for k, _ := range vs {
|
||||
delete(required, k)
|
||||
}
|
||||
if len(required) > 0 {
|
||||
|
@ -155,4 +32,3 @@ func smcVariables(c *Config) []error {
|
|||
|
||||
return errs
|
||||
}
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSMCUserVariables(t *testing.T) {
|
||||
c := testConfig(t, "smc-uservars")
|
||||
|
||||
// Required variables not set
|
||||
errs := smcUserVariables(c, nil)
|
||||
if len(errs) == 0 {
|
||||
t.Fatal("should have errors")
|
||||
}
|
||||
|
||||
// Required variables set, optional variables unset
|
||||
errs = smcUserVariables(c, map[string]string{"foo": "bar"})
|
||||
if len(errs) != 0 {
|
||||
t.Fatalf("err: %#v", errs)
|
||||
}
|
||||
}
|
|
@ -1,455 +0,0 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/depgraph"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
hooks []Hook
|
||||
providers map[string]ResourceProviderFactory
|
||||
stopHook *stopHook
|
||||
}
|
||||
|
||||
// This is a function type used to implement a walker for the resource
|
||||
// tree internally on the Terraform structure.
|
||||
type genericWalkFunc func(*Resource) (map[string]string, error)
|
||||
|
||||
// Config is the configuration that must be given to instantiate
|
||||
// a Terraform structure.
|
||||
type Config struct {
|
||||
Hooks []Hook
|
||||
Providers map[string]ResourceProviderFactory
|
||||
}
|
||||
|
||||
// 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) {
|
||||
sh := new(stopHook)
|
||||
sh.Lock()
|
||||
sh.reset()
|
||||
sh.Unlock()
|
||||
|
||||
// Copy all the hooks and add our stop hook. We don't append directly
|
||||
// to the Config so that we're not modifying that in-place.
|
||||
hooks := make([]Hook, len(c.Hooks)+1)
|
||||
copy(hooks, c.Hooks)
|
||||
hooks[len(c.Hooks)] = sh
|
||||
|
||||
return &Terraform{
|
||||
hooks: hooks,
|
||||
stopHook: sh,
|
||||
providers: c.Providers,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Terraform) Apply(p *Plan) (*State, error) {
|
||||
// Increase the count on the stop hook so we know when to stop
|
||||
serial := t.stopHook.ref()
|
||||
defer t.stopHook.unref(serial)
|
||||
|
||||
// Make sure we're working with a plan that doesn't have null pointers
|
||||
// everywhere, and is instead just empty otherwise.
|
||||
p.init()
|
||||
|
||||
g, err := Graph(&GraphOpts{
|
||||
Config: p.Config,
|
||||
Diff: p.Diff,
|
||||
Providers: t.providers,
|
||||
State: p.State,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.apply(g, p)
|
||||
}
|
||||
|
||||
// Stop stops all running tasks (applies, plans, refreshes).
|
||||
//
|
||||
// This will block until all running tasks are stopped. While Stop is
|
||||
// blocked, any new calls to Apply, Plan, Refresh, etc. will also block. New
|
||||
// calls, however, will start once this Stop has returned.
|
||||
func (t *Terraform) Stop() {
|
||||
log.Printf("[INFO] Terraform stopping tasks")
|
||||
|
||||
t.stopHook.Lock()
|
||||
defer t.stopHook.Unlock()
|
||||
|
||||
// Setup the stoppedCh
|
||||
stoppedCh := make(chan struct{}, t.stopHook.count)
|
||||
t.stopHook.stoppedCh = stoppedCh
|
||||
|
||||
// Close the channel to signal that we're done
|
||||
close(t.stopHook.ch)
|
||||
|
||||
// Expect the number of count stops...
|
||||
log.Printf("[DEBUG] Waiting for %d tasks to stop", t.stopHook.count)
|
||||
for i := 0; i < t.stopHook.count; i++ {
|
||||
<-stoppedCh
|
||||
}
|
||||
log.Printf("[DEBUG] Stopped!")
|
||||
|
||||
// Success, everything stopped, reset everything
|
||||
t.stopHook.reset()
|
||||
}
|
||||
|
||||
func (t *Terraform) Plan(opts *PlanOpts) (*Plan, error) {
|
||||
// Increase the count on the stop hook so we know when to stop
|
||||
serial := t.stopHook.ref()
|
||||
defer t.stopHook.unref(serial)
|
||||
|
||||
g, err := Graph(&GraphOpts{
|
||||
Config: opts.Config,
|
||||
Providers: t.providers,
|
||||
State: opts.State,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.plan(g, opts)
|
||||
}
|
||||
|
||||
// Refresh goes through all the resources in the state and refreshes them
|
||||
// to their latest status.
|
||||
func (t *Terraform) Refresh(c *config.Config, s *State) (*State, error) {
|
||||
// Increase the count on the stop hook so we know when to stop
|
||||
serial := t.stopHook.ref()
|
||||
defer t.stopHook.unref(serial)
|
||||
|
||||
g, err := Graph(&GraphOpts{
|
||||
Config: c,
|
||||
Providers: t.providers,
|
||||
State: s,
|
||||
})
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return t.refresh(g)
|
||||
}
|
||||
|
||||
func (t *Terraform) apply(
|
||||
g *depgraph.Graph,
|
||||
p *Plan) (*State, error) {
|
||||
// Create our result. Make sure we preserve the prior states
|
||||
s := new(State)
|
||||
s.init()
|
||||
for k, v := range p.State.Resources {
|
||||
s.Resources[k] = v
|
||||
}
|
||||
|
||||
err := g.Walk(t.applyWalkFn(s, p))
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (t *Terraform) plan(g *depgraph.Graph, opts *PlanOpts) (*Plan, error) {
|
||||
p := &Plan{
|
||||
Config: opts.Config,
|
||||
Vars: opts.Vars,
|
||||
State: opts.State,
|
||||
}
|
||||
err := g.Walk(t.planWalkFn(p, opts))
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (t *Terraform) refresh(g *depgraph.Graph) (*State, error) {
|
||||
s := new(State)
|
||||
err := g.Walk(t.refreshWalkFn(s))
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (t *Terraform) refreshWalkFn(result *State) depgraph.WalkFunc {
|
||||
var l sync.Mutex
|
||||
|
||||
// Initialize the result so we don't have to nil check everywhere
|
||||
result.init()
|
||||
|
||||
cb := func(r *Resource) (map[string]string, error) {
|
||||
for _, h := range t.hooks {
|
||||
handleHook(h.PreRefresh(r.Id, r.State))
|
||||
}
|
||||
|
||||
rs, err := r.Provider.Refresh(r.State)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rs == nil {
|
||||
rs = new(ResourceState)
|
||||
}
|
||||
|
||||
// Fix the type to be the type we have
|
||||
rs.Type = r.State.Type
|
||||
|
||||
l.Lock()
|
||||
result.Resources[r.Id] = rs
|
||||
l.Unlock()
|
||||
|
||||
for _, h := range t.hooks {
|
||||
handleHook(h.PostRefresh(r.Id, rs))
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return t.genericWalkFn(nil, cb)
|
||||
}
|
||||
|
||||
func (t *Terraform) applyWalkFn(
|
||||
result *State,
|
||||
p *Plan) depgraph.WalkFunc {
|
||||
var l sync.Mutex
|
||||
|
||||
// Initialize the result
|
||||
result.init()
|
||||
|
||||
cb := func(r *Resource) (map[string]string, error) {
|
||||
diff := r.Diff
|
||||
if diff.Empty() {
|
||||
return r.Vars(), nil
|
||||
}
|
||||
|
||||
if !diff.Destroy {
|
||||
var err error
|
||||
diff, err = r.Provider.Diff(r.State, r.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(mitchellh): we need to verify the diff doesn't change
|
||||
// anything and that the diff has no computed values (pre-computed)
|
||||
|
||||
for _, h := range t.hooks {
|
||||
handleHook(h.PreApply(r.Id, r.State, diff))
|
||||
}
|
||||
|
||||
// With the completed diff, apply!
|
||||
log.Printf("[DEBUG] %s: Executing Apply", r.Id)
|
||||
rs, err := r.Provider.Apply(r.State, diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure the result is instantiated
|
||||
if rs == nil {
|
||||
rs = new(ResourceState)
|
||||
}
|
||||
|
||||
// Force the resource state type to be our type
|
||||
rs.Type = r.State.Type
|
||||
|
||||
var errs []error
|
||||
for ak, av := range rs.Attributes {
|
||||
// If the value is the unknown variable value, then it is an error.
|
||||
// In this case we record the error and remove it from the state
|
||||
if av == config.UnknownVariableValue {
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"Attribute with unknown value: %s", ak))
|
||||
delete(rs.Attributes, ak)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the resulting diff
|
||||
l.Lock()
|
||||
if rs.ID == "" {
|
||||
delete(result.Resources, r.Id)
|
||||
} else {
|
||||
result.Resources[r.Id] = rs
|
||||
}
|
||||
l.Unlock()
|
||||
|
||||
// Update the state for the resource itself
|
||||
r.State = rs
|
||||
|
||||
for _, h := range t.hooks {
|
||||
handleHook(h.PostApply(r.Id, r.State))
|
||||
}
|
||||
|
||||
// Determine the new state and update variables
|
||||
err = nil
|
||||
if len(errs) > 0 {
|
||||
err = &MultiError{Errors: errs}
|
||||
}
|
||||
|
||||
return r.Vars(), err
|
||||
}
|
||||
|
||||
return t.genericWalkFn(p.Vars, cb)
|
||||
}
|
||||
|
||||
func (t *Terraform) planWalkFn(result *Plan, opts *PlanOpts) depgraph.WalkFunc {
|
||||
var l sync.Mutex
|
||||
|
||||
// Initialize the result
|
||||
result.init()
|
||||
|
||||
cb := func(r *Resource) (map[string]string, error) {
|
||||
var diff *ResourceDiff
|
||||
|
||||
for _, h := range t.hooks {
|
||||
handleHook(h.PreDiff(r.Id, r.State))
|
||||
}
|
||||
|
||||
if opts.Destroy {
|
||||
if r.State.ID != "" {
|
||||
log.Printf("[DEBUG] %s: Making for destroy", r.Id)
|
||||
diff = &ResourceDiff{Destroy: true}
|
||||
} else {
|
||||
log.Printf("[DEBUG] %s: Not marking for destroy, no ID", r.Id)
|
||||
}
|
||||
} else if r.Config == nil {
|
||||
log.Printf("[DEBUG] %s: Orphan, marking for destroy", r.Id)
|
||||
|
||||
// This is an orphan (no config), so we mark it to be destroyed
|
||||
diff = &ResourceDiff{Destroy: true}
|
||||
} else {
|
||||
log.Printf("[DEBUG] %s: Executing diff", r.Id)
|
||||
|
||||
// Get a diff from the newest state
|
||||
var err error
|
||||
diff, err = r.Provider.Diff(r.State, r.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
if !diff.Empty() {
|
||||
result.Diff.Resources[r.Id] = diff
|
||||
}
|
||||
l.Unlock()
|
||||
|
||||
for _, h := range t.hooks {
|
||||
handleHook(h.PostDiff(r.Id, diff))
|
||||
}
|
||||
|
||||
// Determine the new state and update variables
|
||||
if !diff.Empty() {
|
||||
r.State = r.State.MergeDiff(diff)
|
||||
}
|
||||
|
||||
return r.Vars(), nil
|
||||
}
|
||||
|
||||
return t.genericWalkFn(opts.Vars, cb)
|
||||
}
|
||||
|
||||
func (t *Terraform) genericWalkFn(
|
||||
invars map[string]string,
|
||||
cb genericWalkFunc) depgraph.WalkFunc {
|
||||
var l sync.RWMutex
|
||||
|
||||
// Initialize the variables for application
|
||||
vars := make(map[string]string)
|
||||
for k, v := range invars {
|
||||
vars[fmt.Sprintf("var.%s", k)] = v
|
||||
}
|
||||
|
||||
// This will keep track of whether we're stopped or not
|
||||
var stop uint32 = 0
|
||||
|
||||
return func(n *depgraph.Noun) error {
|
||||
// If it is the root node, ignore
|
||||
if n.Name == GraphRootNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we're stopped, return right away
|
||||
if atomic.LoadUint32(&stop) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch m := n.Meta.(type) {
|
||||
case *GraphNodeResource:
|
||||
case *GraphNodeResourceProvider:
|
||||
var rc *ResourceConfig
|
||||
if m.Config != nil {
|
||||
if err := m.Config.RawConfig.Interpolate(vars); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rc = NewResourceConfig(m.Config.RawConfig)
|
||||
}
|
||||
|
||||
for k, p := range m.Providers {
|
||||
log.Printf("[INFO] Configuring provider: %s", k)
|
||||
err := p.Configure(rc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
rn := n.Meta.(*GraphNodeResource)
|
||||
|
||||
l.RLock()
|
||||
if len(vars) > 0 && rn.Config != nil {
|
||||
if err := rn.Config.RawConfig.Interpolate(vars); err != nil {
|
||||
panic(fmt.Sprintf("Interpolate error: %s", err))
|
||||
}
|
||||
|
||||
// Force the config to be set later
|
||||
rn.Resource.Config = nil
|
||||
}
|
||||
l.RUnlock()
|
||||
|
||||
// Make sure that at least some resource configuration is set
|
||||
if !rn.Orphan {
|
||||
if rn.Resource.Config == nil {
|
||||
if rn.Config == nil {
|
||||
rn.Resource.Config = new(ResourceConfig)
|
||||
} else {
|
||||
rn.Resource.Config = NewResourceConfig(rn.Config.RawConfig)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rn.Resource.Config = nil
|
||||
}
|
||||
|
||||
// Handle recovery of special panic scenarios
|
||||
defer func() {
|
||||
if v := recover(); v != nil {
|
||||
if v == HookActionHalt {
|
||||
atomic.StoreUint32(&stop, 1)
|
||||
} else {
|
||||
panic(v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Call the callack
|
||||
log.Printf("[INFO] Walking: %s", rn.Resource.Id)
|
||||
newVars, err := cb(rn.Resource)
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
|
@ -14,712 +11,6 @@ import (
|
|||
// This is the directory where our test fixtures are.
|
||||
const fixtureDir = "./test-fixtures"
|
||||
|
||||
func TestTerraformApply(t *testing.T) {
|
||||
c := testConfig(t, "apply-good")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := tf.Apply(p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(state.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", state.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformApply_cancel(t *testing.T) {
|
||||
stopped := false
|
||||
stopCh := make(chan struct{})
|
||||
stopReplyCh := make(chan struct{})
|
||||
|
||||
rpAWS := new(MockResourceProvider)
|
||||
rpAWS.ResourcesReturn = []ResourceType{
|
||||
ResourceType{Name: "aws_instance"},
|
||||
}
|
||||
rpAWS.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
|
||||
return &ResourceDiff{
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"num": &ResourceAttrDiff{
|
||||
New: "bar",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
rpAWS.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
|
||||
if !stopped {
|
||||
stopped = true
|
||||
close(stopCh)
|
||||
<-stopReplyCh
|
||||
}
|
||||
|
||||
return &ResourceState{
|
||||
ID: "foo",
|
||||
Attributes: map[string]string{
|
||||
"num": "2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
c := testConfig(t, "apply-cancel")
|
||||
tf := testTerraform2(t, &Config{
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(rpAWS),
|
||||
},
|
||||
})
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Start the Apply in a goroutine
|
||||
stateCh := make(chan *State)
|
||||
go func() {
|
||||
state, err := tf.Apply(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stateCh <- state
|
||||
}()
|
||||
|
||||
// Start a goroutine so we can inject exactly when we stop
|
||||
s := tf.stopHook.ref()
|
||||
go func() {
|
||||
defer tf.stopHook.unref(s)
|
||||
<-tf.stopHook.ch
|
||||
close(stopReplyCh)
|
||||
tf.stopHook.stoppedCh <- struct{}{}
|
||||
}()
|
||||
|
||||
<-stopCh
|
||||
tf.Stop()
|
||||
|
||||
state := <-stateCh
|
||||
|
||||
if len(state.Resources) != 1 {
|
||||
t.Fatalf("bad: %#v", state.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyCancelStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformApply_compute(t *testing.T) {
|
||||
// This tests that computed variables are properly re-diffed
|
||||
// to get the value prior to application (Apply).
|
||||
c := testConfig(t, "apply-compute")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
p.Vars["value"] = "1"
|
||||
|
||||
state, err := tf.Apply(p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyComputeStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformApply_destroy(t *testing.T) {
|
||||
h := new(HookRecordApplyOrder)
|
||||
|
||||
// First, apply the good configuration, build it
|
||||
c := testConfig(t, "apply-destroy")
|
||||
tf := testTerraform2(t, &Config{
|
||||
Hooks: []Hook{h},
|
||||
})
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := tf.Apply(p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Next, plan and apply a destroy operation
|
||||
p, err = tf.Plan(&PlanOpts{
|
||||
Config: new(config.Config),
|
||||
State: state,
|
||||
Destroy: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
h.Active = true
|
||||
|
||||
state, err = tf.Apply(p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Test that things were destroyed
|
||||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyDestroyStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
|
||||
// Test that things were destroyed _in the right order_
|
||||
expected2 := []string{"aws_instance.bar", "aws_instance.foo"}
|
||||
actual2 := h.IDs
|
||||
if !reflect.DeepEqual(actual2, expected2) {
|
||||
t.Fatalf("bad: %#v", actual2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformApply_destroyOrphan(t *testing.T) {
|
||||
rpAWS := new(MockResourceProvider)
|
||||
rpAWS.ResourcesReturn = []ResourceType{
|
||||
ResourceType{Name: "aws_instance"},
|
||||
}
|
||||
rpAWS.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
|
||||
return &ResourceDiff{
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"num": &ResourceAttrDiff{
|
||||
New: "bar",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
rpAWS.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
c := testConfig(t, "apply-error")
|
||||
tf := testTerraform2(t, &Config{
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(rpAWS),
|
||||
},
|
||||
})
|
||||
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.baz": &ResourceState{
|
||||
ID: "bar",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{Config: c, State: s})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := tf.Apply(p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(state.Resources) != 0 {
|
||||
t.Fatalf("bad: %#v", state.Resources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformApply_error(t *testing.T) {
|
||||
errored := false
|
||||
|
||||
rpAWS := new(MockResourceProvider)
|
||||
rpAWS.ResourcesReturn = []ResourceType{
|
||||
ResourceType{Name: "aws_instance"},
|
||||
}
|
||||
rpAWS.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
|
||||
return &ResourceDiff{
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"num": &ResourceAttrDiff{
|
||||
New: "bar",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
rpAWS.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
|
||||
if errored {
|
||||
return nil, fmt.Errorf("error")
|
||||
}
|
||||
errored = true
|
||||
|
||||
return &ResourceState{
|
||||
ID: "foo",
|
||||
Attributes: map[string]string{
|
||||
"num": "2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
c := testConfig(t, "apply-error")
|
||||
tf := testTerraform2(t, &Config{
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(rpAWS),
|
||||
},
|
||||
})
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := tf.Apply(p)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
if len(state.Resources) != 1 {
|
||||
t.Fatalf("bad: %#v", state.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyErrorStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformApply_errorPartial(t *testing.T) {
|
||||
errored := false
|
||||
|
||||
rpAWS := new(MockResourceProvider)
|
||||
rpAWS.ResourcesReturn = []ResourceType{
|
||||
ResourceType{Name: "aws_instance"},
|
||||
}
|
||||
rpAWS.DiffFn = func(*ResourceState, *ResourceConfig) (*ResourceDiff, error) {
|
||||
return &ResourceDiff{
|
||||
Attributes: map[string]*ResourceAttrDiff{
|
||||
"num": &ResourceAttrDiff{
|
||||
New: "bar",
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
rpAWS.ApplyFn = func(*ResourceState, *ResourceDiff) (*ResourceState, error) {
|
||||
if errored {
|
||||
return nil, fmt.Errorf("error")
|
||||
}
|
||||
errored = true
|
||||
|
||||
return &ResourceState{
|
||||
ID: "foo",
|
||||
Attributes: map[string]string{
|
||||
"num": "2",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
c := testConfig(t, "apply-error")
|
||||
tf := testTerraform2(t, &Config{
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(rpAWS),
|
||||
},
|
||||
})
|
||||
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.bar": &ResourceState{
|
||||
ID: "bar",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{Config: c, State: s})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := tf.Apply(p)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
if len(state.Resources) != 2 {
|
||||
t.Fatalf("bad: %#v", state.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyErrorPartialStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformApply_hook(t *testing.T) {
|
||||
c := testConfig(t, "apply-good")
|
||||
h := new(MockHook)
|
||||
tf := testTerraform2(t, &Config{
|
||||
Hooks: []Hook{h},
|
||||
})
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if _, err := tf.Apply(p); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !h.PreApplyCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if !h.PostApplyCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformApply_unknownAttribute(t *testing.T) {
|
||||
c := testConfig(t, "apply-unknown")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
state, err := tf.Apply(p)
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyUnknownAttrStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformApply_vars(t *testing.T) {
|
||||
c := testConfig(t, "apply-vars")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
p, err := tf.Plan(&PlanOpts{
|
||||
Config: c,
|
||||
Vars: map[string]string{"foo": "baz"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Explicitly set the "foo" variable
|
||||
p.Vars["foo"] = "bar"
|
||||
|
||||
state, err := tf.Apply(p)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(state.String())
|
||||
expected := strings.TrimSpace(testTerraformApplyVarsStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan(t *testing.T) {
|
||||
c := testConfig(t, "plan-good")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_nil(t *testing.T) {
|
||||
c := testConfig(t, "plan-nil")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if len(plan.Diff.Resources) != 0 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_computed(t *testing.T) {
|
||||
c := testConfig(t, "plan-computed")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{Config: c})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanComputedStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_destroy(t *testing.T) {
|
||||
c := testConfig(t, "plan-destroy")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.one": &ResourceState{
|
||||
ID: "bar",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
"aws_instance.two": &ResourceState{
|
||||
ID: "baz",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{
|
||||
Destroy: true,
|
||||
Config: c,
|
||||
State: s,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) != 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanDestroyStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_hook(t *testing.T) {
|
||||
c := testConfig(t, "plan-good")
|
||||
h := new(MockHook)
|
||||
tf := testTerraform2(t, &Config{
|
||||
Hooks: []Hook{h},
|
||||
})
|
||||
|
||||
if _, err := tf.Plan(&PlanOpts{Config: c}); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !h.PreDiffCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if !h.PostDiffCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_orphan(t *testing.T) {
|
||||
c := testConfig(t, "plan-orphan")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.baz": &ResourceState{
|
||||
ID: "bar",
|
||||
Type: "aws_instance",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{
|
||||
Config: c,
|
||||
State: s,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanOrphanStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformPlan_state(t *testing.T) {
|
||||
c := testConfig(t, "plan-good")
|
||||
tf := testTerraform2(t, nil)
|
||||
|
||||
s := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.foo": &ResourceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
plan, err := tf.Plan(&PlanOpts{
|
||||
Config: c,
|
||||
State: s,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if len(plan.Diff.Resources) < 2 {
|
||||
t.Fatalf("bad: %#v", plan.Diff.Resources)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(plan.String())
|
||||
expected := strings.TrimSpace(testTerraformPlanStateStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformRefresh(t *testing.T) {
|
||||
rpAWS := new(MockResourceProvider)
|
||||
rpAWS.ResourcesReturn = []ResourceType{
|
||||
ResourceType{Name: "aws_instance"},
|
||||
}
|
||||
|
||||
c := testConfig(t, "refresh-basic")
|
||||
tf := testTerraform2(t, &Config{
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(rpAWS),
|
||||
},
|
||||
})
|
||||
|
||||
rpAWS.RefreshReturn = &ResourceState{
|
||||
ID: "foo",
|
||||
}
|
||||
|
||||
s, err := tf.Refresh(c, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !rpAWS.RefreshCalled {
|
||||
t.Fatal("refresh should be called")
|
||||
}
|
||||
if rpAWS.RefreshState.ID != "" {
|
||||
t.Fatalf("bad: %#v", rpAWS.RefreshState)
|
||||
}
|
||||
if !reflect.DeepEqual(s.Resources["aws_instance.web"], rpAWS.RefreshReturn) {
|
||||
t.Fatalf("bad: %#v", s.Resources)
|
||||
}
|
||||
|
||||
for _, r := range s.Resources {
|
||||
if r.Type == "" {
|
||||
t.Fatalf("no type: %#v", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformRefresh_hook(t *testing.T) {
|
||||
rpAWS := new(MockResourceProvider)
|
||||
rpAWS.ResourcesReturn = []ResourceType{
|
||||
ResourceType{Name: "aws_instance"},
|
||||
}
|
||||
|
||||
h := new(MockHook)
|
||||
|
||||
c := testConfig(t, "refresh-basic")
|
||||
tf := testTerraform2(t, &Config{
|
||||
Hooks: []Hook{h},
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(rpAWS),
|
||||
},
|
||||
})
|
||||
|
||||
if _, err := tf.Refresh(c, nil); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !h.PreRefreshCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if h.PreRefreshState.Type != "aws_instance" {
|
||||
t.Fatalf("bad: %#v", h.PreRefreshState)
|
||||
}
|
||||
if !h.PostRefreshCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if h.PostRefreshState.Type != "aws_instance" {
|
||||
t.Fatalf("bad: %#v", h.PostRefreshState)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerraformRefresh_state(t *testing.T) {
|
||||
rpAWS := new(MockResourceProvider)
|
||||
rpAWS.ResourcesReturn = []ResourceType{
|
||||
ResourceType{Name: "aws_instance"},
|
||||
}
|
||||
|
||||
c := testConfig(t, "refresh-basic")
|
||||
tf := testTerraform2(t, &Config{
|
||||
Providers: map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFuncFixed(rpAWS),
|
||||
},
|
||||
})
|
||||
|
||||
rpAWS.RefreshReturn = &ResourceState{
|
||||
ID: "foo",
|
||||
}
|
||||
|
||||
state := &State{
|
||||
Resources: map[string]*ResourceState{
|
||||
"aws_instance.web": &ResourceState{
|
||||
ID: "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s, err := tf.Refresh(c, state)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if !rpAWS.RefreshCalled {
|
||||
t.Fatal("refresh should be called")
|
||||
}
|
||||
if !reflect.DeepEqual(rpAWS.RefreshState, state.Resources["aws_instance.web"]) {
|
||||
t.Fatalf("bad: %#v", rpAWS.RefreshState)
|
||||
}
|
||||
if !reflect.DeepEqual(s.Resources["aws_instance.web"], rpAWS.RefreshReturn) {
|
||||
t.Fatalf("bad: %#v", s.Resources)
|
||||
}
|
||||
}
|
||||
|
||||
func testConfig(t *testing.T, name string) *config.Config {
|
||||
c, err := config.Load(filepath.Join(fixtureDir, name, "main.tf"))
|
||||
if err != nil {
|
||||
|
@ -729,171 +20,12 @@ func testConfig(t *testing.T, name string) *config.Config {
|
|||
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) {
|
||||
p := &MockResourceProvider{Meta: n}
|
||||
|
||||
applyFn := func(
|
||||
s *ResourceState,
|
||||
d *ResourceDiff) (*ResourceState, error) {
|
||||
if d.Destroy {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
id := "foo"
|
||||
if idAttr, ok := d.Attributes["id"]; ok && !idAttr.NewComputed {
|
||||
id = idAttr.New
|
||||
}
|
||||
|
||||
result := &ResourceState{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
if d != nil {
|
||||
result = result.MergeDiff(d)
|
||||
}
|
||||
|
||||
if depAttr, ok := d.Attributes["dep"]; ok {
|
||||
result.Dependencies = []ResourceDependency{
|
||||
ResourceDependency{
|
||||
ID: depAttr.New,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
diffFn := func(
|
||||
s *ResourceState,
|
||||
c *ResourceConfig) (*ResourceDiff, error) {
|
||||
var diff ResourceDiff
|
||||
diff.Attributes = make(map[string]*ResourceAttrDiff)
|
||||
diff.Attributes["type"] = &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: s.Type,
|
||||
}
|
||||
|
||||
for k, v := range c.Raw {
|
||||
if _, ok := v.(string); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if k == "nil" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// This key is used for other purposes
|
||||
if k == "compute_value" {
|
||||
continue
|
||||
}
|
||||
|
||||
if k == "compute" {
|
||||
attrDiff := &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: "",
|
||||
NewComputed: true,
|
||||
}
|
||||
|
||||
if cv, ok := c.Config["compute_value"]; ok {
|
||||
if cv.(string) == "1" {
|
||||
attrDiff.NewComputed = false
|
||||
attrDiff.New = fmt.Sprintf("computed_%s", v.(string))
|
||||
}
|
||||
}
|
||||
|
||||
diff.Attributes[v.(string)] = attrDiff
|
||||
continue
|
||||
}
|
||||
|
||||
// If this key is not computed, then look it up in the
|
||||
// cleaned config.
|
||||
found := false
|
||||
for _, ck := range c.ComputedKeys {
|
||||
if ck == k {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
v = c.Config[k]
|
||||
}
|
||||
|
||||
attrDiff := &ResourceAttrDiff{
|
||||
Old: "",
|
||||
New: v.(string),
|
||||
}
|
||||
|
||||
diff.Attributes[k] = attrDiff
|
||||
}
|
||||
|
||||
for _, k := range c.ComputedKeys {
|
||||
diff.Attributes[k] = &ResourceAttrDiff{
|
||||
Old: "",
|
||||
NewComputed: true,
|
||||
}
|
||||
}
|
||||
|
||||
return &diff, nil
|
||||
}
|
||||
|
||||
refreshFn := func(s *ResourceState) (*ResourceState, error) {
|
||||
if _, ok := s.Attributes["nil"]; ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
p.ApplyFn = applyFn
|
||||
p.DiffFn = diffFn
|
||||
p.RefreshFn = refreshFn
|
||||
p.ResourcesReturn = resources
|
||||
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
func testProviderFuncFixed(rp ResourceProvider) ResourceProviderFactory {
|
||||
return func() (ResourceProvider, error) {
|
||||
return rp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func testProviderMock(p ResourceProvider) *MockResourceProvider {
|
||||
return p.(*MockResourceProvider)
|
||||
}
|
||||
|
||||
func testTerraform2(t *testing.T, c *Config) *Terraform {
|
||||
if c == nil {
|
||||
c = new(Config)
|
||||
}
|
||||
|
||||
if c.Providers == nil {
|
||||
c.Providers = map[string]ResourceProviderFactory{
|
||||
"aws": testProviderFunc("aws", []string{"aws_instance"}),
|
||||
"do": testProviderFunc("do", []string{"do_droplet"}),
|
||||
}
|
||||
}
|
||||
|
||||
tf, err := New(c)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if tf == nil {
|
||||
t.Fatal("tf should not be nil")
|
||||
}
|
||||
|
||||
return tf
|
||||
}
|
||||
|
||||
// HookRecordApplyOrder is a test hook that records the order of applies
|
||||
// by recording the PreApply event.
|
||||
type HookRecordApplyOrder struct {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
# Required
|
||||
variable "foo" {
|
||||
}
|
||||
|
||||
# Optional
|
||||
variable "bar" {
|
||||
default = "baz"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
provider "aws" {
|
||||
foo = "bar"
|
||||
}
|
||||
|
||||
resource "aws_instance" "test" {}
|
|
@ -0,0 +1,3 @@
|
|||
resource "aws_instance" "test" {
|
||||
foo = "bar"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
resource "aws_instance" "foo" {
|
||||
num = "2"
|
||||
}
|
||||
|
||||
resource "aws_instance" "bar" {
|
||||
foo = "${var.foo}"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
resource "aws_instance" "foo" {
|
||||
num = "2"
|
||||
}
|
||||
|
||||
resource "aws_instance" "bar" {
|
||||
foo = "bar"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
variable "foo" {}
|
||||
|
||||
resource "aws_instance" "web" {
|
||||
ami = "${var.foo}"
|
||||
}
|
Loading…
Reference in New Issue