Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

atlasexec/migrate-lint: added support for --web #18

Merged
merged 15 commits into from
Sep 27, 2023
35 changes: 31 additions & 4 deletions atlasexec/atlas.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ type (
ConfigURL string
DevURL string
DirURL string
Web bool
Latest uint64
Vars Vars
Writer io.Writer
}
// SchemaApplyParams are the parameters for the `schema apply` command.
SchemaApplyParams struct {
Expand Down Expand Up @@ -281,9 +283,13 @@ func (c *Client) SchemaInspect(ctx context.Context, params *SchemaInspectParams)
return stringVal(c.runCommand(ctx, args))
}

// MigrateLint runs the 'migrate lint' command.
func (c *Client) MigrateLint(ctx context.Context, params *MigrateLintParams) (*SummaryReport, error) {
args := []string{"migrate", "lint", "--format", "{{ json . }}"}
func lintArgs(params *MigrateLintParams) []string {
args := []string{"migrate", "lint"}
if params.Web {
args = append(args, "-w")
} else {
args = append(args, "--format", "{{ json . }}")
}
if params.Env != "" {
args = append(args, "--env", params.Env)
}
Expand All @@ -300,7 +306,28 @@ func (c *Client) MigrateLint(ctx context.Context, params *MigrateLintParams) (*S
args = append(args, "--latest", strconv.FormatUint(params.Latest, 10))
}
args = append(args, params.Vars.AsArgs()...)
return jsonDecode[SummaryReport](c.runCommand(ctx, args, validJSON))
return args
}

// MigrateLint runs the 'migrate lint' command.
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")
}
r, err := c.runCommand(ctx, lintArgs(params), validJSON)
return jsonDecode[SummaryReport](r, err)
}

// MigrateLintError runs the 'migrate lint' command, the output is written to params.Writer
func (c *Client) MigrateLintError(ctx context.Context, params *MigrateLintParams) error {
r, err := c.runCommand(ctx, lintArgs(params))
if err != nil {
return err
}
if params.Writer != nil {
_, err = io.Copy(params.Writer, r)
}
return err
}

// MigrateStatus runs the 'migrate status' command.
Expand Down
152 changes: 149 additions & 3 deletions atlasexec/atlas_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package atlasexec_test

import (
"bytes"
"context"
"database/sql"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"

"ariga.io/atlas-go-sdk/atlasexec"
"ariga.io/atlas/cmd/atlas/x"
"ariga.io/atlas/sql/sqlcheck"

_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/require"

"ariga.io/atlas-go-sdk/atlasexec"
)

func Test_MigrateApply(t *testing.T) {
Expand Down Expand Up @@ -51,7 +58,146 @@ func Test_MigrateApply(t *testing.T) {
Env: "test",
})
require.NoError(t, err)
require.EqualValues(t, "20230727105615", got.Target)
require.EqualValues(t, "20230926085734", got.Target)
}

func TestMigrateLint(t *testing.T) {
t.Run("with broken config", func(t *testing.T) {
c, err := atlasexec.NewClient(".", "atlas")
require.NoError(t, err)
got, err := c.MigrateLint(context.Background(), &atlasexec.MigrateLintParams{
ConfigURL: "file://config-broken.hcl",
})
require.ErrorContains(t, err, `project file "config-broken.hcl" was not found`)
require.Nil(t, got)
})
t.Run("with broken dev-url", func(t *testing.T) {
c, err := atlasexec.NewClient(".", "atlas")
require.NoError(t, err)
got, err := c.MigrateLint(context.Background(), &atlasexec.MigrateLintParams{
DirURL: "file://atlasexec/testdata/migrations",
})
require.ErrorContains(t, err, `required flag(s) "dev-url" not set`)
require.Nil(t, got)
})
t.Run("broken dir", func(t *testing.T) {
c, err := atlasexec.NewClient(".", "atlas")
require.NoError(t, err)
got, err := c.MigrateLint(context.Background(), &atlasexec.MigrateLintParams{
DevURL: "sqlite://file?mode=memory",
DirURL: "file://atlasexec/testdata/doesnotexist",
})
require.ErrorContains(t, err, `stat atlasexec/testdata/doesnotexist: no such file or directory`)
require.Nil(t, got)
})
t.Run("lint error parsing", func(t *testing.T) {
c, err := atlasexec.NewClient(".", "atlas")
require.NoError(t, err)
got, err := c.MigrateLint(context.Background(), &atlasexec.MigrateLintParams{
DevURL: "sqlite://file?mode=memory",
DirURL: "file://testdata/migrations",
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))
expectedReport := &x.FileReport{
Name: "20230926085734_destructive-change.sql",
Text: "DROP TABLE t2;\n",
Reports: []sqlcheck.Report{{
Text: "destructive changes detected",
Diagnostics: []sqlcheck.Diagnostic{{
Pos: 0,
Text: `Dropping table "t2"`,
Code: "DS102",
}},
}},
Error: "destructive changes detected",
}
require.EqualValues(t, expectedReport, got.Files[0])
})
t.Run("lint with manually parsing output", func(t *testing.T) {
c, err := atlasexec.NewClient(".", "atlas")
require.NoError(t, err)
var buf bytes.Buffer
err = c.MigrateLintError(context.Background(), &atlasexec.MigrateLintParams{
DevURL: "sqlite://file?mode=memory",
DirURL: "file://testdata/migrations",
Latest: 1,
Writer: &buf,
})
require.NoError(t, err)
var raw json.RawMessage
require.NoError(t, json.NewDecoder(&buf).Decode(&raw))
})
}

