Skip to content

Commit

Permalink
atlasexec: support for multiple database results
Browse files Browse the repository at this point in the history
  • Loading branch information
giautm committed Jan 12, 2024
1 parent 9dd71d7 commit be8e87f
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 45 deletions.
55 changes: 26 additions & 29 deletions atlasexec/atlas.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,24 +180,6 @@ func (t Client) WithWorkDir(dir string, fn func(*Client) error) error {
return fn(&t)
}

// Apply runs the 'migrate apply' command.
// Deprecated: use MigrateApply instead.
func (c *Client) Apply(ctx context.Context, params *MigrateApplyParams) (*MigrateApply, error) {
return c.MigrateApply(ctx, params)
}

// Lint runs the 'migrate lint' command.
// Deprecated: use MigrateLint instead.
func (c *Client) Lint(ctx context.Context, params *MigrateLintParams) (*SummaryReport, error) {
return c.MigrateLint(ctx, params)
}

// Status runs the 'migrate status' command.
// Deprecated: use MigrateStatus instead.
func (c *Client) Status(ctx context.Context, params *MigrateStatusParams) (*MigrateStatus, error) {
return c.MigrateStatus(ctx, params)
}

// Login runs the 'login' command.
func (c *Client) Login(ctx context.Context, params *LoginParams) error {
if params.Token == "" {
Expand Down Expand Up @@ -256,7 +238,7 @@ func (c *Client) MigratePush(ctx context.Context, params *MigratePushParams) (st

// MigrateApply runs the 'migrate apply' command. If the underlying command returns an error, but prints to stdout
// it will be returned as a MigrateApply with the error message in the Error field.
func (c *Client) MigrateApply(ctx context.Context, params *MigrateApplyParams) (*MigrateApply, error) {
func (c *Client) MigrateApply(ctx context.Context, params *MigrateApplyParams) ([]*MigrateApply, error) {
args := []string{"migrate", "apply", "--format", "{{ json . }}"}
if params.Env != "" {
args = append(args, "--env", params.Env)
Expand Down Expand Up @@ -303,7 +285,7 @@ func (c *Client) MigrateApply(ctx context.Context, params *MigrateApplyParams) (
}

// SchemaApply runs the 'schema apply' command.
func (c *Client) SchemaApply(ctx context.Context, params *SchemaApplyParams) (*SchemaApply, error) {
func (c *Client) SchemaApply(ctx context.Context, params *SchemaApplyParams) ([]*SchemaApply, error) {
args := []string{"schema", "apply", "--format", "{{ json . }}"}
if params.Env != "" {
args = append(args, "--env", params.Env)
Expand Down Expand Up @@ -406,7 +388,7 @@ func lintArgs(params *MigrateLintParams) ([]string, error) {
}

// MigrateLint runs the 'migrate lint' command.
func (c *Client) MigrateLint(ctx context.Context, params *MigrateLintParams) (*SummaryReport, error) {
func (c *Client) MigrateLint(ctx context.Context, params *MigrateLintParams) ([]*SummaryReport, error) {
if params.Writer != nil || params.Web {
return nil, errors.New("atlasexec: Writer or Web reporting are not supported with MigrateLint, use MigrateLintError")
}
Expand Down Expand Up @@ -479,7 +461,14 @@ func (c *Client) MigrateStatus(ctx context.Context, params *MigrateStatusParams)
args = append(args, "--revisions-schema", params.RevisionsSchema)
}
args = append(args, params.Vars.AsArgs()...)
return jsonDecode[MigrateStatus](c.runCommand(ctx, args))
r, err := jsonDecode[MigrateStatus](c.runCommand(ctx, args))
if err != nil {
return nil, err
}
if len(r) == 0 {
return nil, errors.New("unexpected output format")
}
return r[0], nil
}

var reVersion = regexp.MustCompile(`^atlas version v(\d+\.\d+.\d+)-?([a-z0-9]*)?`)
Expand Down Expand Up @@ -609,24 +598,32 @@ func stringVal(r io.Reader, err error) (string, error) {
return string(s), nil
}

func jsonDecode[T any](r io.Reader, err error) (*T, error) {
func jsonDecode[T any](r io.Reader, err error) ([]*T, error) {
if err != nil {
return nil, err
}
buf, err := io.ReadAll(r)
if err != nil {
return nil, err
}
var dst T
if err = json.Unmarshal(buf, &dst); err != nil {
return nil, cliError{
stdout: string(buf),
var dst []*T
dec := json.NewDecoder(bytes.NewReader(buf))
for {
var m T
switch err := dec.Decode(&m); err {
case io.EOF:
return dst, nil
case nil:
dst = append(dst, &m)
default:
return nil, cliError{
stdout: string(buf),
}
}
}
return &dst, nil
}

func jsonDecodeErr[T any, Err error](r io.Reader, err error) (*T, error) {
func jsonDecodeErr[T any, Err error](r io.Reader, err error) ([]*T, error) {
if err != nil {
if cliErr := (cliError{}); errors.As(err, &cliErr) && cliErr.stderr == "" {
var dst Err
Expand Down
16 changes: 8 additions & 8 deletions atlasexec/atlas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func Test_MigrateApply(t *testing.T) {
Env: "test",
})
require.NoError(t, err)
require.EqualValues(t, "20230926085734", got.Target)
require.EqualValues(t, "20230926085734", got[0].Target)
// Add dirty changes and try again
os.Setenv("DB_URL", "sqlite://test.db?_fk=1&cache=shared&mode=memory")
drv, err := sql.Open("sqlite3", "test.db")
Expand All @@ -90,7 +90,7 @@ func Test_MigrateApply(t *testing.T) {
AllowDirty: true,
})
require.NoError(t, err)
require.EqualValues(t, "20230926085734", got.Target)
require.EqualValues(t, "20230926085734", got[0].Target)
}

func Test_MigrateApplyWithRemote(t *testing.T) {
Expand Down Expand Up @@ -226,11 +226,11 @@ func TestMigrateLint(t *testing.T) {
Latest: 1,
})
require.NoError(t, err)
require.GreaterOrEqual(t, 4, len(got.Steps))
require.Equal(t, "sqlite3", got.Env.Driver)
require.Equal(t, "testdata/migrations", got.Env.Dir)
require.Equal(t, "sqlite://file?mode=memory", got.Env.URL.String())
require.Equal(t, 1, len(got.Files))
require.GreaterOrEqual(t, 4, len(got[0].Steps))
require.Equal(t, "sqlite3", got[0].Env.Driver)
require.Equal(t, "testdata/migrations", got[0].Env.Dir)
require.Equal(t, "sqlite://file?mode=memory", got[0].Env.URL.String())
require.Equal(t, 1, len(got[0].Files))
expectedReport := &atlasexec.FileReport{
Name: "20230926085734_destructive-change.sql",
Text: "DROP TABLE t2;\n",
Expand All @@ -244,7 +244,7 @@ func TestMigrateLint(t *testing.T) {
}},
Error: "destructive changes detected",
}
require.EqualValues(t, expectedReport, got.Files[0])
require.EqualValues(t, expectedReport, got[0].Files[0])
})
t.Run("lint with manually parsing output", func(t *testing.T) {
c, err := atlasexec.NewClient(".", "atlas")
Expand Down
37 changes: 29 additions & 8 deletions atlasexec/internal/e2e/sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Test_SQLite(t *testing.T) {
runTestWithVersions(t, []string{"latest"}, "versioned-basic", func(t *testing.T, ver *atlasexec.Version, c *atlasexec.Client) {
url := "sqlite://file.db?_fk=1"
ctx := context.Background()
s, err := c.Status(ctx, &atlasexec.MigrateStatusParams{
s, err := c.MigrateStatus(ctx, &atlasexec.MigrateStatusParams{
URL: url,
Env: "local",
})
Expand All @@ -26,16 +26,16 @@ func Test_SQLite(t *testing.T) {
Env: "local",
})
require.NoError(t, err)
require.Equal(t, 1, len(r.Applied), "Should be one migration applied")
require.Equal(t, "20240112070806", r.Applied[0].Version, "Should be the correct migration applied")
require.Equal(t, 1, len(r[0].Applied), "Should be one migration applied")
require.Equal(t, "20240112070806", r[0].Applied[0].Version, "Should be the correct migration applied")

// Apply again, should be a no-op.
r, err = c.MigrateApply(ctx, &atlasexec.MigrateApplyParams{
URL: url,
Env: "local",
})
require.NoError(t, err, "Should be no error")
require.Equal(t, 0, len(r.Applied), "Should be no migrations applied")
require.Equal(t, 0, len(r[0].Applied), "Should be no migrations applied")
})
}

Expand All @@ -47,7 +47,7 @@ func Test_PostgreSQL(t *testing.T) {
runTestWithVersions(t, []string{"latest"}, "versioned-basic", func(t *testing.T, ver *atlasexec.Version, c *atlasexec.Client) {
url := u
ctx := context.Background()
s, err := c.Status(ctx, &atlasexec.MigrateStatusParams{
s, err := c.MigrateStatus(ctx, &atlasexec.MigrateStatusParams{
URL: url,
Env: "local",
})
Expand All @@ -60,15 +60,36 @@ func Test_PostgreSQL(t *testing.T) {
Env: "local",
})
require.NoError(t, err)
require.Equal(t, 1, len(r.Applied), "Should be one migration applied")
require.Equal(t, "20240112070806", r.Applied[0].Version, "Should be the correct migration applied")
require.Equal(t, 1, len(r[0].Applied), "Should be one migration applied")
require.Equal(t, "20240112070806", r[0].Applied[0].Version, "Should be the correct migration applied")

// Apply again, should be a no-op.
r, err = c.MigrateApply(ctx, &atlasexec.MigrateApplyParams{
URL: url,
Env: "local",
})
require.NoError(t, err, "Should be no error")
require.Equal(t, 0, len(r.Applied), "Should be no migrations applied")
require.Equal(t, 0, len(r[0].Applied), "Should be no migrations applied")
})
}

func Test_MultiTenants(t *testing.T) {
t.Setenv("ATLASEXEC_E2ETEST_ATLAS_PATH", "atlas")
runTestWithVersions(t, []string{"latest"}, "multi-tenants", func(t *testing.T, ver *atlasexec.Version, c *atlasexec.Client) {
ctx := context.Background()
r, err := c.MigrateApply(ctx, &atlasexec.MigrateApplyParams{
Env: "local",
})
require.NoError(t, err)
require.Len(t, r, 2, "Should be two tenants")
require.Equal(t, 1, len(r[0].Applied), "Should be one migration applied")
require.Equal(t, "20240112070806", r[0].Applied[0].Version, "Should be the correct migration applied")

// Apply again, should be a no-op.
r, err = c.MigrateApply(ctx, &atlasexec.MigrateApplyParams{
Env: "local",
})
require.NoError(t, err, "Should be no error")
require.Equal(t, 0, len(r[0].Applied), "Should be no migrations applied")
})
}
9 changes: 9 additions & 0 deletions atlasexec/internal/e2e/testdata/multi-tenants/atlas.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
env {
for_each = toset(["sqlite://foo.db?_fk=1", "sqlite://bar.db?_fk=1"])
name = atlas.env
url = each.value
migration {
dir = "file://migrations"
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE TABLE t1(c1 int);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
h1:vefBQWShy7/4OI7C1NqFH9y2PtGtOUS5zFQ1492XitE=
20240112070806.sql h1:nhoPxDs1H3UH6aEpy1qJ6Bj6zbFRt61sB4ndi0sx7zw=

0 comments on commit be8e87f

Please sign in to comment.