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