func TestMigrateLintWithLogin(t *testing.T) {
type graphQLQuery struct {
Query string `json:"query"`
Variables json.RawMessage `json:"variables"`
}
token := "123456789"
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "Bearer "+token, r.Header.Get("Authorization"))
var query graphQLQuery
require.NoError(t, json.NewDecoder(r.Body).Decode(&query))
if strings.Contains(query.Query, "mutation reportMigrationLint") {
_, err := fmt.Fprint(w, `{ "data": { "reportMigrationLint": { "url": "https://migration-lint-report-url" } } }`)
require.NoError(t, err)
}
}))
t.Cleanup(srv.Close)
st := fmt.Sprintf(
`atlas {
cloud {
token = %q
url = %q
}
}
env "test" {}
`, token, srv.URL)
atlasConfigURL, clean, err := atlasexec.TempFile(st, "hcl")
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, clean())
})
t.Run("Web and Writer params produces an error", func(t *testing.T) {
c, err := atlasexec.NewClient(".", "atlas")
require.NoError(t, err)
params := &atlasexec.MigrateLintParams{
ConfigURL: atlasConfigURL,
DevURL: "sqlite://file?mode=memory",
DirURL: "file://testdata/migrations",
Latest: 1,
Web: true,
}
got, err := c.MigrateLint(context.Background(), params)
require.ErrorContains(t, err, "Writer or Web reporting are not supported")
require.Nil(t, got)
params.Web = false
params.Writer = &bytes.Buffer{}
got, err = c.MigrateLint(context.Background(), params)
require.ErrorContains(t, err, "Writer or Web reporting are not supported")
require.Nil(t, got)
})
t.Run("lint parse web output", func(t *testing.T) {
c, err := atlasexec.NewClient(".", "atlas")
require.NoError(t, err)
var buf bytes.Buffer
require.NoError(t, c.MigrateLintError(context.Background(), &atlasexec.MigrateLintParams{
DevURL: "sqlite://file?mode=memory",
DirURL: "file://testdata/migrations",
ConfigURL: atlasConfigURL,
Latest: 1,
Writer: &buf,
Web: true,
}))
require.Equal(t, strings.TrimSpace(buf.String()), "https://migration-lint-report-url")
})
}

func Test_MigrateStatus(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE t2;
3 changes: 2 additions & 1 deletion atlasexec/testdata/migrations/atlas.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
h1:QNam/TUdByc0NShpdLNZ0ojLMN6bJWxZ6bSLTF4X0JM=
h1:hnQZfRcN6sV+y+0YePtkLazMy+Ty3lhyGv69ixeYoXc=
20230727105553_init.sql h1:jxgvnWO6tZD3lSPpH1ao5E/6VjapP7iwvBCUJ6aez58=
20230727105615_t2.sql h1:UvzeoFxe90Y/7b21ziwg6pPzWJQSV7LeYwJl8J63lMU=
20230926085734_destructive-change.sql h1:Gf/bSvUkfqHr/MEXKCxdGu2YvG8zwe4ER5TW8T/laA0=