github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/backend/local/testing.go (about)

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