kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/backend/local/testing.go (about)

     1  package local
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"testing"
     7  
     8  	"github.com/zclconf/go-cty/cty"
     9  
    10  	"kubeform.dev/terraform-backend-sdk/addrs"
    11  	"kubeform.dev/terraform-backend-sdk/backend"
    12  	"kubeform.dev/terraform-backend-sdk/configs/configschema"
    13  	"kubeform.dev/terraform-backend-sdk/providers"
    14  	"kubeform.dev/terraform-backend-sdk/states"
    15  	"kubeform.dev/terraform-backend-sdk/states/statemgr"
    16  	"kubeform.dev/terraform-backend-sdk/terraform"
    17  )
    18  
    19  // TestLocal returns a configured Local struct with temporary paths and
    20  // in-memory ContextOpts.
    21  //
    22  // No operations will be called on the returned value, so you can still set
    23  // public fields without any locks.
    24  func TestLocal(t *testing.T) (*Local, func()) {
    25  	t.Helper()
    26  	tempDir := t.TempDir()
    27  
    28  	local := New()
    29  	local.StatePath = filepath.Join(tempDir, "state.tfstate")
    30  	local.StateOutPath = filepath.Join(tempDir, "state.tfstate")
    31  	local.StateBackupPath = filepath.Join(tempDir, "state.tfstate.bak")
    32  	local.StateWorkspaceDir = filepath.Join(tempDir, "state.tfstate.d")
    33  	local.ContextOpts = &terraform.ContextOpts{}
    34  
    35  	cleanup := func() {
    36  		if err := os.RemoveAll(tempDir); err != nil {
    37  			t.Fatal("error cleanup up test:", err)
    38  		}
    39  	}
    40  
    41  	return local, cleanup
    42  }
    43  
    44  // TestLocalProvider modifies the ContextOpts of the *Local parameter to
    45  // have a provider with the given name.
    46  func TestLocalProvider(t *testing.T, b *Local, name string, schema *terraform.ProviderSchema) *terraform.MockProvider {
    47  	// Build a mock resource provider for in-memory operations
    48  	p := new(terraform.MockProvider)
    49  
    50  	if schema == nil {
    51  		schema = &terraform.ProviderSchema{} // default schema is empty
    52  	}
    53  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
    54  		Provider:      providers.Schema{Block: schema.Provider},
    55  		ProviderMeta:  providers.Schema{Block: schema.ProviderMeta},
    56  		ResourceTypes: map[string]providers.Schema{},
    57  		DataSources:   map[string]providers.Schema{},
    58  	}
    59  	for name, res := range schema.ResourceTypes {
    60  		p.GetProviderSchemaResponse.ResourceTypes[name] = providers.Schema{
    61  			Block:   res,
    62  			Version: int64(schema.ResourceTypeSchemaVersions[name]),
    63  		}
    64  	}
    65  	for name, dat := range schema.DataSources {
    66  		p.GetProviderSchemaResponse.DataSources[name] = providers.Schema{Block: dat}
    67  	}
    68  
    69  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
    70  		rSchema, _ := schema.SchemaForResourceType(addrs.ManagedResourceMode, req.TypeName)
    71  		if rSchema == nil {
    72  			rSchema = &configschema.Block{} // default schema is empty
    73  		}
    74  		plannedVals := map[string]cty.Value{}
    75  		for name, attrS := range rSchema.Attributes {
    76  			val := req.ProposedNewState.GetAttr(name)
    77  			if attrS.Computed && val.IsNull() {
    78  				val = cty.UnknownVal(attrS.Type)
    79  			}
    80  			plannedVals[name] = val
    81  		}
    82  		for name := range rSchema.BlockTypes {
    83  			// For simplicity's sake we just copy the block attributes over
    84  			// verbatim, since this package's mock providers are all relatively
    85  			// simple -- we're testing the backend, not esoteric provider features.
    86  			plannedVals[name] = req.ProposedNewState.GetAttr(name)
    87  		}
    88  
    89  		return providers.PlanResourceChangeResponse{
    90  			PlannedState:   cty.ObjectVal(plannedVals),
    91  			PlannedPrivate: req.PriorPrivate,
    92  		}
    93  	}
    94  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
    95  		return providers.ReadResourceResponse{NewState: req.PriorState}
    96  	}
    97  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
    98  		return providers.ReadDataSourceResponse{State: req.Config}
    99  	}
   100  
   101  	// Initialize the opts
   102  	if b.ContextOpts == nil {
   103  		b.ContextOpts = &terraform.ContextOpts{}
   104  	}
   105  
   106  	// Set up our provider
   107  	b.ContextOpts.Providers = map[addrs.Provider]providers.Factory{
   108  		addrs.NewDefaultProvider(name): providers.FactoryFixed(p),
   109  	}
   110  
   111  	return p
   112  
   113  }
   114  
   115  // TestLocalSingleState is a backend implementation that wraps Local
   116  // and modifies it to only support single states (returns
   117  // ErrWorkspacesNotSupported for multi-state operations).
   118  //
   119  // This isn't an actual use case, this is exported just to provide a
   120  // easy way to test that behavior.
   121  type TestLocalSingleState struct {
   122  	*Local
   123  }
   124  
   125  // TestNewLocalSingle is a factory for creating a TestLocalSingleState.
   126  // This function matches the signature required for backend/init.
   127  func TestNewLocalSingle() backend.Backend {
   128  	return &TestLocalSingleState{Local: New()}
   129  }
   130  
   131  func (b *TestLocalSingleState) Workspaces() ([]string, error) {
   132  	return nil, backend.ErrWorkspacesNotSupported
   133  }
   134  
   135  func (b *TestLocalSingleState) DeleteWorkspace(string) error {
   136  	return backend.ErrWorkspacesNotSupported
   137  }
   138  
   139  func (b *TestLocalSingleState) StateMgr(name string) (statemgr.Full, error) {
   140  	if name != backend.DefaultStateName {
   141  		return nil, backend.ErrWorkspacesNotSupported
   142  	}
   143  
   144  	return b.Local.StateMgr(name)
   145  }
   146  
   147  // TestLocalNoDefaultState is a backend implementation that wraps
   148  // Local and modifies it to support named states, but not the
   149  // default state. It returns ErrDefaultWorkspaceNotSupported when
   150  // the DefaultStateName is used.
   151  type TestLocalNoDefaultState struct {
   152  	*Local
   153  }
   154  
   155  // TestNewLocalNoDefault is a factory for creating a TestLocalNoDefaultState.
   156  // This function matches the signature required for backend/init.
   157  func TestNewLocalNoDefault() backend.Backend {
   158  	return &TestLocalNoDefaultState{Local: New()}
   159  }
   160  
   161  func (b *TestLocalNoDefaultState) Workspaces() ([]string, error) {
   162  	workspaces, err := b.Local.Workspaces()
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	filtered := workspaces[:0]
   168  	for _, name := range workspaces {
   169  		if name != backend.DefaultStateName {
   170  			filtered = append(filtered, name)
   171  		}
   172  	}
   173  
   174  	return filtered, nil
   175  }
   176  
   177  func (b *TestLocalNoDefaultState) DeleteWorkspace(name string) error {
   178  	if name == backend.DefaultStateName {
   179  		return backend.ErrDefaultWorkspaceNotSupported
   180  	}
   181  	return b.Local.DeleteWorkspace(name)
   182  }
   183  
   184  func (b *TestLocalNoDefaultState) StateMgr(name string) (statemgr.Full, error) {
   185  	if name == backend.DefaultStateName {
   186  		return nil, backend.ErrDefaultWorkspaceNotSupported
   187  	}
   188  	return b.Local.StateMgr(name)
   189  }
   190  
   191  func testStateFile(t *testing.T, path string, s *states.State) {
   192  	stateFile := statemgr.NewFilesystem(path)
   193  	stateFile.WriteState(s)
   194  }
   195  
   196  func mustProviderConfig(s string) addrs.AbsProviderConfig {
   197  	p, diags := addrs.ParseAbsProviderConfigStr(s)
   198  	if diags.HasErrors() {
   199  		panic(diags.Err())
   200  	}
   201  	return p
   202  }
   203  
   204  func mustResourceInstanceAddr(s string) addrs.AbsResourceInstance {
   205  	addr, diags := addrs.ParseAbsResourceInstanceStr(s)
   206  	if diags.HasErrors() {
   207  		panic(diags.Err())
   208  	}
   209  	return addr
   210  }
   211  
   212  // assertBackendStateUnlocked attempts to lock the backend state. Failure
   213  // indicates that the state was indeed locked and therefore this function will
   214  // return true.
   215  func assertBackendStateUnlocked(t *testing.T, b *Local) bool {
   216  	t.Helper()
   217  	stateMgr, _ := b.StateMgr(backend.DefaultStateName)
   218  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   219  		t.Errorf("state is already locked: %s", err.Error())
   220  		return false
   221  	}
   222  	return true
   223  }
   224  
   225  // assertBackendStateLocked attempts to lock the backend state. Failure
   226  // indicates that the state was already locked and therefore this function will
   227  // return false.
   228  func assertBackendStateLocked(t *testing.T, b *Local) bool {
   229  	t.Helper()
   230  	stateMgr, _ := b.StateMgr(backend.DefaultStateName)
   231  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   232  		return true
   233  	}
   234  	t.Error("unexpected success locking state")
   235  	return true
   236  }