github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/backend/local/backend.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/hashicorp/errwrap"
     9  	"github.com/hashicorp/terraform/backend"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  	"github.com/hashicorp/terraform/state"
    12  	"github.com/hashicorp/terraform/terraform"
    13  	"github.com/mitchellh/cli"
    14  	"github.com/mitchellh/colorstring"
    15  )
    16  
    17  // Local is an implementation of EnhancedBackend that performs all operations
    18  // locally. This is the "default" backend and implements normal Terraform
    19  // behavior as it is well known.
    20  type Local struct {
    21  	// CLI and Colorize control the CLI output. If CLI is nil then no CLI
    22  	// output will be done. If CLIColor is nil then no coloring will be done.
    23  	CLI      cli.Ui
    24  	CLIColor *colorstring.Colorize
    25  
    26  	// StatePath is the local path where state is read from.
    27  	//
    28  	// StateOutPath is the local path where the state will be written.
    29  	// If this is empty, it will default to StatePath.
    30  	//
    31  	// StateBackupPath is the local path where a backup file will be written.
    32  	// If this is empty, no backup will be taken.
    33  	StatePath       string
    34  	StateOutPath    string
    35  	StateBackupPath string
    36  
    37  	// we only want to create a single instance of the local state
    38  	state state.State
    39  
    40  	// ContextOpts are the base context options to set when initializing a
    41  	// Terraform context. Many of these will be overridden or merged by
    42  	// Operation. See Operation for more details.
    43  	ContextOpts *terraform.ContextOpts
    44  
    45  	// OpInput will ask for necessary input prior to performing any operations.
    46  	//
    47  	// OpValidation will perform validation prior to running an operation. The
    48  	// variable naming doesn't match the style of others since we have a func
    49  	// Validate.
    50  	OpInput      bool
    51  	OpValidation bool
    52  
    53  	// Backend, if non-nil, will use this backend for non-enhanced behavior.
    54  	// This allows local behavior with remote state storage. It is a way to
    55  	// "upgrade" a non-enhanced backend to an enhanced backend with typical
    56  	// behavior.
    57  	//
    58  	// If this is nil, local performs normal state loading and storage.
    59  	Backend backend.Backend
    60  
    61  	schema *schema.Backend
    62  	opLock sync.Mutex
    63  	once   sync.Once
    64  }
    65  
    66  func (b *Local) Input(
    67  	ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
    68  	b.once.Do(b.init)
    69  
    70  	f := b.schema.Input
    71  	if b.Backend != nil {
    72  		f = b.Backend.Input
    73  	}
    74  
    75  	return f(ui, c)
    76  }
    77  
    78  func (b *Local) Validate(c *terraform.ResourceConfig) ([]string, []error) {
    79  	b.once.Do(b.init)
    80  
    81  	f := b.schema.Validate
    82  	if b.Backend != nil {
    83  		f = b.Backend.Validate
    84  	}
    85  
    86  	return f(c)
    87  }
    88  
    89  func (b *Local) Configure(c *terraform.ResourceConfig) error {
    90  	b.once.Do(b.init)
    91  
    92  	f := b.schema.Configure
    93  	if b.Backend != nil {
    94  		f = b.Backend.Configure
    95  	}
    96  
    97  	return f(c)
    98  }
    99  
   100  func (b *Local) State() (state.State, error) {
   101  	// If we have a backend handling state, defer to that.
   102  	if b.Backend != nil {
   103  		return b.Backend.State()
   104  	}
   105  
   106  	if b.state != nil {
   107  		return b.state, nil
   108  	}
   109  
   110  	// Otherwise, we need to load the state.
   111  	var s state.State = &state.LocalState{
   112  		Path:    b.StatePath,
   113  		PathOut: b.StateOutPath,
   114  	}
   115  
   116  	// Load the state as a sanity check
   117  	if err := s.RefreshState(); err != nil {
   118  		return nil, errwrap.Wrapf("Error reading local state: {{err}}", err)
   119  	}
   120  
   121  	// If we are backing up the state, wrap it
   122  	if path := b.StateBackupPath; path != "" {
   123  		s = &state.BackupState{
   124  			Real: s,
   125  			Path: path,
   126  		}
   127  	}
   128  
   129  	b.state = s
   130  	return s, nil
   131  }
   132  
   133  // Operation implements backend.Enhanced
   134  //
   135  // This will initialize an in-memory terraform.Context to perform the
   136  // operation within this process.
   137  //
   138  // The given operation parameter will be merged with the ContextOpts on
   139  // the structure with the following rules. If a rule isn't specified and the
   140  // name conflicts, assume that the field is overwritten if set.
   141  func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.RunningOperation, error) {
   142  	// Determine the function to call for our operation
   143  	var f func(context.Context, *backend.Operation, *backend.RunningOperation)
   144  	switch op.Type {
   145  	case backend.OperationTypeRefresh:
   146  		f = b.opRefresh
   147  	case backend.OperationTypePlan:
   148  		f = b.opPlan
   149  	case backend.OperationTypeApply:
   150  		f = b.opApply
   151  	default:
   152  		return nil, fmt.Errorf(
   153  			"Unsupported operation type: %s\n\n"+
   154  				"This is a bug in Terraform and should be reported. The local backend\n"+
   155  				"is built-in to Terraform and should always support all operations.",
   156  			op.Type)
   157  	}
   158  
   159  	// Lock
   160  	b.opLock.Lock()
   161  
   162  	// Build our running operation
   163  	runningCtx, runningCtxCancel := context.WithCancel(context.Background())
   164  	runningOp := &backend.RunningOperation{Context: runningCtx}
   165  
   166  	// Do it
   167  	go func() {
   168  		defer b.opLock.Unlock()
   169  		defer runningCtxCancel()
   170  		f(ctx, op, runningOp)
   171  	}()
   172  
   173  	// Return
   174  	return runningOp, nil
   175  }
   176  
   177  // Colorize returns the Colorize structure that can be used for colorizing
   178  // output. This is gauranteed to always return a non-nil value and so is useful
   179  // as a helper to wrap any potentially colored strings.
   180  func (b *Local) Colorize() *colorstring.Colorize {
   181  	if b.CLIColor != nil {
   182  		return b.CLIColor
   183  	}
   184  
   185  	return &colorstring.Colorize{
   186  		Colors:  colorstring.DefaultColors,
   187  		Disable: true,
   188  	}
   189  }
   190  
   191  func (b *Local) init() {
   192  	b.schema = &schema.Backend{
   193  		Schema: map[string]*schema.Schema{
   194  			"path": &schema.Schema{
   195  				Type:     schema.TypeString,
   196  				Optional: true,
   197  			},
   198  		},
   199  
   200  		ConfigureFunc: b.schemaConfigure,
   201  	}
   202  }
   203  
   204  func (b *Local) schemaConfigure(ctx context.Context) error {
   205  	d := schema.FromContextBackendConfig(ctx)
   206  
   207  	// Set the path if it is set
   208  	pathRaw, ok := d.GetOk("path")
   209  	if ok {
   210  		path := pathRaw.(string)
   211  		if path == "" {
   212  			return fmt.Errorf("configured path is empty")
   213  		}
   214  
   215  		b.StatePath = path
   216  	}
   217  
   218  	return nil
   219  }