graphql-engine/cli/commands/migrate_apply_test.go
Kali Vara Purushotham Santhati ffce0d266b cli: non zero exit code if any of the database migrations aren't applied
Issue: https://github.com/hasura/graphql-engine/issues/7499

Problem: cli doesn't exit if `migrate apply --all-databases` fail on 1 or more databases

Solution: Store the names of the databases for which it failed and return
```
operation failed on : <database1>,<database2>,etc
```

https://github.com/hasura/graphql-engine-mono/pull/2305

Co-authored-by: Aravind K P <8335904+scriptonist@users.noreply.github.com>
Co-authored-by: Vijay Prasanna <11921040+vijayprasanna13@users.noreply.github.com>
GitOrigin-RevId: 8a30bdcc24de8a204320b00e5ea9d593a6293ad4
2021-09-23 06:50:25 +00:00

417 lines
13 KiB
Go

package commands
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"github.com/Pallinder/go-randomdata"
"golang.org/x/term"
"github.com/hasura/graphql-engine/cli/v2/internal/testutil"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gbytes"
. "github.com/onsi/gomega/gexec"
)
func AddDatabaseToHasura(hgeEndpoint, sourceName, databaseKind string) (string, func()) {
if databaseKind == "postgres" {
connectionStringPG, teardownPG := testutil.StartPGContainer(GinkgoT())
testutil.AddPGSourceToHasura(GinkgoT(), hgeEndpoint, connectionStringPG, sourceName)
return connectionStringPG, teardownPG
}
if databaseKind == "citus" {
connectionStringCitus, teardownCitus := testutil.StartCitusContainer(GinkgoT())
testutil.AddCitusSourceToHasura(GinkgoT(), hgeEndpoint, connectionStringCitus, sourceName)
return connectionStringCitus, teardownCitus
}
if databaseKind == "mssql" {
mssqlPort, teardownMSSQL := testutil.StartMSSQLContainer(GinkgoT())
connectionStringMSSQL := fmt.Sprintf("DRIVER={ODBC Driver 17 for SQL Server};SERVER=%s,%s;DATABASE=master;Uid=SA;Pwd=%s;Encrypt=no", testutil.DockerSwitchIP, mssqlPort, testutil.MSSQLPassword)
testutil.AddMSSQLSourceToHasura(GinkgoT(), hgeEndpoint, connectionStringMSSQL, sourceName)
return connectionStringMSSQL, teardownMSSQL
}
return "", nil
}
var testMigrateApply = func(projectDirectory string, globalFlags []string) {
Context("migrate apply", func() {
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: append(
[]string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;"},
globalFlags...,
),
WorkingDirectory: projectDirectory,
})
session := testutil.Hasura(testutil.CmdOpts{
Args: append(
[]string{"migrate", "apply"},
globalFlags...,
),
WorkingDirectory: projectDirectory,
})
wantKeywordList := []string{
"migrations applied",
}
Eventually(session, timeout).Should(Exit(0))
for _, keyword := range wantKeywordList {
Expect(session.Err.Contents()).Should(ContainSubstring(keyword))
}
})
}
var testMigrateApplySkipExecution = func(projectDirectory string, globalFlags []string) {
Context("migrate apply --skip-execution", func() {
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: append(
[]string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;"},
globalFlags...,
),
WorkingDirectory: projectDirectory,
})
session := testutil.Hasura(testutil.CmdOpts{
Args: append(
[]string{"migrate", "apply"},
globalFlags...,
),
WorkingDirectory: projectDirectory,
})
wantKeywordList := []string{
"migrations applied",
}
Eventually(session, timeout).Should(Exit(0))
for _, keyword := range wantKeywordList {
Expect(session.Err.Contents()).Should(ContainSubstring(keyword))
}
session = testutil.Hasura(testutil.CmdOpts{
Args: append(
[]string{"migrate", "apply", "--skip-execution", "--down", "all"},
globalFlags...,
),
WorkingDirectory: projectDirectory,
})
wantKeywordList = []string{
"migrations applied",
}
Eventually(session, timeout).Should(Exit(0))
for _, keyword := range wantKeywordList {
Expect(session.Err.Contents()).Should(ContainSubstring(keyword))
}
session = testutil.Hasura(testutil.CmdOpts{
Args: append(
[]string{"migrate", "status"},
globalFlags...,
),
WorkingDirectory: projectDirectory,
})
wantKeywordList = []string{
".*VERSION*.",
".*SOURCE STATUS*.",
".*DATABASE STATUS*.",
".*schema_creation*.",
".*Present Not Present*.",
}
Eventually(session, timeout).Should(Exit(0))
for _, keyword := range wantKeywordList {
Expect(session.Wait(timeout).Out).Should(Say(keyword))
}
})
}
var testMigrateApplyAllDatabases = func(projectDirectory string, databases ...string) {
Context("migrate apply", func() {
for _, database := range databases {
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: []string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;", "--database-name", database},
WorkingDirectory: projectDirectory,
})
}
session := testutil.Hasura(testutil.CmdOpts{
Args: []string{"migrate", "apply", "--all-databases"},
WorkingDirectory: projectDirectory,
})
wantKeywordList := []string{
"migrations applied",
}
Eventually(session, timeout).Should(Exit(0))
for _, keyword := range wantKeywordList {
Expect(session.Err.Contents()).Should(ContainSubstring(keyword))
}
})
}
var testMigrateApplyAllDatabasesWithError = func(projectDirectory string, databases ...string) {
Context("migrate apply", func() {
for _, database := range databases {
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: []string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;", "--database-name", database},
WorkingDirectory: projectDirectory,
})
}
if len(databases) > 0 {
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: []string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;", "--database-name", databases[0]},
WorkingDirectory: projectDirectory,
})
}
session := testutil.Hasura(testutil.CmdOpts{
Args: []string{"migrate", "apply", "--all-databases"},
WorkingDirectory: projectDirectory,
})
wantKeywordList := []string{
fmt.Sprintf("operation failed on : %s", databases[0]),
}
if len(databases) > 0 {
Eventually(session, timeout).Should(Exit(1))
for _, keyword := range wantKeywordList {
Expect(session.Err.Contents()).Should(ContainSubstring(keyword))
}
} else {
Eventually(session, timeout).Should(Exit(0))
}
})
}
var testProgressBar = func(projectDirectory string) {
progressBar := "Applying migrations: . / 8 .*%"
Context("migrate apply should display progress bar", func() {
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: []string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;"},
WorkingDirectory: projectDirectory,
})
session := testutil.Hasura(testutil.CmdOpts{
Args: []string{"migrate", "apply", "--progressbar-logs"},
WorkingDirectory: projectDirectory,
})
wantKeywordList := []string{
progressBar,
"migrations applied",
}
Eventually(session, 60*40).Should(Exit(0))
for _, keyword := range wantKeywordList {
Eventually(session.Err, 60*40).Should(Say(keyword))
}
})
Context("migrate apply shouldn't display progress bar in non-terminal (default behaviour)", func() {
session := testutil.Hasura(testutil.CmdOpts{
Args: []string{"migrate", "apply", "--down", "all"},
WorkingDirectory: projectDirectory,
})
if !term.IsTerminal(int(os.Stdout.Fd())) {
Eventually(session.Err, 60*40).ShouldNot(Say(progressBar))
} else {
Eventually(session.Err, 60*40).Should(Say(progressBar))
}
Eventually(session, 60*40).Should(Exit(0))
})
}
var testByRunningAPI = func(hgeEndpoint string, urlPath string, body io.Reader) {
uri, err := url.Parse(hgeEndpoint)
Expect(err).To(BeNil())
uri.Path = path.Join(uri.Path, urlPath)
req, err := http.NewRequest("POST", uri.String(), body)
Expect(err).To(BeNil())
req.Header.Set("Content-Type", "application/json")
adminSecret := os.Getenv("HASURA_GRAPHQL_TEST_ADMIN_SECRET")
if adminSecret != "" {
req.Header.Set("x-hasura-admin-secret", adminSecret)
}
resp, err := http.DefaultClient.Do(req)
Expect(err).To(BeNil())
defer resp.Body.Close()
Expect(fmt.Sprint(resp.StatusCode)).Should(ContainSubstring(fmt.Sprint(http.StatusOK)))
}
var _ = Describe("hasura migrate apply", func() {
var hgeEndpoint string
var teardown func()
BeforeEach(func() {
var hgeEndPort string
hgeEndPort, teardown = testutil.StartHasura(GinkgoT(), testutil.HasuraDockerImage)
hgeEndpoint = fmt.Sprintf("http://0.0.0.0:%s", hgeEndPort)
})
AfterEach(func() { teardown() })
It("can apply the migrations on server (single database config v3)", func() {
projectDirectoryV3 := testutil.RandDirName()
defer os.RemoveAll(projectDirectoryV3)
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: []string{"init", projectDirectoryV3},
})
editEndpointInConfig(filepath.Join(projectDirectoryV3, defaultConfigFilename), hgeEndpoint)
testMigrateApply(projectDirectoryV3, []string{"--database-name", "default"})
})
It("can apply the migrations on server (single database config v2)", func() {
projectDirectoryV2 := testutil.RandDirName()
defer os.RemoveAll(projectDirectoryV2)
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: []string{"init", projectDirectoryV2, "--version", "2"},
})
editEndpointInConfig(filepath.Join(projectDirectoryV2, defaultConfigFilename), hgeEndpoint)
testMigrateApply(projectDirectoryV2, nil)
})
It("should mark the migrations as apply ", func() {
projectDirectoryV2 := testutil.RandDirName()
defer os.RemoveAll(projectDirectoryV2)
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: []string{"init", projectDirectoryV2, "--version", "2"},
})
editEndpointInConfig(filepath.Join(projectDirectoryV2, defaultConfigFilename), hgeEndpoint)
testMigrateApplySkipExecution(projectDirectoryV2, nil)
createTable := strings.NewReader(`
{
"type": "run_sql",
"args": {
"sql": "CREATE TABLE testing.test();"
}
}
`)
testByRunningAPI(hgeEndpoint, "v2/query", createTable)
})
})
var _ = Describe("hasura migrate apply (config v3)", func() {
// automatic state migration should not affect new config v3 projects
var projectDirectory string
var hgeEndpoint string
var teardown func()
var pgSource = randomdata.SillyName()
var citusSource = randomdata.SillyName()
var mssqlSource = randomdata.SillyName()
BeforeEach(func() {
projectDirectory = testutil.RandDirName()
hgeEndPort, teardownHGE := testutil.StartHasuraWithMetadataDatabase(GinkgoT(), testutil.HasuraDockerImage)
hgeEndpoint = fmt.Sprintf("http://0.0.0.0:%s", hgeEndPort)
_, teardownPG := AddDatabaseToHasura(hgeEndpoint, pgSource, "postgres")
_, teardownCitus := AddDatabaseToHasura(hgeEndpoint, citusSource, "citus")
_, teardownMSSQL := AddDatabaseToHasura(hgeEndpoint, mssqlSource, "mssql")
testutil.RunCommandAndSucceed(testutil.CmdOpts{
Args: []string{"init", projectDirectory},
})
editEndpointInConfig(filepath.Join(projectDirectory, defaultConfigFilename), hgeEndpoint)
teardown = func() {
os.RemoveAll(projectDirectory)
teardownHGE()
teardownPG()
teardownCitus()
teardownMSSQL()
}
})
AfterEach(func() { teardown() })
It("should apply the migrations on server", func() {
testMigrateApply(projectDirectory, []string{"--database-name", pgSource})
testMigrateApply(projectDirectory, []string{"--database-name", citusSource})
testMigrateApply(projectDirectory, []string{"--database-name", mssqlSource})
})
It("should mark the migrations as applied ", func() {
testMigrateApplySkipExecution(projectDirectory, []string{"--database-name", pgSource})
createTableString := fmt.Sprintf(`
{
"type": "run_sql",
"args": {
"sql": "CREATE TABLE testing.test();",
"source": "%s"
}
}
`, pgSource)
createTable := strings.NewReader(createTableString)
testByRunningAPI(hgeEndpoint, "v2/query", createTable)
})
It("should apply the migrations on all-databases", func() {
testMigrateApplyAllDatabases(projectDirectory, pgSource, citusSource, mssqlSource)
pgBody := fmt.Sprintf(`
{
"type": "run_sql",
"args": {
"sql": "CREATE TABLE testing.test();",
"source": "%s"
}
}
`, pgSource)
createTable := strings.NewReader(pgBody)
testByRunningAPI(hgeEndpoint, "v2/query", createTable)
citusBody := fmt.Sprintf(`
{
"type": "citus_run_sql",
"args": {
"sql": "CREATE TABLE testing.test();",
"source": "%s"
}
}
`, citusSource)
createTable = strings.NewReader(citusBody)
testByRunningAPI(hgeEndpoint, "v2/query", createTable)
})
It("should the migrations on all-databases except first database and it should exit with code 1", func() {
testMigrateApplyAllDatabasesWithError(projectDirectory, pgSource, citusSource, mssqlSource)
citusBody := fmt.Sprintf(`
{
"type": "citus_run_sql",
"args": {
"sql": "CREATE TABLE testing.test();",
"source": "%s"
}
}
`, citusSource)
createTable := strings.NewReader(citusBody)
testByRunningAPI(hgeEndpoint, "v2/query", createTable)
})
})
var _ = Describe("hasura migrate apply progress bar", func() {
var projectDirectory string
var teardown func()
BeforeEach(func() {
projectDirectory = testutil.RandDirName()
hgeEndPort, teardownHGE := testutil.StartHasura(GinkgoT(), testutil.HasuraDockerImage)
hgeEndpoint := fmt.Sprintf("http://0.0.0.0:%s", hgeEndPort)
copyTestConfigV2Project(projectDirectory)
editEndpointInConfig(filepath.Join(projectDirectory, defaultConfigFilename), hgeEndpoint)
teardown = func() {
os.RemoveAll(projectDirectory)
teardownHGE()
}
})
AfterEach(func() {
teardown()
})
It("test the progress bar of migrate apply", func() {
testProgressBar(projectDirectory)
})
})