From 4acb2d3bdc4b7ec3f007e59eff745c484d3f575c Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Fri, 6 Mar 2026 12:21:43 +0100 Subject: [PATCH] feat: register migrate commands and fix argument ordering Register "migrate" and "migrate list" in the commands map. Fix argument parser to allow flags before or after the migration ID, since Go's flag package stops at the first non-flag argument. --- commands.go | 12 +++++++++ internal/command/arguments/migrate.go | 38 ++++++++++++++++++++++----- internal/command/migrate_command.go | 12 +++++---- 3 files changed, 50 insertions(+), 12 deletions(-) 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