mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-11-10 10:29:12 +03:00
cli: migrate delete
command, update migration state on server after squash
It contains 2 flags all - To delete all migrations locally and on database version - To delete a single migration locally and on database Usage : `hasura migrate delete --all` `hasura migrate delete --version <version_number>` Additional fix : The `migrate squash` will deletes the migration history on server after squashing if user opts to delete the migrations. closes https://github.com/hasura/graphql-engine-mono/issues/292 closes https://github.com/hasura/graphql-engine/issues/5373 closes https://github.com/hasura/graphql-engine/issues/6434 Co-authored-by: Aravind K P <8335904+scriptonist@users.noreply.github.com> GitOrigin-RevId: fa7ceae7a1970d6724fb601a147900e880ad2e6f
This commit is contained in:
parent
feb08d8998
commit
5e92ce028e
@ -40,7 +40,7 @@
|
||||
- console: allow editing sources configuration
|
||||
- console: show db version and source details in manage db page
|
||||
- console: add one-to-one relationships support
|
||||
- cli: add `-o`/`--output` flag for `metadata` `apply` & `export` subcommands
|
||||
- cli: add `-o`/`--output` flag for `hasura metadata` `apply` & `export` subcommands
|
||||
```
|
||||
# export metadata and write to stdout
|
||||
$ hasura metadata export -o json
|
||||
@ -50,6 +50,7 @@ $ hasura metadata export -o json
|
||||
- cli: fix bug caused by usage of space character in database name (#6852)
|
||||
- cli: fix issues with generated filepaths in windows (#6813)
|
||||
- cli: add warning for incompatible pro plugin version
|
||||
- cli: add new sub command `delete` to `hasura migrate`
|
||||
|
||||
## v2.0.0-alpha.10
|
||||
|
||||
|
@ -46,7 +46,7 @@ var _ = Describe("actions_codegen", func() {
|
||||
Args: []string{"actions", "codegen"},
|
||||
WorkingDirectory: dirName,
|
||||
})
|
||||
Eventually(session, 60*40).Should(Exit(0))
|
||||
Eventually(session, 60*60).Should(Exit(0))
|
||||
Eventually(session.Wait().Err.Contents()).Should(ContainSubstring("Codegen files generated at codegen"))
|
||||
})
|
||||
})
|
||||
|
@ -65,6 +65,7 @@ func NewMigrateCmd(ec *cli.ExecutionContext) *cobra.Command {
|
||||
newMigrateStatusCmd(ec),
|
||||
newMigrateCreateCmd(ec),
|
||||
newMigrateSquashCmd(ec),
|
||||
newMigrateDeleteCmd(ec),
|
||||
)
|
||||
|
||||
return migrateCmd
|
||||
|
152
cli/commands/migrate_delete.go
Normal file
152
cli/commands/migrate_delete.go
Normal file
@ -0,0 +1,152 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/hasura/graphql-engine/cli"
|
||||
"github.com/hasura/graphql-engine/cli/migrate"
|
||||
mig "github.com/hasura/graphql-engine/cli/migrate/cmd"
|
||||
"github.com/hasura/graphql-engine/cli/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newMigrateDeleteCmd(ec *cli.ExecutionContext) *cobra.Command {
|
||||
opts := &MigrateDeleteOptions{
|
||||
EC: ec,
|
||||
}
|
||||
migrateDeleteCmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "(PREVIEW) clear migrations from local project and server",
|
||||
Example: `
|
||||
# Usage to delete a version:
|
||||
hasura migrate delete --version <version_delete> --database-name default
|
||||
|
||||
# Usage to delete all versions
|
||||
hasura migrate delete --all`,
|
||||
SilenceUsage: true,
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
ec.Logger.Warn("[PREVIEW] this command is in preview. usage may change in future\n")
|
||||
if err := validateConfigV3Flags(cmd, ec); err != nil {
|
||||
return err
|
||||
}
|
||||
if !cmd.Flags().Changed("all") && !cmd.Flags().Changed("version") {
|
||||
return fmt.Errorf("at least one flag [--all , --version] should be set")
|
||||
}
|
||||
if cmd.Flags().Changed("all") && cmd.Flags().Changed("version") {
|
||||
return fmt.Errorf("only one of [--all , --version] should be set")
|
||||
}
|
||||
if cmd.Flags().Changed("all") && !opts.force {
|
||||
confirmation, err := util.GetYesNoPrompt("clear all migrations of database and it's history on the server?")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting user input: %w", err)
|
||||
}
|
||||
if confirmation == "n" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Source = ec.Source
|
||||
if ec.Config.Version >= cli.V3 {
|
||||
var err error
|
||||
opts.EC.Spin("Removing migrations")
|
||||
err = opts.Run()
|
||||
opts.EC.Spinner.Stop()
|
||||
if err != nil {
|
||||
return fmt.Errorf("operation failed: %w", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
opts.EC.Spin("Removing migrations")
|
||||
err := opts.Run()
|
||||
opts.EC.Spinner.Stop()
|
||||
if err != nil {
|
||||
return fmt.Errorf("operation failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
f := migrateDeleteCmd.Flags()
|
||||
f.Uint64Var(&opts.version, "version", 0, "deletes the specified version in migrations")
|
||||
f.BoolVar(&opts.all, "all", false, "clears all migrations for selected database")
|
||||
f.BoolVar(&opts.force, "force", false, "when set executes operation without any confirmation")
|
||||
|
||||
return migrateDeleteCmd
|
||||
}
|
||||
|
||||
type MigrateDeleteOptions struct {
|
||||
EC *cli.ExecutionContext
|
||||
version uint64
|
||||
all bool
|
||||
force bool
|
||||
|
||||
Source cli.Source
|
||||
}
|
||||
|
||||
func (o *MigrateDeleteOptions) Run() error {
|
||||
o.EC.Spin("Deleting migration...")
|
||||
defer o.EC.Spinner.Stop()
|
||||
|
||||
migrateDrv, err := migrate.NewMigrate(o.EC, true, o.Source.Name, o.Source.Kind)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in creation of new migrate instance %w", err)
|
||||
}
|
||||
|
||||
status, err := migrateDrv.GetStatus()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while retrieving migration status %w", err)
|
||||
}
|
||||
|
||||
if !o.all {
|
||||
if _, ok := status.Migrations[o.version]; !ok {
|
||||
return fmt.Errorf("version %v not found", o.version)
|
||||
}
|
||||
err := DeleteVersions(o.EC, []uint64{o.version}, o.Source)
|
||||
if err != nil {
|
||||
o.EC.Logger.Warn(errors.Wrap(err, "error in deletion of migration in source"))
|
||||
}
|
||||
versions := []uint64{o.version}
|
||||
err = migrateDrv.RemoveVersions(versions)
|
||||
} else if o.all {
|
||||
var sourceVersions, serverVersions []uint64
|
||||
for k, v := range status.Migrations {
|
||||
if v.IsApplied {
|
||||
serverVersions = append(serverVersions, k)
|
||||
}
|
||||
if v.IsPresent {
|
||||
sourceVersions = append(sourceVersions, k)
|
||||
}
|
||||
}
|
||||
// delete version history on server
|
||||
err = migrateDrv.RemoveVersions(serverVersions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error removing migration from server: %w", err)
|
||||
}
|
||||
// delete migrations history in project
|
||||
err = DeleteVersions(o.EC, sourceVersions, o.Source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error removing migration from project: %w", err)
|
||||
}
|
||||
}
|
||||
o.EC.Logger.Infof("Deleted migrations")
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeleteVersions(ec *cli.ExecutionContext, versions []uint64, source cli.Source) error {
|
||||
for _, v := range versions {
|
||||
delOptions := mig.CreateOptions{
|
||||
Version: strconv.FormatUint(v, 10),
|
||||
Directory: filepath.Join(ec.MigrationDir, source.Name),
|
||||
}
|
||||
err := delOptions.Delete()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to delete migrations from project for: %v : %w", v, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
231
cli/commands/migrate_delete_test.go
Normal file
231
cli/commands/migrate_delete_test.go
Normal file
@ -0,0 +1,231 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hasura/graphql-engine/cli/internal/testutil"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
. "github.com/onsi/gomega/gbytes"
|
||||
. "github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
var _ = Describe("migrate_delete", func() {
|
||||
var session *Session
|
||||
var teardown func()
|
||||
var hgeEndpoint string
|
||||
BeforeEach(func() {
|
||||
hgeEndPort, teardownHGE := testutil.StartHasura(GinkgoT(), testutil.HasuraVersion)
|
||||
hgeEndpoint = fmt.Sprintf("http://0.0.0.0:%s", hgeEndPort)
|
||||
|
||||
teardown = func() {
|
||||
session.Kill()
|
||||
teardownHGE()
|
||||
}
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
teardown()
|
||||
})
|
||||
|
||||
Context("migrate delete --all", func() {
|
||||
It("should delete the migrations on server and on source ", func() {
|
||||
projectDirectory := testutil.RandDirName()
|
||||
testutil.RunCommandAndSucceed(testutil.CmdOpts{
|
||||
Args: []string{"init", projectDirectory},
|
||||
})
|
||||
editEndpointInConfig(filepath.Join(projectDirectory, defaultConfigFilename), hgeEndpoint)
|
||||
|
||||
session = testutil.RunCommandAndSucceed(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;", "--database-name", "default"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
|
||||
str := string(session.Err.Contents())
|
||||
i := strings.Index(str, "\"version\"")
|
||||
version := str[i+10 : i+23]
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "apply", "--database-name", "default"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
wantKeywordList := []string{
|
||||
".*Applying migrations...*.",
|
||||
".*migrations*.",
|
||||
".*applied*.",
|
||||
}
|
||||
|
||||
for _, keyword := range wantKeywordList {
|
||||
Eventually(session.Err, 60*40).Should(Say(keyword))
|
||||
}
|
||||
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "delete", "--all"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
|
||||
Eventually(session.Err, 60*40).Should(Say("--database-name flag is required"))
|
||||
|
||||
args := strings.Join([]string{"yes", "|", testutil.CLIBinaryPath, "migrate", "delete", "--all", "--database-name", "default"}, " ")
|
||||
cmd := exec.Command("bash", "-c", args)
|
||||
cmd.Dir = projectDirectory
|
||||
session, err := Start(
|
||||
cmd,
|
||||
NewPrefixedWriter(testutil.DebugOutPrefix, GinkgoWriter),
|
||||
NewPrefixedWriter(testutil.DebugErrPrefix, GinkgoWriter),
|
||||
)
|
||||
Expect(err).To(BeNil())
|
||||
Eventually(session.Err, 60*40).Should(Say("Deleted migrations"))
|
||||
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "status", "--database-name", "default"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
Eventually(session.Err, 60*40).ShouldNot(Say(version))
|
||||
Eventually(session, 60*50).Should(Exit(0))
|
||||
})
|
||||
})
|
||||
|
||||
Context("migrate delete --version <version>", func() {
|
||||
It("should delete the migrations on server and on source ", func() {
|
||||
dirName := testutil.RandDirName()
|
||||
testutil.RunCommandAndSucceed(testutil.CmdOpts{
|
||||
Args: []string{"init", dirName},
|
||||
})
|
||||
|
||||
editEndpointInConfig(filepath.Join(dirName, defaultConfigFilename), hgeEndpoint)
|
||||
session = testutil.RunCommandAndSucceed(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;", "--database-name", "default"},
|
||||
WorkingDirectory: dirName,
|
||||
})
|
||||
|
||||
str := string(session.Err.Contents())
|
||||
i := strings.Index(str, "\"version\"")
|
||||
version := str[i+10 : i+23]
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "apply", "--database-name", "default"},
|
||||
WorkingDirectory: dirName,
|
||||
})
|
||||
wantKeywordList := []string{
|
||||
".*Applying migrations...*.",
|
||||
".*migrations*.",
|
||||
".*applied*.",
|
||||
}
|
||||
|
||||
for _, keyword := range wantKeywordList {
|
||||
Eventually(session.Err, 60*40).Should(Say(keyword))
|
||||
}
|
||||
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "delete", "--version", version, "--database-name", "default"},
|
||||
WorkingDirectory: dirName,
|
||||
})
|
||||
Eventually(session.Err, 60*40).Should(Say("Deleted migrations"))
|
||||
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "status", "--database-name", "default"},
|
||||
WorkingDirectory: dirName,
|
||||
})
|
||||
Eventually(session.Err, 60*40).ShouldNot(Say(version))
|
||||
Eventually(session, 60*50).Should(Exit(0))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Context("migrate delete --version <version> (config v2)", func() {
|
||||
It("should delete the migrations on server and on source ", func() {
|
||||
projectDirectory := testutil.RandDirName()
|
||||
testutil.RunCommandAndSucceed(testutil.CmdOpts{
|
||||
Args: []string{"init", projectDirectory, "--version", "2"},
|
||||
})
|
||||
|
||||
editEndpointInConfig(filepath.Join(projectDirectory, defaultConfigFilename), hgeEndpoint)
|
||||
session = testutil.RunCommandAndSucceed(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
|
||||
str := string(session.Err.Contents())
|
||||
i := strings.Index(str, "\"version\"")
|
||||
version := str[i+10 : i+23]
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "apply"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
wantKeywordList := []string{
|
||||
".*Applying migrations...*.",
|
||||
".*migrations*.",
|
||||
".*applied*.",
|
||||
}
|
||||
|
||||
for _, keyword := range wantKeywordList {
|
||||
Eventually(session.Err, 60*40).Should(Say(keyword))
|
||||
}
|
||||
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "delete", "--version", version},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
Eventually(session.Err, 60*40).Should(Say("Deleted migrations"))
|
||||
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "status"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
Eventually(session.Err, 60*40).ShouldNot(Say(version))
|
||||
Eventually(session, 60*50).Should(Exit(0))
|
||||
})
|
||||
})
|
||||
|
||||
Context("migrate delete --all (config v2)", func() {
|
||||
It("should delete the migrations on server and on source ", func() {
|
||||
projectDirectory := testutil.RandDirName()
|
||||
testutil.RunCommandAndSucceed(testutil.CmdOpts{
|
||||
Args: []string{"init", projectDirectory, "--version", "2"},
|
||||
})
|
||||
editEndpointInConfig(filepath.Join(projectDirectory, defaultConfigFilename), hgeEndpoint)
|
||||
|
||||
session = testutil.RunCommandAndSucceed(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "create", "schema_creation", "--up-sql", "create schema \"testing\";", "--down-sql", "drop schema \"testing\" cascade;"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
|
||||
str := string(session.Err.Contents())
|
||||
i := strings.Index(str, "\"version\"")
|
||||
version := str[i+10 : i+23]
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "apply"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
wantKeywordList := []string{
|
||||
".*Applying migrations...*.",
|
||||
".*migrations*.",
|
||||
".*applied*.",
|
||||
}
|
||||
|
||||
for _, keyword := range wantKeywordList {
|
||||
Eventually(session.Err, 60*40).Should(Say(keyword))
|
||||
}
|
||||
|
||||
args := []string{"migrate", "delete", "--all", "--force"}
|
||||
cmd := exec.Command(testutil.CLIBinaryPath, args...)
|
||||
cmd.Dir = projectDirectory
|
||||
session, err := Start(
|
||||
cmd,
|
||||
NewPrefixedWriter(testutil.DebugOutPrefix, GinkgoWriter),
|
||||
NewPrefixedWriter(testutil.DebugErrPrefix, GinkgoWriter),
|
||||
)
|
||||
Expect(err).To(BeNil())
|
||||
Eventually(session.Err, 60*40).Should(Say("Deleted migrations"))
|
||||
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "status"},
|
||||
WorkingDirectory: projectDirectory,
|
||||
})
|
||||
Eventually(session.Err, 60*40).ShouldNot(Say(version))
|
||||
Eventually(session, 60*50).Should(Exit(0))
|
||||
})
|
||||
})
|
||||
})
|
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
@ -98,15 +97,23 @@ func (o *migrateSquashOptions) run() error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range versions {
|
||||
delOptions := mig.CreateOptions{
|
||||
Version: strconv.FormatInt(v, 10),
|
||||
Directory: filepath.Join(o.EC.MigrationDir, o.Source.Name),
|
||||
}
|
||||
err = delOptions.Delete()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to delete source file")
|
||||
var uversions []uint64
|
||||
for _, version := range versions {
|
||||
if version < 0 {
|
||||
return fmt.Errorf("operation failed foound version value should >= 0, which is not expected")
|
||||
}
|
||||
uversions = append(uversions, uint64(version))
|
||||
}
|
||||
|
||||
// If the first argument is true then it deletes all the migration versions
|
||||
err = DeleteVersions(o.EC, uversions, o.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = migrateDrv.RemoveVersions(uversions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -62,6 +62,16 @@ var _ = Describe("migrate_squash", func() {
|
||||
Eventually(session.Err, 60*40).Should(Say(keyword))
|
||||
}
|
||||
Eventually(session, 60*40).Should(Exit(0))
|
||||
// verify files were deleted
|
||||
v := strings.Split(matches[0], ":")[1]
|
||||
Expect(filepath.Join(dirName, "migrations", "default", v)).ShouldNot(BeADirectory())
|
||||
// verify squashed migrations are deleted in statestore
|
||||
session = testutil.Hasura(testutil.CmdOpts{
|
||||
Args: []string{"migrate", "status", "--database-name", "default"},
|
||||
WorkingDirectory: dirName,
|
||||
})
|
||||
Eventually(session, 60).Should(Exit(0))
|
||||
Eventually(session.Out.Contents()).ShouldNot(ContainSubstring(v))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -402,7 +402,6 @@ func (h *HasuraDB) Drop() error {
|
||||
|
||||
func (h *HasuraDB) sendSchemaDumpQuery(m interface{}) (resp *http.Response, body []byte, err error) {
|
||||
request := h.config.Req.Clone()
|
||||
|
||||
request = request.Post(h.config.pgDumpURL.String()).Send(m)
|
||||
|
||||
for headerName, headerValue := range h.config.Headers {
|
||||
|
@ -11,11 +11,10 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/hasura/graphql-engine/cli/migrate/source"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
|
Loading…
Reference in New Issue
Block a user