diff --git a/commands.go b/commands.go index 646b9a8182..6dd05b0db8 100644 --- a/commands.go +++ b/commands.go @@ -385,6 +385,18 @@ func initCommands( }, nil }, + "migrate": func() (cli.Command, error) { + return &command.MigrateCommand{ + Meta: meta, + }, nil + }, + + "migrate list": func() (cli.Command, error) { + return &command.MigrateListCommand{ + Meta: meta, + }, nil + }, + "state": func() (cli.Command, error) { return &command.StateCommand{}, nil }, diff --git a/internal/command/arguments/migrate.go b/internal/command/arguments/migrate.go index 6287362ab5..5776a750c4 100644 --- a/internal/command/arguments/migrate.go +++ b/internal/command/arguments/migrate.go @@ -64,18 +64,37 @@ type MigrateApply struct { } // ParseMigrateApply parses command-line flags for "terraform migrate". +// The migration ID (containing /) can appear before or after flags. func ParseMigrateApply(args []string) (*MigrateApply, tfdiags.Diagnostics) { var diags tfdiags.Diagnostics var jsonOutput bool migrateApply := &MigrateApply{} + // Extract the migration ID from args before flag parsing, since Go's + // flag package stops at the first non-flag argument. + var flagArgs []string + for _, arg := range args { + if !strings.HasPrefix(arg, "-") && strings.Contains(arg, "/") { + if migrateApply.MigrationID != "" { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Too many migration IDs", + "Expected exactly one migration ID argument.", + )) + } + migrateApply.MigrationID = arg + } else { + flagArgs = append(flagArgs, arg) + } + } + cmdFlags := defaultFlagSet("migrate") cmdFlags.BoolVar(&migrateApply.DryRun, "dry-run", false, "dry-run") cmdFlags.BoolVar(&migrateApply.Step, "step", false, "step") cmdFlags.BoolVar(&jsonOutput, "json", false, "json") - if err := cmdFlags.Parse(args); err != nil { + if err := cmdFlags.Parse(flagArgs); err != nil { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Failed to parse command-line flags", @@ -83,16 +102,21 @@ func ParseMigrateApply(args []string) (*MigrateApply, tfdiags.Diagnostics) { )) } - args = cmdFlags.Args() - if len(args) != 1 { + if remaining := cmdFlags.Args(); len(remaining) > 0 { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, - "Invalid number of command line arguments", - "Expected exactly one positional argument: the migration ID (namespace/provider/name)", + "Unexpected arguments", + "Unexpected positional arguments after flags.", )) - } else { - migrateApply.MigrationID = args[0] + } + if migrateApply.MigrationID == "" { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Missing migration ID", + "Usage: terraform migrate [-dry-run] [-step]", + )) + } else { parts := strings.SplitN(migrateApply.MigrationID, "/", 4) if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" { diags = diags.Append(tfdiags.Sourceless( diff --git a/internal/command/migrate_command.go b/internal/command/migrate_command.go index 6eac15a19b..0cbe7f729b 100644 --- a/internal/command/migrate_command.go +++ b/internal/command/migrate_command.go @@ -17,11 +17,13 @@ type MigrateCommand struct { } func (c *MigrateCommand) Run(args []string) int { - // If the first arg looks like a migration ID (contains /), delegate - // directly to the apply command for convenience. - if len(args) > 0 && strings.Contains(args[0], "/") { - apply := &MigrateApplyCommand{Meta: c.Meta} - return apply.Run(args) + // If any arg looks like a migration ID (contains / and doesn't start + // with -), delegate to the apply command. + for _, arg := range args { + if strings.Contains(arg, "/") && !strings.HasPrefix(arg, "-") { + apply := &MigrateApplyCommand{Meta: c.Meta} + return apply.Run(args) + } } return cli.RunResultHelp