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 }