github.com/sixgill/terraform@v0.9.0-beta2.0.20170316214032-033f6226ae50/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  	// If we have a backend handling state, defer to that.
   174  	if b.Backend != nil {
   175  		return b.Backend.State(name)
   176  	}
   177  
   178  	if s, ok := b.states[name]; ok {
   179  		return s, nil
   180  	}
   181  
   182  	if err := b.createState(name); err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	statePath, stateOutPath, backupPath := b.StatePaths(name)
   187  
   188  	// Otherwise, we need to load the state.
   189  	var s state.State = &state.LocalState{
   190  		Path:    statePath,
   191  		PathOut: stateOutPath,
   192  	}
   193  
   194  	// If we are backing up the state, wrap it
   195  	if backupPath != "" {
   196  		s = &state.BackupState{
   197  			Real: s,
   198  			Path: backupPath,
   199  		}
   200  	}
   201  
   202  	if b.states == nil {
   203  		b.states = map[string]state.State{}
   204  	}
   205  	b.states[name] = s
   206  	return s, nil
   207  }
   208  
   209  // Operation implements backend.Enhanced
   210  //
   211  // This will initialize an in-memory terraform.Context to perform the
   212  // operation within this process.
   213  //
   214  // The given operation parameter will be merged with the ContextOpts on
   215  // the structure with the following rules. If a rule isn't specified and the
   216  // name conflicts, assume that the field is overwritten if set.
   217  func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.RunningOperation, error) {
   218  	// Determine the function to call for our operation
   219  	var f func(context.Context, *backend.Operation, *backend.RunningOperation)
   220  	switch op.Type {
   221  	case backend.OperationTypeRefresh:
   222  		f = b.opRefresh
   223  	case backend.OperationTypePlan:
   224  		f = b.opPlan
   225  	case backend.OperationTypeApply:
   226  		f = b.opApply
   227  	default:
   228  		return nil, fmt.Errorf(
   229  			"Unsupported operation type: %s\n\n"+
   230  				"This is a bug in Terraform and should be reported. The local backend\n"+
   231  				"is built-in to Terraform and should always support all operations.",
   232  			op.Type)
   233  	}
   234  
   235  	// Lock
   236  	b.opLock.Lock()
   237  
   238  	// Build our running operation
   239  	runningCtx, runningCtxCancel := context.WithCancel(context.Background())
   240  	runningOp := &backend.RunningOperation{Context: runningCtx}
   241  
   242  	// Do it
   243  	go func() {
   244  		defer b.opLock.Unlock()
   245  		defer runningCtxCancel()
   246  		f(ctx, op, runningOp)
   247  	}()
   248  
   249  	// Return
   250  	return runningOp, nil
   251  }
   252  
   253  // Colorize returns the Colorize structure that can be used for colorizing
   254  // output. This is gauranteed to always return a non-nil value and so is useful
   255  // as a helper to wrap any potentially colored strings.
   256  func (b *Local) Colorize() *colorstring.Colorize {
   257  	if b.CLIColor != nil {
   258  		return b.CLIColor
   259  	}
   260  
   261  	return &colorstring.Colorize{
   262  		Colors:  colorstring.DefaultColors,
   263  		Disable: true,
   264  	}
   265  }
   266  
   267  func (b *Local) init() {
   268  	b.schema = &schema.Backend{
   269  		Schema: map[string]*schema.Schema{
   270  			"path": &schema.Schema{
   271  				Type:     schema.TypeString,
   272  				Optional: true,
   273  				Default:  "",
   274  			},
   275  
   276  			"environment_dir": &schema.Schema{
   277  				Type:     schema.TypeString,
   278  				Optional: true,
   279  				Default:  "",
   280  			},
   281  		},
   282  
   283  		ConfigureFunc: b.schemaConfigure,
   284  	}
   285  }
   286  
   287  func (b *Local) schemaConfigure(ctx context.Context) error {
   288  	d := schema.FromContextBackendConfig(ctx)
   289  
   290  	// Set the path if it is set
   291  	pathRaw, ok := d.GetOk("path")
   292  	if ok {
   293  		path := pathRaw.(string)
   294  		if path == "" {
   295  			return fmt.Errorf("configured path is empty")
   296  		}
   297  
   298  		b.StatePath = path
   299  		b.StateOutPath = path
   300  	}
   301  
   302  	if raw, ok := d.GetOk("environment_dir"); ok {
   303  		path := raw.(string)
   304  		if path != "" {
   305  			b.StateEnvDir = path
   306  		}
   307  	}
   308  
   309  	return nil
   310  }
   311  
   312  // StatePaths returns the StatePath, StateOutPath, and StateBackupPath as
   313  // configured from the CLI.
   314  func (b *Local) StatePaths(name string) (string, string, string) {
   315  	statePath := b.StatePath
   316  	stateOutPath := b.StateOutPath
   317  	backupPath := b.StateBackupPath
   318  
   319  	if name == "" {
   320  		name = backend.DefaultStateName
   321  	}
   322  
   323  	if name == backend.DefaultStateName {
   324  		if statePath == "" {
   325  			statePath = DefaultStateFilename
   326  		}
   327  	} else {
   328  		statePath = filepath.Join(b.stateEnvDir(), name, DefaultStateFilename)
   329  	}
   330  
   331  	if stateOutPath == "" {
   332  		stateOutPath = statePath
   333  	}
   334  
   335  	switch backupPath {
   336  	case "-":
   337  		backupPath = ""
   338  	case "":
   339  		backupPath = stateOutPath + DefaultBackupExtension
   340  	}
   341  
   342  	return statePath, stateOutPath, backupPath
   343  }
   344  
   345  // this only ensures that the named directory exists
   346  func (b *Local) createState(name string) error {
   347  	if name == backend.DefaultStateName {
   348  		return nil
   349  	}
   350  
   351  	stateDir := filepath.Join(b.stateEnvDir(), name)
   352  	s, err := os.Stat(stateDir)
   353  	if err == nil && s.IsDir() {
   354  		// no need to check for os.IsNotExist, since that is covered by os.MkdirAll
   355  		// which will catch the other possible errors as well.
   356  		return nil
   357  	}
   358  
   359  	err = os.MkdirAll(stateDir, 0755)
   360  	if err != nil {
   361  		return err
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  // stateEnvDir returns the directory where state environments are stored.
   368  func (b *Local) stateEnvDir() string {
   369  	if b.StateEnvDir != "" {
   370  		return b.StateEnvDir
   371  	}
   372  
   373  	return DefaultEnvDir
   374  }
   375  
   376  // currentStateName returns the name of the current named state as set in the
   377  // configuration files.
   378  // If there are no configured environments, currentStateName returns "default"
   379  func (b *Local) currentStateName() (string, error) {
   380  	contents, err := ioutil.ReadFile(filepath.Join(DefaultDataDir, DefaultEnvFile))
   381  	if os.IsNotExist(err) {
   382  		return backend.DefaultStateName, nil
   383  	}
   384  	if err != nil {
   385  		return "", err
   386  	}
   387  
   388  	if fromFile := strings.TrimSpace(string(contents)); fromFile != "" {
   389  		return fromFile, nil
   390  	}
   391  
   392  	return backend.DefaultStateName, nil
   393  }