Skip to content

Commit

Permalink
cmd/action: add atlas migrate hash command to solve checksum mismatches
Browse files Browse the repository at this point in the history
  • Loading branch information
masseelch committed Mar 18, 2022
1 parent c761b18 commit cf690ee
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 9 deletions.
27 changes: 26 additions & 1 deletion cmd/action/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ the migration directory state to the desired schema. The desired state can be an
Args: cobra.MaximumNArgs(1),
RunE: CmdMigrateDiffRun,
}
// MigrateHashCmd represents the migrate hash command.
MigrateHashCmd = &cobra.Command{
Use: "hash",
Short: "Hash creates an integrity hash file for the migration directories.",
Long: `'atlas migrate hash' computes the integrity hash sum of the migration directory andstores it in the atlas.sum file.
This command should be used whenever a manual change in the migration directory was made.`,
Example: ` atlas migrate validate
atlas migrate validate --dir /path/to/migration/directory`,
RunE: CmdMigrateHashRun,
}
// MigrateValidateCmd represents the migrate validate command.
MigrateValidateCmd = &cobra.Command{
Use: "validate",
Expand All @@ -94,6 +104,7 @@ func init() {
RootCmd.AddCommand(MigrateCmd)
MigrateCmd.AddCommand(MigrateDiffCmd)
MigrateCmd.AddCommand(MigrateValidateCmd)
MigrateCmd.AddCommand(MigrateHashCmd)
// Global flags.
MigrateCmd.PersistentFlags().StringVarP(&MigrateFlags.DirURL, migrateFlagDir, "", "file://migrations", "select migration directory using DSN format")
MigrateCmd.PersistentFlags().StringSliceVarP(&MigrateFlags.Schemas, migrateFlagSchema, "", nil, "set schema names")
Expand All @@ -118,7 +129,7 @@ func CmdMigrateDiffRun(cmd *cobra.Command, args []string) error {
if err := checkClean(cmd.Context(), dev); err != nil {
return err
}
// Open the migration directory. For now only local directories are supported.
// Open the migration directory.
dir, err := dir()
if err != nil {
return err
Expand Down Expand Up @@ -151,6 +162,20 @@ func CmdMigrateDiffRun(cmd *cobra.Command, args []string) error {
// TODO(masseelch): clean up dev after reading the state from migration dir.
}

// CmdMigrateHashRun is the command executed when running the CLI with 'migrate diff' args.
func CmdMigrateHashRun(*cobra.Command, []string) error {
// Open the migration directory.
dir, err := dir()
if err != nil {
return err
}
sum, err := migrate.HashSum(dir)
if err != nil {
return err
}
return migrate.WriteSumFile(dir, sum)
}

// dir returns a migrate.Dir to use as migration directory. For now only local directories are supported.
func dir() (migrate.Dir, error) {
parts := strings.SplitN(MigrateFlags.DirURL, "://", 2)
Expand Down
64 changes: 62 additions & 2 deletions cmd/action/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ package action

import (
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"

"ariga.io/atlas/sql/migrate"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -60,8 +63,9 @@ func TestMigrate_ValidateError(t *testing.T) {
runCmd(RootCmd, "migrate", "validate", "--dir", "file://migrations")
return
}
require.NoError(t, os.WriteFile("migrations/new.sql", []byte("contents"), 0600))
defer os.Remove("migrations/new.sql")
f := filepath.Join("migrations", "new.sql")
require.NoError(t, os.WriteFile(f, []byte("contents"), 0600))
defer os.Remove(f)
cmd := exec.Command(os.Args[0], "-test.run=TestMigrate_ValidateError") //nolint:gosec
cmd.Env = append(os.Environ(), "DO_VALIDATE=1")
err := cmd.Run()
Expand All @@ -71,6 +75,47 @@ func TestMigrate_ValidateError(t *testing.T) {
t.Fatalf("process ran with err %v, want exist status 1", err)
}

func TestMigrate_Hash(t *testing.T) {
s, err := runCmd(RootCmd, "migrate", "hash", "--dir", "file://migrations")
require.Zero(t, s)
require.NoError(t, err)

p := t.TempDir()
err = copyFile(filepath.Join("migrations", "20220318104614_initial.sql"), filepath.Join(p, "20220318104614_initial.sql"))
require.NoError(t, err)

s, err = runCmd(RootCmd, "migrate", "hash", "--dir", "file://"+p, "--force")
require.Zero(t, s)
require.NoError(t, err)
require.FileExists(t, filepath.Join(p, "atlas.sum"))
d, err := ioutil.ReadFile(filepath.Join(p, "atlas.sum"))
require.NoError(t, err)
dir, err := migrate.NewLocalDir(p)
require.NoError(t, err)
sum, err := migrate.HashSum(dir)
require.NoError(t, err)
b, err := sum.MarshalText()
require.NoError(t, err)
require.Equal(t, d, b)
}

func TestMigrate_HashError(t *testing.T) {
if os.Getenv("DO_HASH") == "1" {
runCmd(RootCmd, "migrate", "hash", "--dir", "file://"+os.Getenv("MIGRATION_DIR"))
return
}
p := t.TempDir()
err := copyFile(filepath.Join("migrations", "20220318104614_initial.sql"), filepath.Join(p, "20220318104614_initial.sql"))
require.NoError(t, err)
cmd := exec.Command(os.Args[0], "-test.run=TestMigrate_HashError") //nolint:gosec
cmd.Env = append(os.Environ(), "DO_HASH=1", "MIGRATION_DIR="+p)
err = cmd.Run()
if err, ok := err.(*exec.ExitError); ok && !err.Success() {
return
}
t.Fatalf("process ran with err %v, want exist status 1", err)
}

const hcl = `
schema "main" {
}
Expand Down Expand Up @@ -156,3 +201,18 @@ func hclURL(t *testing.T) string {
require.NoError(t, os.WriteFile(filepath.Join(p, "atlas.hcl"), []byte(hcl), 0600))
return "file://" + filepath.Join(p, "atlas.hcl")
}

func copyFile(src, dst string) error {
sf, err := os.Open(src)
if err != nil {
return err
}
defer sf.Close()
df, err := os.Create(dst)
if err != nil {
return err
}
defer df.Close()
_, err = io.Copy(df, sf)
return err
}
20 changes: 20 additions & 0 deletions doc/md/cli/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,26 @@ the migration directory state to the desired schema. The desired state can be an
```


### atlas migrate hash

Hash creates an integrity hash file for the migration directories.

#### Usage
```
atlas migrate hash
```

#### Details
'atlas migrate hash' computes the integrity hash sum of the migration directory andstores it in the atlas.sum file.
This command should be used whenever a manual change in the migration directory was made.

#### Example

```
atlas migrate validate
atlas migrate validate --dir /path/to/migration/directory
```

### atlas migrate validate

Validates the migration directories checksum.
Expand Down
15 changes: 10 additions & 5 deletions sql/migrate/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,11 +286,7 @@ func (p *Planner) WritePlan(plan *Plan) error {
if err != nil {
return err
}
b, err := sum.MarshalText()
if err != nil {
return err
}
return p.dir.WriteFile(hashFile, b)
return WriteSumFile(p.dir, sum)
}
return nil
}
Expand Down Expand Up @@ -492,6 +488,15 @@ func Validate(dir Dir) error {
return nil
}

// WriteSumFile writes the given HashFile to the Dir. If the file does not exist, it is created.
func WriteSumFile(dir Dir, sum HashFile) error {
b, err := sum.MarshalText()
if err != nil {
return err
}
return dir.WriteFile(hashFile, b)
}

// Sum returns the checksum of the represented hash file.
func (s HashFile) Sum() string {
sha := sha256.New()
Expand Down
2 changes: 1 addition & 1 deletion sql/migrate/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func TestPlanner_Plan(t *testing.T) {
require.Equal(t, drv.plan, plan)
}

func TestHash(t *testing.T) {
func TestHashSum(t *testing.T) {
p := t.TempDir()
d, err := migrate.NewLocalDir(p)
require.NoError(t, err)
Expand Down

0 comments on commit cf690ee

Please sign in to comment.