github.com/loicalbertin/terraform@v0.6.15-0.20170626182346-8e2583055467/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  	DefaultWorkspaceDir    = "terraform.tfstate.d"
    24  	DefaultWorkspaceFile   = "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 backend config, and may be left blank
    40  	// to 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  	// StateWorkspaceDir is the path to the folder containing data for
    52  	// non-default workspaces. This defaults to DefaultWorkspaceDir if not set.
    53  	StatePath         string
    54  	StateOutPath      string
    55  	StateBackupPath   string
    56  	StateWorkspaceDir 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.stateWorkspaceDir())
   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.stateWorkspaceDir(), 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  			"workspace_dir": &schema.Schema{
   296  				Type:     schema.TypeString,
   297  				Optional: true,
   298  				Default:  "",
   299  			},
   300  
   301  			"environment_dir": &schema.Schema{
   302  				Type:          schema.TypeString,
   303  				Optional:      true,
   304  				Default:       "",
   305  				ConflictsWith: []string{"workspace_dir"},
   306  
   307  				Deprecated: "workspace_dir should be used instead, with the same meaning",
   308  			},
   309  		},
   310  
   311  		ConfigureFunc: b.schemaConfigure,
   312  	}
   313  }
   314  
   315  func (b *Local) schemaConfigure(ctx context.Context) error {
   316  	d := schema.FromContextBackendConfig(ctx)
   317  
   318  	// Set the path if it is set
   319  	pathRaw, ok := d.GetOk("path")
   320  	if ok {
   321  		path := pathRaw.(string)
   322  		if path == "" {
   323  			return fmt.Errorf("configured path is empty")
   324  		}
   325  
   326  		b.StatePath = path
   327  		b.StateOutPath = path
   328  	}
   329  
   330  	if raw, ok := d.GetOk("workspace_dir"); ok {
   331  		path := raw.(string)
   332  		if path != "" {
   333  			b.StateWorkspaceDir = path
   334  		}
   335  	}
   336  
   337  	// Legacy name, which ConflictsWith workspace_dir
   338  	if raw, ok := d.GetOk("environment_dir"); ok {
   339  		path := raw.(string)
   340  		if path != "" {
   341  			b.StateWorkspaceDir = path
   342  		}
   343  	}
   344  
   345  	return nil
   346  }
   347  
   348  // StatePaths returns the StatePath, StateOutPath, and StateBackupPath as
   349  // configured from the CLI.
   350  func (b *Local) StatePaths(name string) (string, string, string) {
   351  	statePath := b.StatePath
   352  	stateOutPath := b.StateOutPath
   353  	backupPath := b.StateBackupPath
   354  
   355  	if name == "" {
   356  		name = backend.DefaultStateName
   357  	}
   358  
   359  	if name == backend.DefaultStateName {
   360  		if statePath == "" {
   361  			statePath = DefaultStateFilename
   362  		}
   363  	} else {
   364  		statePath = filepath.Join(b.stateWorkspaceDir(), name, DefaultStateFilename)
   365  	}
   366  
   367  	if stateOutPath == "" {
   368  		stateOutPath = statePath
   369  	}
   370  
   371  	switch backupPath {
   372  	case "-":
   373  		backupPath = ""
   374  	case "":
   375  		backupPath = stateOutPath + DefaultBackupExtension
   376  	}
   377  
   378  	return statePath, stateOutPath, backupPath
   379  }
   380  
   381  // this only ensures that the named directory exists
   382  func (b *Local) createState(name string) error {
   383  	if name == backend.DefaultStateName {
   384  		return nil
   385  	}
   386  
   387  	stateDir := filepath.Join(b.stateWorkspaceDir(), name)
   388  	s, err := os.Stat(stateDir)
   389  	if err == nil && s.IsDir() {
   390  		// no need to check for os.IsNotExist, since that is covered by os.MkdirAll
   391  		// which will catch the other possible errors as well.
   392  		return nil
   393  	}
   394  
   395  	err = os.MkdirAll(stateDir, 0755)
   396  	if err != nil {
   397  		return err
   398  	}
   399  
   400  	return nil
   401  }
   402  
   403  // stateWorkspaceDir returns the directory where state environments are stored.
   404  func (b *Local) stateWorkspaceDir() string {
   405  	if b.StateWorkspaceDir != "" {
   406  		return b.StateWorkspaceDir
   407  	}
   408  
   409  	return DefaultWorkspaceDir
   410  }
   411  
   412  func (b *Local) pluginInitRequired(providerErr *terraform.ResourceProviderError) {
   413  	b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   414  		strings.TrimSpace(errPluginInit)+"\n",
   415  		providerErr)))
   416  }
   417  
   418  // this relies on multierror to format the plugin errors below the copy
   419  const errPluginInit = `
   420  [reset][bold][yellow]Plugin reinitialization required. Please run "terraform init".[reset]
   421  [yellow]Reason: Could not satisfy plugin requirements.
   422  
   423  Plugins are external binaries that Terraform uses to access and manipulate
   424  resources. The configuration provided requires plugins which can't be located,
   425  don't satisfy the version constraints, or are otherwise incompatible.
   426  
   427  [reset][red]%s
   428  
   429  [reset][yellow]Terraform automatically discovers provider requirements from your
   430  configuration, including providers used in child modules. To see the
   431  requirements and constraints from each module, run "terraform providers".
   432  `