github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/backend/local/backend.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"sort"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/hashicorp/terraform/backend"
    15  	"github.com/hashicorp/terraform/helper/schema"
    16  	"github.com/hashicorp/terraform/state"
    17  	"github.com/hashicorp/terraform/terraform"
    18  	"github.com/mitchellh/cli"
    19  	"github.com/mitchellh/colorstring"
    20  )
    21  
    22  const (
    23  	DefaultEnvDir          = "terraform.tfstate.d"
    24  	DefaultEnvFile         = "environment"
    25  	DefaultStateFilename   = "terraform.tfstate"
    26  	DefaultDataDir         = ".terraform"
    27  	DefaultBackupExtension = ".backup"
    28  )
    29  
    30  // Local is an implementation of EnhancedBackend that performs all operations
    31  // locally. This is the "default" backend and implements normal Terraform
    32  // behavior as it is well known.
    33  type Local struct {
    34  	// CLI and Colorize control the CLI output. If CLI is nil then no CLI
    35  	// output will be done. If CLIColor is nil then no coloring will be done.
    36  	CLI      cli.Ui
    37  	CLIColor *colorstring.Colorize
    38  
    39  	// The State* paths are set from the CLI options, and may be left blank to
    40  	// use the defaults. If the actual paths for the local backend state are
    41  	// needed, use the StatePaths method.
    42  	//
    43  	// StatePath is the local path where state is read from.
    44  	//
    45  	// StateOutPath is the local path where the state will be written.
    46  	// If this is empty, it will default to StatePath.
    47  	//
    48  	// StateBackupPath is the local path where a backup file will be written.
    49  	// Set this to "-" to disable state backup.
    50  	//
    51  	// StateEnvPath is the path to the folder containing environments. This
    52  	// defaults to DefaultEnvDir if not set.
    53  	StatePath       string
    54  	StateOutPath    string
    55  	StateBackupPath string
    56  	StateEnvDir     string
    57  
    58  	// We only want to create a single instance of a local state, so store them
    59  	// here as they're loaded.
    60  	states map[string]state.State
    61  
    62  	// Terraform context. Many of these will be overridden or merged by
    63  	// Operation. See Operation for more details.
    64  	ContextOpts *terraform.ContextOpts
    65  
    66  	// OpInput will ask for necessary input prior to performing any operations.
    67  	//
    68  	// OpValidation will perform validation prior to running an operation. The
    69  	// variable naming doesn't match the style of others since we have a func
    70  	// Validate.
    71  	OpInput      bool
    72  	OpValidation bool
    73  
    74  	// Backend, if non-nil, will use this backend for non-enhanced behavior.
    75  	// This allows local behavior with remote state storage. It is a way to
    76  	// "upgrade" a non-enhanced backend to an enhanced backend with typical
    77  	// behavior.
    78  	//
    79  	// If this is nil, local performs normal state loading and storage.
    80  	Backend backend.Backend
    81  
    82  	schema *schema.Backend
    83  	opLock sync.Mutex
    84  	once   sync.Once
    85  }
    86  
    87  func (b *Local) Input(
    88  	ui terraform.UIInput, c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) {
    89  	b.once.Do(b.init)
    90  
    91  	f := b.schema.Input
    92  	if b.Backend != nil {
    93  		f = b.Backend.Input
    94  	}
    95  
    96  	return f(ui, c)
    97  }
    98  
    99  func (b *Local) Validate(c *terraform.ResourceConfig) ([]string, []error) {
   100  	b.once.Do(b.init)
   101  
   102  	f := b.schema.Validate
   103  	if b.Backend != nil {
   104  		f = b.Backend.Validate
   105  	}
   106  
   107  	return f(c)
   108  }
   109  
   110  func (b *Local) Configure(c *terraform.ResourceConfig) error {
   111  	b.once.Do(b.init)
   112  
   113  	f := b.schema.Configure
   114  	if b.Backend != nil {
   115  		f = b.Backend.Configure
   116  	}
   117  
   118  	return f(c)
   119  }
   120  
   121  func (b *Local) States() ([]string, error) {
   122  	// If we have a backend handling state, defer to that.
   123  	if b.Backend != nil {
   124  		return b.Backend.States()
   125  	}
   126  
   127  	// the listing always start with "default"
   128  	envs := []string{backend.DefaultStateName}
   129  
   130  	entries, err := ioutil.ReadDir(b.stateEnvDir())
   131  	// no error if there's no envs configured
   132  	if os.IsNotExist(err) {
   133  		return envs, nil
   134  	}
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	var listed []string
   140  	for _, entry := range entries {
   141  		if entry.IsDir() {
   142  			listed = append(listed, filepath.Base(entry.Name()))
   143  		}
   144  	}
   145  
   146  	sort.Strings(listed)
   147  	envs = append(envs, listed...)
   148  
   149  	return envs, nil
   150  }
   151  
   152  // DeleteState removes a named state.
   153  // The "default" state cannot be removed.
   154  func (b *Local) DeleteState(name string) error {
   155  	// If we have a backend handling state, defer to that.
   156  	if b.Backend != nil {
   157  		return b.Backend.DeleteState(name)
   158  	}
   159  
   160  	if name == "" {
   161  		return errors.New("empty state name")
   162  	}
   163  
   164  	if name == backend.DefaultStateName {
   165  		return errors.New("cannot delete default state")
   166  	}
   167  
   168  	delete(b.states, name)
   169  	return os.RemoveAll(filepath.Join(b.stateEnvDir(), name))
   170  }
   171  
   172  func (b *Local) State(name string) (state.State, error) {
   173  	statePath, stateOutPath, backupPath := b.StatePaths(name)
   174  
   175  	// If we have a backend handling state, defer to that.
   176  	if b.Backend != nil {
   177  		s, err := b.Backend.State(name)
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  
   182  		// make sure we always have a backup state, unless it disabled
   183  		if backupPath == "" {
   184  			return s, nil
   185  		}
   186  
   187  		// see if the delegated backend returned a BackupState of its own
   188  		if s, ok := s.(*state.BackupState); ok {
   189  			return s, nil
   190  		}
   191  
   192  		s = &state.BackupState{
   193  			Real: s,
   194  			Path: backupPath,
   195  		}
   196  		return s, nil
   197  	}
   198  
   199  	if s, ok := b.states[name]; ok {
   200  		return s, nil
   201  	}
   202  
   203  	if err := b.createState(name); err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	// Otherwise, we need to load the state.
   208  	var s state.State = &state.LocalState{
   209  		Path:    statePath,
   210  		PathOut: stateOutPath,
   211  	}
   212  
   213  	// If we are backing up the state, wrap it
   214  	if backupPath != "" {
   215  		s = &state.BackupState{
   216  			Real: s,
   217  			Path: backupPath,
   218  		}
   219  	}
   220  
   221  	if b.states == nil {
   222  		b.states = map[string]state.State{}
   223  	}
   224  	b.states[name] = s
   225  	return s, nil
   226  }
   227  
   228  // Operation implements backend.Enhanced
   229  //
   230  // This will initialize an in-memory terraform.Context to perform the
   231  // operation within this process.
   232  //
   233  // The given operation parameter will be merged with the ContextOpts on
   234  // the structure with the following rules. If a rule isn't specified and the
   235  // name conflicts, assume that the field is overwritten if set.
   236  func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.RunningOperation, error) {
   237  	// Determine the function to call for our operation
   238  	var f func(context.Context, *backend.Operation, *backend.RunningOperation)
   239  	switch op.Type {
   240  	case backend.OperationTypeRefresh:
   241  		f = b.opRefresh
   242  	case backend.OperationTypePlan:
   243  		f = b.opPlan
   244  	case backend.OperationTypeApply:
   245  		f = b.opApply
   246  	default:
   247  		return nil, fmt.Errorf(
   248  			"Unsupported operation type: %s\n\n"+
   249  				"This is a bug in Terraform and should be reported. The local backend\n"+
   250  				"is built-in to Terraform and should always support all operations.",
   251  			op.Type)
   252  	}
   253  
   254  	// Lock
   255  	b.opLock.Lock()
   256  
   257  	// Build our running operation
   258  	runningCtx, runningCtxCancel := context.WithCancel(context.Background())
   259  	runningOp := &backend.RunningOperation{Context: runningCtx}
   260  
   261  	// Do it
   262  	go func() {
   263  		defer b.opLock.Unlock()
   264  		defer runningCtxCancel()
   265  		f(ctx, op, runningOp)
   266  	}()
   267  
   268  	// Return
   269  	return runningOp, nil
   270  }
   271  
   272  // Colorize returns the Colorize structure that can be used for colorizing
   273  // output. This is gauranteed to always return a non-nil value and so is useful
   274  // as a helper to wrap any potentially colored strings.
   275  func (b *Local) Colorize() *colorstring.Colorize {
   276  	if b.CLIColor != nil {
   277  		return b.CLIColor
   278  	}
   279  
   280  	return &colorstring.Colorize{
   281  		Colors:  colorstring.DefaultColors,
   282  		Disable: true,
   283  	}
   284  }
   285  
   286  func (b *Local) init() {
   287  	b.schema = &schema.Backend{
   288  		Schema: map[string]*schema.Schema{
   289  			"path": &schema.Schema{
   290  				Type:     schema.TypeString,
   291  				Optional: true,
   292  				Default:  "",
   293  			},
   294  
   295  			"environment_dir": &schema.Schema{
   296  				Type:     schema.TypeString,
   297  				Optional: true,
   298  				Default:  "",
   299  			},
   300  		},
   301  
   302  		ConfigureFunc: b.schemaConfigure,
   303  	}
   304  }
   305  
   306  func (b *Local) schemaConfigure(ctx context.Context) error {
   307  	d := schema.FromContextBackendConfig(ctx)
   308  
   309  	// Set the path if it is set
   310  	pathRaw, ok := d.GetOk("path")
   311  	if ok {
   312  		path := pathRaw.(string)
   313  		if path == "" {
   314  			return fmt.Errorf("configured path is empty")
   315  		}
   316  
   317  		b.StatePath = path
   318  		b.StateOutPath = path
   319  	}
   320  
   321  	if raw, ok := d.GetOk("environment_dir"); ok {
   322  		path := raw.(string)
   323  		if path != "" {
   324  			b.StateEnvDir = path
   325  		}
   326  	}
   327  
   328  	return nil
   329  }
   330  
   331  // StatePaths returns the StatePath, StateOutPath, and StateBackupPath as
   332  // configured from the CLI.
   333  func (b *Local) StatePaths(name string) (string, string, string) {
   334  	statePath := b.StatePath
   335  	stateOutPath := b.StateOutPath
   336  	backupPath := b.StateBackupPath
   337  
   338  	if name == "" {
   339  		name = backend.DefaultStateName
   340  	}
   341  
   342  	if name == backend.DefaultStateName {
   343  		if statePath == "" {
   344  			statePath = DefaultStateFilename
   345  		}
   346  	} else {
   347  		statePath = filepath.Join(b.stateEnvDir(), name, DefaultStateFilename)
   348  	}
   349  
   350  	if stateOutPath == "" {
   351  		stateOutPath = statePath
   352  	}
   353  
   354  	switch backupPath {
   355  	case "-":
   356  		backupPath = ""
   357  	case "":
   358  		backupPath = stateOutPath + DefaultBackupExtension
   359  	}
   360  
   361  	return statePath, stateOutPath, backupPath
   362  }
   363  
   364  // this only ensures that the named directory exists
   365  func (b *Local) createState(name string) error {
   366  	if name == backend.DefaultStateName {
   367  		return nil
   368  	}
   369  
   370  	stateDir := filepath.Join(b.stateEnvDir(), name)
   371  	s, err := os.Stat(stateDir)
   372  	if err == nil && s.IsDir() {
   373  		// no need to check for os.IsNotExist, since that is covered by os.MkdirAll
   374  		// which will catch the other possible errors as well.
   375  		return nil
   376  	}
   377  
   378  	err = os.MkdirAll(stateDir, 0755)
   379  	if err != nil {
   380  		return err
   381  	}
   382  
   383  	return nil
   384  }
   385  
   386  // stateEnvDir returns the directory where state environments are stored.
   387  func (b *Local) stateEnvDir() string {
   388  	if b.StateEnvDir != "" {
   389  		return b.StateEnvDir
   390  	}
   391  
   392  	return DefaultEnvDir
   393  }
   394  
   395  // currentStateName returns the name of the current named state as set in the
   396  // configuration files.
   397  // If there are no configured environments, currentStateName returns "default"
   398  func (b *Local) currentStateName() (string, error) {
   399  	contents, err := ioutil.ReadFile(filepath.Join(DefaultDataDir, DefaultEnvFile))
   400  	if os.IsNotExist(err) {
   401  		return backend.DefaultStateName, nil
   402  	}
   403  	if err != nil {
   404  		return "", err
   405  	}
   406  
   407  	if fromFile := strings.TrimSpace(string(contents)); fromFile != "" {
   408  		return fromFile, nil
   409  	}
   410  
   411  	return backend.DefaultStateName, nil
   412  }