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  }