You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
terraform/internal/command/migrate_run.go

128 lines
3.1 KiB

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package command
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/terraform/internal/ast"
"github.com/hashicorp/terraform/internal/migrate"
)
// MigrateRunCommand implements "terraform migrate run".
type MigrateRunCommand struct {
Meta
}
func (c *MigrateRunCommand) Run(args []string) int {
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("migrate run")
var migrationsDir string
cmdFlags.StringVar(&migrationsDir, "migrations-dir", ".", "directory containing migration JSON files")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
return 1
}
// Optional filter argument
var filter string
if args := cmdFlags.Args(); len(args) > 0 {
filter = args[0]
}
// Discover and filter migrations
migrations, err := migrate.DiscoverMigrations(migrationsDir)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error discovering migrations: %s", err))
return 1
}
migrations = migrate.FilterMigrations(migrations, filter)
if len(migrations) == 0 {
c.Ui.Output("No matching migrations found.")
return 0
}
// Parse .tf files in current directory
tfDir := "."
mod, err := loadModule(tfDir)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading Terraform files: %s", err))
return 1
}
// Execute migrations
for _, m := range migrations {
c.Ui.Output(fmt.Sprintf("Running: %s", m.Name))
if err := migrate.Execute(m, mod); err != nil {
c.Ui.Error(fmt.Sprintf("Error running migration %s: %s", m.Name, err))
return 1
}
}
// Write back
for filename, content := range mod.Bytes() {
if err := os.WriteFile(filename, content, 0644); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing %s: %s", filename, err))
return 1
}
}
c.Ui.Output(fmt.Sprintf("\nApplied %d migration(s) successfully.", len(migrations)))
return 0
}
// loadModule parses all .tf files in a directory into an ast.Module.
func loadModule(dir string) (*ast.Module, error) {
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("reading directory %s: %w", dir, err)
}
var files []*ast.File
for _, entry := range entries {
if entry.IsDir() {
continue
}
name := entry.Name()
if !strings.HasSuffix(name, ".tf") {
continue
}
path := filepath.Join(dir, name)
src, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading %s: %w", path, err)
}
f, err := ast.ParseFile(src, path, nil)
if err != nil {
return nil, err
}
files = append(files, f)
}
return ast.NewModule(files, dir, true, nil), nil
}
func (c *MigrateRunCommand) Help() string {
helpText := `
Usage: terraform [global options] migrate run [options] [filter]
Runs HCL migrations defined in JSON files. The optional filter argument is a
glob pattern matched against migration names (e.g., "v3to4/*").
Options:
-migrations-dir=DIR Directory containing migration JSON files (default: ".")
`
return strings.TrimSpace(helpText)
}
func (c *MigrateRunCommand) Synopsis() string {
return "Run HCL migrations"
}