github.com/hugorut/terraform@v1.1.3/src/backend/remote-state/pg/backend_test.go (about) 1 package pg 2 3 // Create the test database: createdb terraform_backend_pg_test 4 // TF_ACC=1 GO111MODULE=on go test -v -mod=vendor -timeout=2m -parallel=4 github.com/hugorut/terraform/backend/remote-state/pg 5 6 import ( 7 "database/sql" 8 "fmt" 9 "os" 10 "testing" 11 12 "github.com/hugorut/terraform/src/backend" 13 "github.com/hugorut/terraform/src/states/remote" 14 "github.com/hugorut/terraform/src/states/statemgr" 15 "github.com/lib/pq" 16 _ "github.com/lib/pq" 17 ) 18 19 // Function to skip a test unless in ACCeptance test mode. 20 // 21 // A running Postgres server identified by env variable 22 // DATABASE_URL is required for acceptance tests. 23 func testACC(t *testing.T) { 24 skip := os.Getenv("TF_ACC") == "" 25 if skip { 26 t.Log("pg backend tests require setting TF_ACC") 27 t.Skip() 28 } 29 if os.Getenv("DATABASE_URL") == "" { 30 os.Setenv("DATABASE_URL", "postgres://localhost/terraform_backend_pg_test?sslmode=disable") 31 } 32 } 33 34 func TestBackend_impl(t *testing.T) { 35 var _ backend.Backend = new(Backend) 36 } 37 38 func TestBackendConfig(t *testing.T) { 39 testACC(t) 40 connStr := getDatabaseUrl() 41 schemaName := pq.QuoteIdentifier(fmt.Sprintf("terraform_%s", t.Name())) 42 43 config := backend.TestWrapConfig(map[string]interface{}{ 44 "conn_str": connStr, 45 "schema_name": schemaName, 46 }) 47 schemaName = pq.QuoteIdentifier(schemaName) 48 49 dbCleaner, err := sql.Open("postgres", connStr) 50 if err != nil { 51 t.Fatal(err) 52 } 53 defer dbCleaner.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName)) 54 55 b := backend.TestBackendConfig(t, New(), config).(*Backend) 56 57 if b == nil { 58 t.Fatal("Backend could not be configured") 59 } 60 61 _, err = b.db.Query(fmt.Sprintf("SELECT name, data FROM %s.%s LIMIT 1", schemaName, statesTableName)) 62 if err != nil { 63 t.Fatal(err) 64 } 65 66 _, err = b.StateMgr(backend.DefaultStateName) 67 if err != nil { 68 t.Fatal(err) 69 } 70 71 s, err := b.StateMgr(backend.DefaultStateName) 72 if err != nil { 73 t.Fatal(err) 74 } 75 c := s.(*remote.State).Client.(*RemoteClient) 76 if c.Name != backend.DefaultStateName { 77 t.Fatal("RemoteClient name is not configured") 78 } 79 80 backend.TestBackendStates(t, b) 81 } 82 83 func TestBackendConfigSkipOptions(t *testing.T) { 84 testACC(t) 85 connStr := getDatabaseUrl() 86 87 testCases := []struct { 88 Name string 89 SkipSchemaCreation bool 90 SkipTableCreation bool 91 SkipIndexCreation bool 92 TestIndexIsPresent bool 93 Setup func(t *testing.T, db *sql.DB, schemaName string) 94 }{ 95 { 96 Name: "skip_schema_creation", 97 SkipSchemaCreation: true, 98 TestIndexIsPresent: true, 99 Setup: func(t *testing.T, db *sql.DB, schemaName string) { 100 // create the schema as a prerequisites 101 _, err := db.Query(fmt.Sprintf(`CREATE SCHEMA IF NOT EXISTS %s`, schemaName)) 102 if err != nil { 103 t.Fatal(err) 104 } 105 }, 106 }, 107 { 108 Name: "skip_table_creation", 109 SkipTableCreation: true, 110 TestIndexIsPresent: true, 111 Setup: func(t *testing.T, db *sql.DB, schemaName string) { 112 // since the table needs to be already created the schema must be too 113 _, err := db.Query(fmt.Sprintf(`CREATE SCHEMA %s`, schemaName)) 114 if err != nil { 115 t.Fatal(err) 116 } 117 _, err = db.Query(fmt.Sprintf(`CREATE TABLE %s.%s ( 118 id SERIAL PRIMARY KEY, 119 name TEXT, 120 data TEXT 121 )`, schemaName, statesTableName)) 122 if err != nil { 123 t.Fatal(err) 124 } 125 }, 126 }, 127 { 128 Name: "skip_index_creation", 129 SkipIndexCreation: true, 130 TestIndexIsPresent: true, 131 Setup: func(t *testing.T, db *sql.DB, schemaName string) { 132 // Everything need to exists for the index to be created 133 _, err := db.Query(fmt.Sprintf(`CREATE SCHEMA %s`, schemaName)) 134 if err != nil { 135 t.Fatal(err) 136 } 137 _, err = db.Query(fmt.Sprintf(`CREATE TABLE %s.%s ( 138 id SERIAL PRIMARY KEY, 139 name TEXT, 140 data TEXT 141 )`, schemaName, statesTableName)) 142 if err != nil { 143 t.Fatal(err) 144 } 145 _, err = db.Exec(fmt.Sprintf(`CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s.%s (name)`, statesIndexName, schemaName, statesTableName)) 146 if err != nil { 147 t.Fatal(err) 148 } 149 }, 150 }, 151 { 152 Name: "missing_index", 153 SkipIndexCreation: true, 154 }, 155 } 156 157 for _, tc := range testCases { 158 t.Run(tc.Name, func(t *testing.T) { 159 schemaName := tc.Name 160 161 config := backend.TestWrapConfig(map[string]interface{}{ 162 "conn_str": connStr, 163 "schema_name": schemaName, 164 "skip_schema_creation": tc.SkipSchemaCreation, 165 "skip_table_creation": tc.SkipTableCreation, 166 "skip_index_creation": tc.SkipIndexCreation, 167 }) 168 schemaName = pq.QuoteIdentifier(schemaName) 169 db, err := sql.Open("postgres", connStr) 170 if err != nil { 171 t.Fatal(err) 172 } 173 174 if tc.Setup != nil { 175 tc.Setup(t, db, schemaName) 176 } 177 defer db.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName)) 178 179 b := backend.TestBackendConfig(t, New(), config).(*Backend) 180 181 if b == nil { 182 t.Fatal("Backend could not be configured") 183 } 184 185 // Make sure everything has been created 186 187 // This tests that both the schema and the table have been created 188 _, err = b.db.Query(fmt.Sprintf("SELECT name, data FROM %s.%s LIMIT 1", schemaName, statesTableName)) 189 if err != nil { 190 t.Fatal(err) 191 } 192 if tc.TestIndexIsPresent { 193 // Make sure that the index exists 194 query := `select count(*) from pg_indexes where schemaname=$1 and tablename=$2 and indexname=$3;` 195 var count int 196 if err := b.db.QueryRow(query, tc.Name, statesTableName, statesIndexName).Scan(&count); err != nil { 197 t.Fatal(err) 198 } 199 if count != 1 { 200 t.Fatalf("The index has not been created (%d)", count) 201 } 202 } 203 204 _, err = b.StateMgr(backend.DefaultStateName) 205 if err != nil { 206 t.Fatal(err) 207 } 208 209 s, err := b.StateMgr(backend.DefaultStateName) 210 if err != nil { 211 t.Fatal(err) 212 } 213 c := s.(*remote.State).Client.(*RemoteClient) 214 if c.Name != backend.DefaultStateName { 215 t.Fatal("RemoteClient name is not configured") 216 } 217 218 // Make sure that all workspace must have a unique name 219 _, err = db.Exec(fmt.Sprintf(`INSERT INTO %s.%s VALUES (100, 'unique_name_test', '')`, schemaName, statesTableName)) 220 if err != nil { 221 t.Fatal(err) 222 } 223 _, err = db.Exec(fmt.Sprintf(`INSERT INTO %s.%s VALUES (101, 'unique_name_test', '')`, schemaName, statesTableName)) 224 if err == nil { 225 t.Fatal("Creating two workspaces with the same name did not raise an error") 226 } 227 }) 228 } 229 230 } 231 232 func TestBackendStates(t *testing.T) { 233 testACC(t) 234 connStr := getDatabaseUrl() 235 236 testCases := []string{ 237 fmt.Sprintf("terraform_%s", t.Name()), 238 fmt.Sprintf("test with spaces: %s", t.Name()), 239 } 240 for _, schemaName := range testCases { 241 t.Run(schemaName, func(t *testing.T) { 242 dbCleaner, err := sql.Open("postgres", connStr) 243 if err != nil { 244 t.Fatal(err) 245 } 246 defer dbCleaner.Query("DROP SCHEMA IF EXISTS %s CASCADE", pq.QuoteIdentifier(schemaName)) 247 248 config := backend.TestWrapConfig(map[string]interface{}{ 249 "conn_str": connStr, 250 "schema_name": schemaName, 251 }) 252 b := backend.TestBackendConfig(t, New(), config).(*Backend) 253 254 if b == nil { 255 t.Fatal("Backend could not be configured") 256 } 257 258 backend.TestBackendStates(t, b) 259 }) 260 } 261 } 262 263 func TestBackendStateLocks(t *testing.T) { 264 testACC(t) 265 connStr := getDatabaseUrl() 266 schemaName := fmt.Sprintf("terraform_%s", t.Name()) 267 dbCleaner, err := sql.Open("postgres", connStr) 268 if err != nil { 269 t.Fatal(err) 270 } 271 defer dbCleaner.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName)) 272 273 config := backend.TestWrapConfig(map[string]interface{}{ 274 "conn_str": connStr, 275 "schema_name": schemaName, 276 }) 277 b := backend.TestBackendConfig(t, New(), config).(*Backend) 278 279 if b == nil { 280 t.Fatal("Backend could not be configured") 281 } 282 283 bb := backend.TestBackendConfig(t, New(), config).(*Backend) 284 285 if bb == nil { 286 t.Fatal("Backend could not be configured") 287 } 288 289 backend.TestBackendStateLocks(t, b, bb) 290 } 291 292 func TestBackendConcurrentLock(t *testing.T) { 293 testACC(t) 294 connStr := getDatabaseUrl() 295 dbCleaner, err := sql.Open("postgres", connStr) 296 if err != nil { 297 t.Fatal(err) 298 } 299 300 getStateMgr := func(schemaName string) (statemgr.Full, *statemgr.LockInfo) { 301 defer dbCleaner.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName)) 302 config := backend.TestWrapConfig(map[string]interface{}{ 303 "conn_str": connStr, 304 "schema_name": schemaName, 305 }) 306 b := backend.TestBackendConfig(t, New(), config).(*Backend) 307 308 if b == nil { 309 t.Fatal("Backend could not be configured") 310 } 311 stateMgr, err := b.StateMgr(backend.DefaultStateName) 312 if err != nil { 313 t.Fatalf("Failed to get the state manager: %v", err) 314 } 315 316 info := statemgr.NewLockInfo() 317 info.Operation = "test" 318 info.Who = schemaName 319 320 return stateMgr, info 321 } 322 323 s1, i1 := getStateMgr(fmt.Sprintf("terraform_%s_1", t.Name())) 324 s2, i2 := getStateMgr(fmt.Sprintf("terraform_%s_2", t.Name())) 325 326 // First we need to create the workspace as the lock for creating them is 327 // global 328 lockID1, err := s1.Lock(i1) 329 if err != nil { 330 t.Fatalf("failed to lock first state: %v", err) 331 } 332 333 if err = s1.PersistState(); err != nil { 334 t.Fatalf("failed to persist state: %v", err) 335 } 336 337 if err := s1.Unlock(lockID1); err != nil { 338 t.Fatalf("failed to unlock first state: %v", err) 339 } 340 341 lockID2, err := s2.Lock(i2) 342 if err != nil { 343 t.Fatalf("failed to lock second state: %v", err) 344 } 345 346 if err = s2.PersistState(); err != nil { 347 t.Fatalf("failed to persist state: %v", err) 348 } 349 350 if err := s2.Unlock(lockID2); err != nil { 351 t.Fatalf("failed to unlock first state: %v", err) 352 } 353 354 // Now we can test concurrent lock 355 lockID1, err = s1.Lock(i1) 356 if err != nil { 357 t.Fatalf("failed to lock first state: %v", err) 358 } 359 360 lockID2, err = s2.Lock(i2) 361 if err != nil { 362 t.Fatalf("failed to lock second state: %v", err) 363 } 364 365 if err := s1.Unlock(lockID1); err != nil { 366 t.Fatalf("failed to unlock first state: %v", err) 367 } 368 369 if err := s2.Unlock(lockID2); err != nil { 370 t.Fatalf("failed to unlock first state: %v", err) 371 } 372 } 373 374 func getDatabaseUrl() string { 375 return os.Getenv("DATABASE_URL") 376 }