github.com/kcburge/terraform@v0.11.12-beta1/backend/remote-state/inmem/backend.go (about)

     1  package inmem
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"sort"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/hashicorp/terraform/backend"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/hashicorp/terraform/state"
    14  	"github.com/hashicorp/terraform/state/remote"
    15  	"github.com/hashicorp/terraform/terraform"
    16  )
    17  
    18  // we keep the states and locks in package-level variables, so that they can be
    19  // accessed from multiple instances of the backend. This better emulates
    20  // backend instances accessing a single remote data store.
    21  var (
    22  	states stateMap
    23  	locks  lockMap
    24  )
    25  
    26  func init() {
    27  	Reset()
    28  }
    29  
    30  // Reset clears out all existing state and lock data.
    31  // This is used to initialize the package during init, as well as between
    32  // tests.
    33  func Reset() {
    34  	states = stateMap{
    35  		m: map[string]*remote.State{},
    36  	}
    37  
    38  	locks = lockMap{
    39  		m: map[string]*state.LockInfo{},
    40  	}
    41  }
    42  
    43  // New creates a new backend for Inmem remote state.
    44  func New() backend.Backend {
    45  	// Set the schema
    46  	s := &schema.Backend{
    47  		Schema: map[string]*schema.Schema{
    48  			"lock_id": &schema.Schema{
    49  				Type:        schema.TypeString,
    50  				Optional:    true,
    51  				Description: "initializes the state in a locked configuration",
    52  			},
    53  		},
    54  	}
    55  	backend := &Backend{Backend: s}
    56  	backend.Backend.ConfigureFunc = backend.configure
    57  	return backend
    58  }
    59  
    60  type Backend struct {
    61  	*schema.Backend
    62  }
    63  
    64  func (b *Backend) configure(ctx context.Context) error {
    65  	states.Lock()
    66  	defer states.Unlock()
    67  
    68  	defaultClient := &RemoteClient{
    69  		Name: backend.DefaultStateName,
    70  	}
    71  
    72  	states.m[backend.DefaultStateName] = &remote.State{
    73  		Client: defaultClient,
    74  	}
    75  
    76  	// set the default client lock info per the test config
    77  	data := schema.FromContextBackendConfig(ctx)
    78  	if v, ok := data.GetOk("lock_id"); ok && v.(string) != "" {
    79  		info := state.NewLockInfo()
    80  		info.ID = v.(string)
    81  		info.Operation = "test"
    82  		info.Info = "test config"
    83  
    84  		locks.lock(backend.DefaultStateName, info)
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  func (b *Backend) States() ([]string, error) {
    91  	states.Lock()
    92  	defer states.Unlock()
    93  
    94  	var workspaces []string
    95  
    96  	for s := range states.m {
    97  		workspaces = append(workspaces, s)
    98  	}
    99  
   100  	sort.Strings(workspaces)
   101  	return workspaces, nil
   102  }
   103  
   104  func (b *Backend) DeleteState(name string) error {
   105  	states.Lock()
   106  	defer states.Unlock()
   107  
   108  	if name == backend.DefaultStateName || name == "" {
   109  		return fmt.Errorf("can't delete default state")
   110  	}
   111  
   112  	delete(states.m, name)
   113  	return nil
   114  }
   115  
   116  func (b *Backend) State(name string) (state.State, error) {
   117  	states.Lock()
   118  	defer states.Unlock()
   119  
   120  	s := states.m[name]
   121  	if s == nil {
   122  		s = &remote.State{
   123  			Client: &RemoteClient{
   124  				Name: name,
   125  			},
   126  		}
   127  		states.m[name] = s
   128  
   129  		// to most closely replicate other implementations, we are going to
   130  		// take a lock and create a new state if it doesn't exist.
   131  		lockInfo := state.NewLockInfo()
   132  		lockInfo.Operation = "init"
   133  		lockID, err := s.Lock(lockInfo)
   134  		if err != nil {
   135  			return nil, fmt.Errorf("failed to lock inmem state: %s", err)
   136  		}
   137  		defer s.Unlock(lockID)
   138  
   139  		// If we have no state, we have to create an empty state
   140  		if v := s.State(); v == nil {
   141  			if err := s.WriteState(terraform.NewState()); err != nil {
   142  				return nil, err
   143  			}
   144  			if err := s.PersistState(); err != nil {
   145  				return nil, err
   146  			}
   147  		}
   148  	}
   149  
   150  	return s, nil
   151  }
   152  
   153  type stateMap struct {
   154  	sync.Mutex
   155  	m map[string]*remote.State
   156  }
   157  
   158  // Global level locks for inmem backends.
   159  type lockMap struct {
   160  	sync.Mutex
   161  	m map[string]*state.LockInfo
   162  }
   163  
   164  func (l *lockMap) lock(name string, info *state.LockInfo) (string, error) {
   165  	l.Lock()
   166  	defer l.Unlock()
   167  
   168  	lockInfo := l.m[name]
   169  	if lockInfo != nil {
   170  		lockErr := &state.LockError{
   171  			Info: lockInfo,
   172  		}
   173  
   174  		lockErr.Err = errors.New("state locked")
   175  		// make a copy of the lock info to avoid any testing shenanigans
   176  		*lockErr.Info = *lockInfo
   177  		return "", lockErr
   178  	}
   179  
   180  	info.Created = time.Now().UTC()
   181  	l.m[name] = info
   182  
   183  	return info.ID, nil
   184  }
   185  
   186  func (l *lockMap) unlock(name, id string) error {
   187  	l.Lock()
   188  	defer l.Unlock()
   189  
   190  	lockInfo := l.m[name]
   191  
   192  	if lockInfo == nil {
   193  		return errors.New("state not locked")
   194  	}
   195  
   196  	lockErr := &state.LockError{
   197  		Info: &state.LockInfo{},
   198  	}
   199  
   200  	if id != lockInfo.ID {
   201  		lockErr.Err = errors.New("invalid lock id")
   202  		*lockErr.Info = *lockInfo
   203  		return lockErr
   204  	}
   205  
   206  	delete(l.m, name)
   207  	return nil
   208  }