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 }